-
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