In a React Native expense app using expo-auth-session, why does tapping Sign in right after logout sign the same corporate user back in without a password prompt?
answer
- the app forgot, the browser did not
- provider session cookie in the system browser
- Prompt.Login or Prompt.SelectAccount
- preferEphemeralSession: iOS only, default false
- end the provider session too
basics
~20 sLogout cleared the app's tokens, but the identity provider's session cookie still lives in the system browser, so the next authorization request completes silently. Fix it with prompt: Prompt.Login or SelectAccount, an ephemeral iOS session, or ending the provider session.
solid answer
~40 s`openAuthSessionAsync` runs sign-in in the **system browser**, which by design shares cookies with the user's normal browsing, so employees get single sign-on. Deleting the app's tokens does not touch the identity provider's session cookie there; the next `promptAsync` reaches the provider, finds that session and redirects with a new code at once. Fixes, by intent: set `prompt: Prompt.Login` (re-authenticate) or `Prompt.SelectAccount` (switch accounts) in the `useAuthRequest` config, which `expo-auth-session`'s docs recommend over clearing cookies because that is mostly not possible on iOS; pass `preferEphemeralSession: true` to `promptAsync` on iOS for a private session with no shared cookies; or also end the provider session by opening its logout URL. Each trades away some single sign-on.
code
tsx · 26 linesimport { Button } from 'react-native';
import { makeRedirectUri, Prompt, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
const redirectUri = makeRedirectUri({ scheme: 'expenses', path: 'auth' });
export function SwitchAccountButton() {
const discovery = useAutoDiscovery('https://sso.example.com');
const [request, , promptAsync] = useAuthRequest(
{
clientId: 'expenses-mobile',
redirectUri,
scopes: ['openid', 'profile'],
prompt: Prompt.SelectAccount, // ask the provider, instead of trying to clear cookies
},
discovery,
);
return (
<Button
title="Use a different account"
disabled={!request}
// iOS only: a private browser session that shares no cookies with the user's browser
onPress={() => promptAsync({ preferEphemeralSession: true })}
/>
);
}go deeper
Remember that the sign-in page runs in the system browser, which keeps its own session separate from the app's tokens.
Explain the prompt values, what preferEphemeralSession does on iOS, and why the app cannot just clear cookies.
Match the fix to the device model: personal phones, account switching, shared tablets and step-up approval, each with its single sign-on cost.
Set the organisation's sign-out policy with the identity team: how far app logout propagates to the provider session and what that does to single sign-on.
## What the user sees and why An employee signs out of the expense app on a shared tablet, a colleague taps **Sign in**, and the app opens straight into the first employee's account. Nothing in the app is broken; the behaviour follows from where sign-in runs. - `expo-auth-session` opens the provider through `expo-web-browser`'s `openAuthSessionAsync`: `ASWebAuthenticationSession` on iOS, a Custom Tab on Android. - Both are the **system browser**, and Expo's docs describe sharing cookies with it as intended, so users already signed in to the company portal do not type their password again. - The app's logout removed its own tokens. It did **not** remove the provider's **session cookie** in the browser. - The next authorization request reaches the provider, which sees a live session and immediately redirects with a fresh `code`. ## The fixes and their trade-offs | Fix | Where | Effect | Cost | |---|---|---|---| | `prompt: Prompt.Login` in the `useAuthRequest` config | both platforms | the provider should make the user re-authenticate | no single sign-on for that request | | `prompt: Prompt.SelectAccount` | both platforms | the provider shows an account chooser | one extra tap; depends on provider support | | `promptAsync({ preferEphemeralSession: true })` | iOS only, default `false` | asks for a private browser session that shares no cookies | no single sign-on on iOS at all; honoured depending on the user's default browser | | open the provider's logout (end-session) URL during logout | both platforms | the provider ends its own session | another browser round trip at logout | `expo-auth-session`'s own documentation for the `Prompt` enum says to use it **in favour of clearing cookies, which is mostly not possible on iOS**. That is the key interview point: the app cannot reach into the system browser's cookie jar, so it has to ask the provider for the behaviour it wants. ## Choosing for an expense app 1. **Personal phones, one user each**: keep the default. Single sign-on with the company portal is a feature, and logout only needs to clear the app's tokens and revoke the refresh token. 2. **"Switch account" button**: send `Prompt.SelectAccount` (or `Prompt.Login`) on that path only, so the normal sign-in stays one tap. 3. **Shared or kiosk devices**: end the provider session at logout, and on iOS consider `preferEphemeralSession: true` so nothing persists between users. 4. **High-assurance actions** such as approving a large reimbursement: request `Prompt.Login` for that step. ## A logout sequence for shared devices 1. Revoke the refresh token with the provider or your backend while you still hold it. 2. Delete the app's stored tokens and clear in-memory session state. 3. Open the provider's logout URL in the system browser so its session ends too. 4. On the next sign-in, send `Prompt.Login`, and on iOS consider `preferEphemeralSession: true`. ## Verifying the fix - Sign in, sign out, then tap Sign in again on a **physical device** with a signed-in browser: the provider must show its login or account chooser. - Repeat on both platforms; the iOS-only option can make iOS pass while Android still reuses the session. - Confirm that ordinary sign-in on a personal device still gets single sign-on if that was the intent. ## Details that trip people up - `preferEphemeralSession` is an **iOS-only** option; on Android `openAuthSessionAsync` is a Custom Tab polyfill, and the option has no effect. - `Prompt.None` does the opposite of what people expect from its name: it forbids any login UI and fails with an error such as `login_required` if there is no session. - Revoking the refresh token (for providers that support revocation, `revokeAsync` exists) stops the old tokens working, but it does not end the browser session either. - Test this on devices with a real signed-in browser; a fresh simulator has no cookies and hides the problem.
- Why not simply clear the browser's cookies from the app at logout?The app does not own the system browser's cookie jar; `ASWebAuthenticationSession` and Custom Tabs run in the browser's context, not the app's. `expo-auth-session`'s docs say clearing cookies is mostly not possible on iOS and recommend the `prompt` parameter instead.
- What does turning on `preferEphemeralSession` cost a corporate user?On iOS the session no longer sees the user's existing sign-in to the company portal, so every sign-in asks for credentials and multi-factor again. It also only asks the browser for privacy; whether it is honoured depends on the user's default browser. Many teams therefore reserve it for shared devices.
Logging out of the app but not the provider is like handing back your hotel key card while the front desk still has you checked in: ask for a new key and they print one without checking your ID.
saying these in an interview costs you the question
- Deleting the app's tokens also signs the user out of the provider.
- The app can clear the system browser's cookies at logout.
- preferEphemeralSession also works on Android Custom Tabs.
- Prompt.None forces the provider to show a login screen.
- Revoking the refresh token ends the provider's browser session.