- 
                Notifications
    You must be signed in to change notification settings 
- Fork 2.2k
Description
Before opening, please confirm:
- I have searched for duplicate or closed issues and discussions.
- I have read the guide for submitting bug reports.
- I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.
JavaScript Framework
React Native
Amplify APIs
Authentication
Amplify Version
v6
Amplify Categories
auth
Backend
Amplify Gen 2
Environment information
 System:
    OS: macOS 15.6.1
    CPU: (10) arm64 Apple M2 Pro
    Memory: 500.70 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 23.4.0 - ~/.nvm/versions/node/v23.4.0/bin/node
    Yarn: 1.22.22 - /opt/homebrew/bin/yarn
    npm: 11.5.2 - ~/.nvm/versions/node/v23.4.0/bin/npm
    bun: 1.2.19 - /opt/homebrew/bin/bun
    Watchman: 2025.04.14.00 - /opt/homebrew/bin/watchman
  Browsers:
    Chrome: 139.0.7258.155
    Safari: 18.6
  npmPackages:
    %name%:  0.1.0 
    @aws-amplify/backend: ^1.11.0 => 1.16.1 
    @aws-amplify/backend-cli: ^1.8.0 => 1.8.0 
    @aws-amplify/react-native: ^1.1.10 => 1.1.10 
    @aws-amplify/rtn-web-browser: ^1.1.4 => 1.1.4 
    @aws-appsync/utils: 2.0.3 => 2.0.3 
    @aws-sdk/client-cognito-identity-provider: 3.799.0 => 3.799.0 
    @aws-sdk/client-dynamodb: 3.799.0 => 3.799.0 
    @aws-sdk/client-sso-oidc: 3.799.0 => 3.799.0 (3.622.0, 3.637.0, 3.624.0, 3.621.0)
    @aws-sdk/client-sts: 3.799.0 => 3.799.0 (3.622.0, 3.879.0, 3.624.0, 3.621.0)
    @aws-sdk/types: 3.775.0 => 3.775.0 (3.609.0, 3.387.0, 3.862.0, 3.398.0, 3.821.0, 3.734.0)
    @aws-sdk/util-dynamodb: 3.799.0 => 3.799.0 
    @babel/core: ^7.20.0 => 7.28.3 
    @babel/plugin-proposal-export-namespace-from: ^7.18.9 => 7.18.9 
    @babel/plugin-proposal-optional-chaining: ^7.0.0 => 7.21.0 
    @babel/plugin-transform-arrow-functions: ^7.0.0 => 7.27.1 
    @babel/plugin-transform-nullish-coalescing-operator: ^7.0.0 => 7.27.1 
    @babel/plugin-transform-shorthand-properties: ^7.0.0 => 7.27.1 
    @babel/plugin-transform-template-literals: ^7.0.0 => 7.27.1 
    @babel/preset-env: ^7.20.0 => 7.28.3 
    @babel/runtime: ^7.20.0 => 7.28.3 
    @config-plugins/ffmpeg-kit-react-native: 9.0.0 => 9.0.0 
    @expo-google-fonts/m-plus-1p: 0.2.3 => 0.2.3 
    @expo-google-fonts/montserrat: 0.2.3 => 0.2.3 
    @expo/config-plugins: ~11.0.5 => 11.0.5 (10.1.2)
    @expo/metro-runtime: ~6.1.0 => 6.1.1 
    @gorhom/bottom-sheet: 5.1.6 => 5.1.6 
    @legendapp/list: ^2.0.0-beta.4 => 2.0.0-next.25 
    @react-native-async-storage/async-storage: 2.2.0 => 2.2.0 
    @react-native-community/netinfo: 11.4.1 => 11.4.1 
    @react-native-menu/menu: ^1.2.4 => 1.2.4 
    @react-navigation/bottom-tabs: 7.4.6 => 7.4.6 
    @react-navigation/native: 7.1.17 => 7.1.17 
    @react-navigation/native-stack: 7.3.25 => 7.3.25 
    @sentry/react-native: ~6.20.0 => 6.20.0 
    @tanstack/query-codemods:  undefined ()
    @tanstack/react-query: ^5.85.5 => 5.85.5 
    @tanstack/react-query-persist-client: ^5.85.5 => 5.85.5 
    @types/i18n-js: 3.8.2 => 3.8.2 
    @types/jest: ^29.2.1 => 29.5.14 
    @types/lodash.filter: ^4.6.9 => 4.6.9 
    @types/node: 22.10.5 => 22.10.5 (24.3.0)
    @types/react: ~19.1.10 => 19.1.12 (18.3.24)
    @types/react-test-renderer: ^18.0.0 => 18.3.1 
    @typescript-eslint/eslint-plugin: ^8.33.1 => 8.41.0 
    @typescript-eslint/parser: ^8.33.1 => 8.41.0 
    @typescript-eslint/utils: ^8.31.1 => 8.41.0 
    ContextAPIMixpanel:  0.0.1 
    MixpanelDemo:  0.0.1 
    MixpanelExample:  0.0.1 
    SimpleMixpanel:  0.0.1 
    aws-amplify: ^6.15.5 => 6.15.5 
    aws-amplify/adapter-core:  undefined ()
    aws-amplify/adapter-core/internals:  undefined ()
    aws-amplify/analytics:  undefined ()
    aws-amplify/analytics/kinesis:  undefined ()
    aws-amplify/analytics/kinesis-firehose:  undefined ()
    aws-amplify/analytics/personalize:  undefined ()
    aws-amplify/analytics/pinpoint:  undefined ()
    aws-amplify/api:  undefined ()
    aws-amplify/api/internals:  undefined ()
    aws-amplify/api/server:  undefined ()
    aws-amplify/auth:  undefined ()
    aws-amplify/auth/cognito:  undefined ()
    aws-amplify/auth/cognito/server:  undefined ()
    aws-amplify/auth/enable-oauth-listener:  undefined ()
    aws-amplify/auth/server:  undefined ()
    aws-amplify/data:  undefined ()
    aws-amplify/data/server:  undefined ()
    aws-amplify/datastore:  undefined ()
    aws-amplify/in-app-messaging:  undefined ()
    aws-amplify/in-app-messaging/pinpoint:  undefined ()
    aws-amplify/push-notifications:  undefined ()
    aws-amplify/push-notifications/pinpoint:  undefined ()
    aws-amplify/storage:  undefined ()
    aws-amplify/storage/s3:  undefined ()
    aws-amplify/storage/s3/server:  undefined ()
    aws-amplify/storage/server:  undefined ()
    aws-amplify/utils:  undefined ()
    aws-cdk: 2.1013.0 => 2.1013.0 
    aws-cdk-lib: 2.194.0 => 2.194.0 
    babel-jest: ^29.2.1 => 29.7.0 
    buffer: 6.0.3 => 6.0.3 (4.9.2, 5.6.0, 5.7.1)
    constructs: ^10.3.0 => 10.4.2 
    date-fns: 4.1.0 => 4.1.0 
    esbuild: ^0.21.1 => 0.21.5 (0.25.9)
    eslint: ^9.28.0 => 9.34.0 
    eslint-config-prettier: 9.1.0 => 9.1.0 
    eslint-config-standard: 17.0.0 => 17.0.0 
    eslint-plugin-import: 2.26.0 => 2.26.0 
    eslint-plugin-n: ^15.0.0 => 15.7.0 
    eslint-plugin-promise: 6.6.0 => 6.6.0 
    eslint-plugin-react: ^7.37.5 => 7.37.5 
    eslint-plugin-react-native: 4.0.0 => 4.0.0 
    expo: 54.0.0-preview.11 => 54.0.0-preview.11 
    expo-application: ~7.0.4 => 7.0.4 
    expo-blur: ~15.0.4 => 15.0.4 
    expo-build-properties: ~1.0.5 => 1.0.5 
    expo-clipboard: ~8.0.4 => 8.0.4 
    expo-constants: ~18.0.5 => 18.0.5 
    expo-contacts: ~15.0.5 => 15.0.5 
    expo-dev-client: ~6.0.6 => 6.0.6 
    expo-device: ~8.0.4 => 8.0.4 
    expo-file-system: ~19.0.5 => 19.0.6 
    expo-font: ~14.0.5 => 14.0.5 
    expo-haptics: ~15.0.4 => 15.0.4 
    expo-image: ~3.0.3 => 3.0.4 
    expo-image-picker: ~17.0.5 => 17.0.5 
    expo-keep-awake: ~15.0.4 => 15.0.4 
    expo-linear-gradient: ~15.0.4 => 15.0.4 
    expo-linking: ~8.0.5 => 8.0.5 
    expo-localization: ~17.0.4 => 17.0.4 
    expo-secure-store: ~15.0.4 => 15.0.4 
    expo-share-intent: 4.1.1 => 4.1.1 
    expo-sharing: ~14.0.4 => 14.0.4 
    expo-sms: ~14.0.4 => 14.0.4 
    expo-splash-screen: ~31.0.6 => 31.0.6 
    expo-store-review: ~9.0.4 => 9.0.4 
    expo-video: ~3.0.7 => 3.0.7 
    expo-video-metadata: 1.5.0 => 1.5.0 
    expo-video-thumbnails: ~10.0.4 => 10.0.4 
    ffmpeg-kit-react-native: 6.0.2 => 6.0.2 
    follow-redirects: 1.15.9 => 1.15.9 
    i18next: 25.2.1 => 25.2.1 
    intl-pluralrules: 2.0.1 => 2.0.1 
    jest: ^29.2.1 => 29.7.0 
    jest-expo: ~54.0.5 => 54.0.5 
    libphonenumber-js: 1.11.19 => 1.11.19 (1.9.47)
    libphonenumber-js-core:  undefined (1.0.0)
    libphonenumber-js-max:  undefined (1.0.0)
    libphonenumber-js-min:  undefined (1.0.0)
    libphonenumber-js-mobile:  undefined (1.0.0)
    libphonenumber-js/build:  undefined ()
    libphonenumber-js/core:  undefined ()
    libphonenumber-js/max:  undefined ()
    libphonenumber-js/max/metadata:  undefined ()
    libphonenumber-js/min:  undefined ()
    libphonenumber-js/min/metadata:  undefined ()
    libphonenumber-js/mobile:  undefined ()
    libphonenumber-js/mobile/examples:  undefined ()
    libphonenumber-js/mobile/metadata:  undefined ()
    lodash: 4.17.21 => 4.17.21 
    lottie-react-native: ~7.3.1 => 7.3.3 
    mixpanel-react-native: 3.1.2 => 3.1.2 
    mixpanelexpo:  1.0.0 
    onesignal-expo-plugin: 2.0.3 => 2.0.3 
    patch-package: 6.4.7 => 6.4.7 
    postinstall-prepare: 1.0.1 => 1.0.1 
    prettier: 2.8.8 => 2.8.8 (2.3.2, 3.6.2, 1.19.1)
    react: 19.1.0 => 19.1.0 (19.2.0-canary-5252281c-20250408)
    react-dom: 19.1.0 => 19.1.0 (19.2.0-canary-5252281c-20250408)
    react-i18next: 15.4.1 => 15.4.1 
    react-native: 0.81.1 => 0.81.1 
    react-native-blurhash: ^2.1.1 => 2.1.2 
    react-native-compressor: ^1.12.0 => 1.12.0 
    react-native-edge-to-edge: ^1.6.2 => 1.7.0 
    react-native-gesture-handler: ~2.28.0 => 2.28.0 
    react-native-get-random-values: 1.11.0 => 1.11.0 
    react-native-ios-context-menu: 3.1.3 => 3.1.3 
    react-native-ios-utilities: 5.1.8 => 5.1.8 
    react-native-keyboard-controller: 1.18.5 => 1.18.5 
    react-native-mime-types: 2.5.0 => 2.5.0 
    react-native-mmkv: 2.12.2 => 2.12.2 
    react-native-nitro-audio-manager: 0.1.3 => 0.1.3 
    react-native-nitro-modules: 0.28.1 => 0.28.1 
    react-native-nitro-screen-recorder: ^0.3.5 => 0.3.5 
    react-native-onesignal: 5.2.13 => 5.2.13 
    react-native-reanimated: ~3.19.1 => 3.19.1 
    react-native-safe-area-context: ~5.6.0 => 5.6.1 
    react-native-screens: ~4.15.0 => 4.15.4 
    react-native-static-safe-area-insets: 2.2.0 => 2.2.0 
    react-native-url-polyfill: 2.0.0 => 2.0.0 
    react-native-vision-camera: 4.7.0 => 4.7.0 
    react-native-vision-camera-face-detector: 1.8.3 => 1.8.3 
    react-native-webview: 13.15.0 => 13.15.0 
    react-native-worklets-core: 1.5.0 => 1.5.0 
    react-native-youtube-iframe: 2.3.0 => 2.3.0 
    react-native-z-view: 0.2.4 => 0.2.4 
    react-test-renderer: 18.2.0 => 18.2.0 (19.1.0)
    ts-jest: ^29.1.1 => 29.4.1 
    ts-node: ^10.9.2 => 10.9.2 
    tsx: ^4.9.4 => 4.20.5 (4.19.4)
    typescript: ~5.9.2 => 5.9.2 (4.4.4, 4.9.5)
    uuid: 11.0.5 => 11.0.5 (9.0.1, 11.1.0, 3.3.2, 7.0.3)
    zeego: 3.0.6 => 3.0.6 
    zustand: 5.0.5 => 5.0.5 
  npmGlobalPackages:
    corepack: 0.30.0
    eas-cli: 16.17.4
    license-checker: 25.0.1
    npm: 11.5.2
Describe the bug
Okay so its hard to reproduce, but we keep having users that get randomly logged out of our application after varying different times. They just open the app on iOS (currently using react-native & expo) and they are just logged out. I've spent a ton of time trying to troubleshoot this bug and its absolutely driving us crazy. I hope this is a problem with my client-side code, but I have add all kinds of extra error logging and Sentry logging and it seems to just... skip it? They are just logged out.
Related Issues I have found:
- Users are randomly getting logged out amplify-swift#3972
- Users become signed out on app upgrade amplify-swift#3969 (Except this is not on app upgrade)
- Amplify does not call Cognito to refresh session after idToken/accessToken expiry #14406
I haven't been able to reproduce this in development builds, most likely because its on a fast-refresh / recompile time and the tokens are always valid, but its happening in production often. I have had dozens of complaints from users open up the app to just be on the walkthrough screen.
If this ends up being something going on in the Amplify caching system or Cognito system, I hope that this takes high priority and gets fixed.
Expected behavior
The user only is logged out:
- When they delete the app and re-install it
- When they hit logout
Reproduction steps
I'm using Zustand to manage a Global Authentication State:
export interface AuthStore {
  isVerified: boolean
  isMissingRequiredAttributes: boolean
  isAuthenticated: boolean
  currentUser: User | null
  setCurrentUser: (user: User | null) => void
  clearAuthStore: () => void
}Simplified Protected Routes with React Navigation:
function App(props: AppProps) {
  const { hasFinishedProcessingAuth } = useAuth()
  const isReadyToShowApp =
    hasFinishedProcessingAuth // && other permissives like cache rehydration
  useEffect(() => {
    if (isReadyToShowApp) {
      setTimeout(SplashScreen.hideAsync, 600)
    }
  }, [isReadyToShowApp])
  if (!isReadyToShowApp) {
    return null
  }
  const linking: LinkingOptions<NonNullable<unknown>> = {
     // ...
  }
  const $rootView: ViewStyle = {
    flex: 1,
  }
  return (
    <SafeAreaProvider
      initialMetrics={initialWindowMetrics}
      style={{ backgroundColor: colors.background }}
    >
        <AppNavigator linking={linking} />
    </SafeAreaProvider>
  )
}
export const AppNavigator = (props: NavigationProps) => {
  const { isAuthenticated, isVerified, isMissingRequiredAttributes } = useAuthenticationStore()
  return (
    <NavigationContainer
      {...props}
    >
      <AppStack
        isAuthenticated={isAuthenticated}
        isVerified={isVerified}
        isMissingRequiredAttributes={isMissingRequiredAttributes}
      />
    </NavigationContainer>
  )
}
// Simplified Auth Stack Layer
const AppStack = (props: AppStackProps) => {
  const { isAuthenticated, isVerified, isMissingRequiredAttributes } = props
  const initialRouteName = useMemo(() => {
    if (isAuthenticated && isVerified) {
      if (isMissingRequiredAttributes) {
        return "RequiredAttributes"
      } else {
        return "Tab"
      }
    } else {
      return "LoginSignUp"
    }
  }, [isAuthenticated, isVerified, isMissingRequiredAttributes])
  return (
    <Stack.Navigator
      screenOptions={{
        headerShown: false,
        navigationBarColor: colors.background,
      }}
      initialRouteName={initialRouteName}
    >
      {isAuthenticated && isVerified ? (
        <>
          {isMissingRequiredAttributes ? (
            <>
              <Stack.Screen
                name="RequiredAttributes"
                component={Screens.RequiredAttributesScreen}
              />
            </>
          ) : (
              <Stack.Screen name="Tab" component={TabNavigator} />
          )}
        </>
      ) : (
          <Stack.Screen
            name="LoginSignUp"
            component={Screens.LoginSignUpScreen}
            options={{ animation: "slide_from_right" }}
          />
      )}
    </Stack.Navigator>
  )
}Code Snippet
Backend Configuration:
// Backend.ts
import { defineBackend } from "@aws-amplify/backend"
import { auth } from "./auth/resource"
import { data } from "./data/resource"
import { storage } from "./storage/resource"
import { blockDuplicateEmails } from "./functions/blockDuplicateEmails/resource"
import { weeklyPushNotifications } from "../amplify/functions/weeklyPushNotifications/resource"
/**
 * @see https://docs.amplify.aws/react/build-a-backend/ to add storage, functions, and more
 */
const backend = defineBackend({
  auth,
  data,
  storage,
  blockDuplicateEmails,
  weeklyPushNotifications,
})
// Define Phone Table variable for batch query
backend.data.resources.cfnResources.cfnGraphqlApi.environmentVariables = {
  PHONENUMBER_TABLE: "PhoneNumber-btsurcazonc3vcefwbvlflcwbm-NONE",
}
const { cfnUserPoolClient } = backend.auth.resources.cfnResources
cfnUserPoolClient.refreshTokenValidity = 120//auth.resource.ts
import { defineAuth, secret } from "@aws-amplify/backend"
// import { blockDuplicateEmails } from "../functions/blockDuplicateEmails/resource"
import { linkAccounts } from "../functions/linkAccounts/resource"
/**
 * Define and configure your auth resource
 * @see https://docs.amplify.aws/gen2/build-a-backend/auth
 */
export const auth = defineAuth({
  loginWith: {
    email: {
      verificationEmailSubject: "Verify Your ReactApp Account",
      verificationEmailBody: (createCode: any) => `Your ReactApp verification code is: ${createCode()}`,
    },
    phone: {
      verificationMessage: (createCode) =>
        `Use this code to confirm your ReactApp account: ${createCode()}`,
    },
    externalProviders: {
      google: {
        clientId: secret("GOOGLE_CLIENT_ID"),
        clientSecret: secret("GOOGLE_CLIENT_SECRET"),
        attributeMapping: {
          email: "email",
          emailVerified: "email_verified",
          familyName: "family_name",
          givenName: "given_name",
          phoneNumber: "phone_number",
        },
        scopes: ["email", "openid", "profile", "phone"],
      },
      signInWithApple: {
        clientId: secret("SIWA_CLIENT_ID"),
        keyId: secret("SIWA_KEY_ID"),
        privateKey: secret("SIWA_PRIVATE_KEY"),
        teamId: secret("SIWA_TEAM_ID"),
        attributeMapping: {
          email: "email",
          givenName: "firstName",
          familyName: "lastName",
          emailVerified: "email_verified"
        },
        scopes: ["email", "name"],
      },
      callbackUrls: ["reactapp://"],
      logoutUrls: ["reactapp://"],
    },
  },
  accountRecovery: "EMAIL_ONLY",
  userAttributes: {
    birthdate: {
      mutable: true,
      required: false,
    },
    phoneNumber: {
      mutable: true,
      required: false,
    },
    givenName: {
      mutable: true,
      required: false,
    },
    familyName: {
      mutable: true,
      required: false,
    },
    preferredUsername: {
      mutable: true,
      required: false,
    },
    profilePicture: {
      mutable: true,
      required: false,
    },
  },
  triggers: {
    preSignUp: linkAccounts
  }
})I have a useAuth hook:
/**
 * This hook checks for changes in the authentication state, as well as checks the user session upon login.
 * @returns isAuthenticated - The user's current authentication state
 * @returns isVerified - The user's current verification state
 */
export function useAuth(): {
  isAuthenticated: boolean
  isVerified: boolean
  isMissingRequiredAttributes: boolean
  hasFinishedProcessingAuth: boolean
} {
  const [authStateChanged, setAuthStateChanged] = useState(false)
  const [hasFinishedProcessingAuth, setHasFinishedProcessingAuth] = useState(false)
  const resetRootStore = useStore((state) => state.resetRootStore)
  const { bustCache } = useCacheBuster()
  const { isAuthenticated, isVerified, isMissingRequiredAttributes, setAuthProp, setCurrentUser } =
    useAuthenticationStore()
  const handleSignInAndApplyAttributes = async () => {
    try {
      const client = generateClient<Schema>()
      const currentUser = await getCurrentUser()
      const { errors, data: userSigningIn } = await client.models.User.get(
        { id: currentUser.userId },
        { selectionSet: userSelectionSet, authMode: "userPool" },
      )
      if (!errors && userSigningIn) {
        // Full user exists in database
        setAuthProp({
          currentUser: userSigningIn,
          isAuthenticated: true,
          isVerified: true,
          isMissingRequiredAttributes: false,
        })
      } else if (!errors && userSigningIn === null) {
        // User exists in Cognito but not in database
        const { data: userAttributes } = await tryCatch(fetchUserAttributes())
        setAuthProp({
          isVerified: userAttributes?.email_verified === "true",
          isAuthenticated: true,
          isMissingRequiredAttributes: true,
        })
      } else {
        // Error occurred or fallback scenario
        const { data: userAttributes, error } = await tryCatch(fetchUserAttributes())
        if (!userAttributes || !userAttributes.sub) {
          reportCrash({ error, method: "UserAttributesBackupCheck" })
          setAuthProp({
            isAuthenticated: false,
            isVerified: false,
            isMissingRequiredAttributes: true,
          })
          return // Early return to avoid setting tracking IDs
        }
        // This scenario is unclear - consider if user should be authenticated
        setAuthProp({
          currentUser: {
            id: userAttributes.sub,
            firstName: capitalizeFirstLetter(userAttributes.given_name) || "",
            lastName: capitalizeFirstLetter(userAttributes.family_name) || "",
            username: userAttributes.preferred_username || "",
            phoneNumber: { phoneNumber: userAttributes.phone_number || "" },
            birthdate: userAttributes.birthdate,
            email: userAttributes.email || "",
            profileImageS3Path: userAttributes.picture,
          },
          isAuthenticated: true, // Changed from false - if we have user data, they're authenticated
          isVerified: userAttributes?.email_verified === "true",
        })
      }
    } catch (error) {
      reportCrash({ error, method: "handleSignInAndApplyAttributes" })
      setAuthProp({
        isAuthenticated: false,
        isVerified: false,
        isMissingRequiredAttributes: true,
      })
    } finally {
      setHasFinishedProcessingAuth(true)
    }
  }
  useEffect(() => {
    void (async function manageAuth() {
      const { data: session, error: authSessionError } = await tryCatch(fetchAuthSession())
      if (authSessionError) {
        reportCrash({
          error: authSessionError,
          method: "useAuth-fetchAuthSession",
          component: "app.tsx",
        })
        switch (authSessionError.name) {
          case "UserUnAuthenticatedException":
          case "UserNotFoundException":
          case "NotAuthorizedException": {
            // Something is wrong and the user does not appear to be authenticated
            // So force a log out and reset
            resetRootStore()
            setAuthProp({
              isAuthenticated: false,
              isVerified: false,
              isMissingRequiredAttributes: true,
            })
            setHasFinishedProcessingAuth(true)
            await bustCache()
            break
          }
          case "UserAlreadyAuthenticatedException": {
            // The user is authenticated already
            // So hard set a login
            await handleSignInAndApplyAttributes()
            break
          }
          case "NetworkError":
            // CANNOT DETERMINE AUTH STATUS SO DO NOTHING
            const result = await NetInfo.fetch()
            const alertString = result.isConnected
              ? translate("errors:maybeConnection")
              : translate("errors:noConnection")
            Alert.alert("Network Error", alertString, [
              {
                text: translate("common:cancel"),
                style: "cancel",
              },
              {
                text: translate("common:retry"),
                onPress: async () => {
                  const result = await NetInfo.refresh()
                  if (result.isConnected || result.isInternetReachable) {
                    await tryCatch(fetchAuthSession({ forceRefresh: true }))
                    setAuthStateChanged((prev) => !prev)
                  }
                },
              },
            ])
            setHasFinishedProcessingAuth(true)
            break
          default: {
            reportCrash({ error: authSessionError, method: "useAuth", component: "app.tsx" })
          }
        }
        return
      }
      save(storageKey.accessTokenExpiration, session.tokens?.accessToken.payload.exp ?? "")
      const userIsNotAuthenticated = !session.tokens
      if (userIsNotAuthenticated) {
        setCurrentUser(null)
        setAuthProp({
          isAuthenticated: false,
          isVerified: false,
          isMissingRequiredAttributes: true,
        })
        setHasFinishedProcessingAuth(true)
        return
      }
      await handleSignInAndApplyAttributes()
    })()
  }, [authStateChanged])
  useEffect(() => {
    const unsubscribe = Hub.listen("auth", async (data) => {
      if (data.payload.event === "tokenRefresh") {
        return
      }
      if (data.payload.event === "tokenRefresh_failure") {
        await handleTokenRefreshFailure()
        return
      }
      setAuthStateChanged((prev) => !prev)
    })
    return () => unsubscribe()
  }, [])
  const handleTokenRefreshFailure = async () => {
    try {
      // Attempt one more refresh
      const { data: session, error } = await tryCatch(fetchAuthSession({ forceRefresh: true }))
      if (error || !session?.tokens) {
        reportCrash({
          error: error ?? new Error("Token refresh failed and session is empty"),
          method: "handleTokenRefreshFailure",
          component: "useAuth",
        })
        // If it still fails, show user-friendly retry option
        Alert.alert(
          translate("errors:somethingWentWrong"), // "Your session has expired"
          translate("errors:poorConnectivity"), // "Please try again to continue using the app"
          [
            {
              text: translate("common:cancel"),
              style: "cancel",
              onPress: async () => {
                // User chose to cancel - log them out gracefully
                await bustCache()
                resetRootStore()
                setAuthProp({
                  isAuthenticated: false,
                  isVerified: false,
                  isMissingRequiredAttributes: true,
                })
              },
            },
            {
              text: translate("common:retry"),
              onPress: async () => {
                // Force a complete re-authentication check
                setAuthStateChanged((prev) => !prev)
              },
            },
          ],
        )
      } else {
        // Silent success - update stored expiration
        if (session.tokens?.accessToken.payload.exp) {
          setAuthStateChanged((prev) => !prev)
        }
      }
    } catch (error) {
      // Complete failure - this is rare but handle gracefully
      reportCrash({ error, method: "handleTokenRefreshFailure", component: "useAuth" })
      await bustCache()
      resetRootStore()
    }
  }
  return { isAuthenticated, isVerified, isMissingRequiredAttributes, hasFinishedProcessingAuth }
}My reportCrash function should log in Sentry when a user experiences a sign in error, but they just don't appear at all? It's like somehow just silently signing out the user?
I also have a function like so, that I call every time the app is opened that just makes sure that any API calls made are done with a fully refreshed user
export async function refreshAuthSessionOnlyIfExpired(): Promise<void> {
  const tokenExpiration = mmkvStorage.getNumber(storageKey.accessTokenExpiration) || 0
  const now = Math.floor(Date.now() / 1000)
  const FIVE_MINUTES = 60 * 5
  const tokenIsExpiredOrNearingExpiry = tokenExpiration < now + FIVE_MINUTES
  if (tokenIsExpiredOrNearingExpiry) {
    const { data: session, error } = await tryCatch(fetchAuthSession({ forceRefresh: true }))
    if (error as AuthError) {
      throw new Error(`AUTH_REFRESH_FAILURE: ${error?.message}`)
    }
    if (session?.tokens?.accessToken.payload.exp) {
      save(storageKey.accessTokenExpiration, session.tokens.accessToken.payload.exp)
    } else {
      throw new Error(`AUTH_REFRESH_FAILURE: No token expiration found in session.`)
    }
  } else {
    const secondsRemaining = tokenExpiration - now
    const minutesRemaining = Math.floor(secondsRemaining / 60)
    console.log(
      `Token is still valid, no need to refresh — ${minutesRemaining} minute(s) remaining.`,
    )
  }
}Log output
None
aws-exports.js
See below.
Manual configuration
{
  "auth": {
    "user_pool_id": "*****",
    "aws_region": "us-east-1",
    "user_pool_client_id": "*****",
    "identity_pool_id": "*****",
    "mfa_methods": [],
    "standard_required_attributes": [
      "email"
    ],
    "username_attributes": [
      "email",
      "phone_number"
    ],
    "user_verification_types": [
      "email",
      "phone_number"
    ],
    "groups": [],
    "mfa_configuration": "NONE",
    "password_policy": {
      "min_length": 8,
      "require_lowercase": true,
      "require_numbers": true,
      "require_symbols": true,
      "require_uppercase": true
    },
    "oauth": {
      "identity_providers": [
        "GOOGLE",
        "SIGN_IN_WITH_APPLE"
      ],
      "redirect_sign_in_uri": [
        "reactapp://"
      ],
      "redirect_sign_out_uri": [
        "reactapp://"
      ],
      "response_type": "code",
      "scopes": [
        "phone",
        "email",
        "openid",
        "profile",
        "aws.cognito.signin.user.admin"
      ],
      "domain": "d39edb9c00f802d4f76e.auth.us-east-1.amazoncognito.com"
    },
    "unauthenticated_identities_enabled": true
  },
}Additional configuration
No response
Mobile Device
iPhones 12 - 16
Mobile Operating System
iOS 18
Mobile Browser
N/A
Mobile Browser Version
N/A
Additional information and screenshots
No response