In an Expo app, which expo-web-browser function runs an OAuth sign-in in the system browser, and what does it use on iOS and Android?
answer
- not a WebView inside your app
- openAuthSessionAsync(url, redirectUrl)
- iOS: ASWebAuthenticationSession
- Android: Custom Tab plus a url listener
- success carries the redirect URL
basics
~20 sWebBrowser.openAuthSessionAsync(authUrl, redirectUrl) from expo-web-browser. On iOS it runs ASWebAuthenticationSession; on Android it opens a Custom Tab and waits for the redirect deep link. It resolves with { type: 'success', url } or a cancel or dismiss result.
solid answer
~40 sThe function is `WebBrowser.openAuthSessionAsync(url, redirectUrl, options)` from `expo-web-browser`; `expo-auth-session`'s `promptAsync` calls it for you. On **iOS** it is a thin wrapper over **`ASWebAuthenticationSession`**, which runs the page in a system browser sheet and hands back the callback URL. On **Android** there is no equivalent system API, so the library opens the page in a **Custom Tab** and listens for a `Linking` `url` event that starts with the redirect URL. It resolves with `{ type: 'success', url }` when the redirect arrives; backing out gives `{ type: 'cancel' }` on iOS and `{ type: 'dismiss' }` on Android. The point of all this is that the corporate password is typed into a browser the app cannot script or read, which an embedded `react-native-webview` login cannot promise.
code
typescript · 9 linesimport * as WebBrowser from 'expo-web-browser';
export async function openCorporateSignIn(authUrl: string, redirectUrl: string): Promise<string | null> {
const result = await WebBrowser.openAuthSessionAsync(authUrl, redirectUrl);
if (result.type === 'success') {
return result.url; // e.g. expenses://auth?code=...&state=...
}
return null; // 'cancel' on iOS, 'dismiss' on Android: the user backed out
}go deeper
Name the function, openAuthSessionAsync from expo-web-browser, and the rule that credentials never go through a WebView inside the app.
Explain the platform split: ASWebAuthenticationSession on iOS, a Custom Tab plus a Linking listener on Android, and the result types each produces.
Diagnose redirect problems: prefix mismatches on Android, missing scheme registration, concurrent sessions, and dismiss versus cancel handling.
Decide between expo-auth-session and a fully native library such as react-native-app-auth, weighing Expo integration against provider quirks and maintenance.
## The function and its job In an Expo or bare React Native app with `expo-web-browser` installed, OAuth and OpenID Connect sign-in goes through **`WebBrowser.openAuthSessionAsync(url, redirectUrl, options)`**. You give it the identity provider's authorization URL and the redirect URL your app registered; it shows the provider's login page in a system browser surface and resolves once the browser is sent to that redirect URL. Most apps never call it directly: `expo-auth-session` builds the authorization URL, and its `promptAsync` calls `openAuthSessionAsync` underneath. Knowing what happens there is what interviewers probe. ## What runs on each platform | | iOS | Android | |---|---|---| | Browser surface | `ASWebAuthenticationSession`, a system sheet | a **Custom Tab** in the user's browser | | How the app learns it finished | the session's callback receives the redirect URL, matched on its scheme | the redirect arrives as a deep link; the library listens for a `Linking` `url` event that **starts with** the redirect URL | | User backs out | resolves `{ type: 'cancel' }` | resolves `{ type: 'dismiss' }` when the app returns to the foreground without a redirect | | Extra options | `preferEphemeralSession`, `preferUniversalLinks` | the regular browser options, used by the Custom Tab | The Android side is a **polyfill** built from `openBrowserAsync`, an `AppState` listener and a `Linking` listener, because Android has no system call that does the whole job. That is why the redirect must be a URL the app can actually receive, and why a redirect URL that does not match the prefix you passed never produces `success`. ## What it resolves with - `{ type: 'success', url }`: the browser reached the redirect URL; `url` carries the query string with `code` and `state`, or an `error`. - `{ type: 'cancel' }` (iOS): the user closed the sheet or declined. - `{ type: 'dismiss' }`: on Android, the user came back without a redirect; on iOS, the app closed the sheet with `dismissAuthSession()`. It resolves in all of these cases; your code branches on `type`. ## Android options that affect the flow Because the Android side is built on `openBrowserAsync`, the regular browser options apply to the Custom Tab: - `useProxyActivity` (default `true`) launches the browser through a transparent activity with a different task affinity, so the browser is not destroyed when the app goes to the background, which is the app's normal state while the Custom Tab is in front. - `createTask` (default `true`) opens the browser in a new task; `showInRecents` (default `false`) controls whether that task appears in the recents view. - `browserPackage` picks a specific browser, and `warmUpAsync()` asks the Custom Tabs service to warm up the preferred browser before the user taps Sign in. ## Why not a login screen in react-native-webview A React Native app can embed a web page with the community `react-native-webview` component, and a login page in it will render. The problem is that the **host app controls that WebView**: it can inject JavaScript, read what is typed and read cookies. The user has no way to know the corporate password is safe, the web view does not share the user's existing browser session, and many identity providers refuse embedded user agents. The protocol reasoning is standard OAuth guidance for native apps; the React Native takeaway is simply to use `openAuthSessionAsync` (or a native library such as `react-native-app-auth`) and never a WebView for credentials. ## An expense app, concretely 1. The user taps "Sign in with your company account". 2. `expo-auth-session` builds the authorization URL and calls `openAuthSessionAsync` with it and `expenses://auth`. 3. The corporate identity provider's page shows in the system browser; if the employee is already signed in there, it may skip the password. 4. The provider redirects to `expenses://auth?code=...&state=...`; the promise resolves with `success` and that URL. 5. The app exchanges the code for tokens. ## Common mistakes - Starting a second session while one is open: `expo-auth-session` answers with `{ type: 'locked' }`, and `expo-web-browser`'s Android path throws. - Passing a redirect URL that differs from the one the provider will actually use, so Android never matches it. - Treating `dismiss` on Android as an error rather than an ordinary back-out. - Testing in Expo Go, which cannot use your custom scheme for the redirect; use a development build.
- Why does the Android implementation need a deep link at all when iOS does not?`ASWebAuthenticationSession` hands the callback URL straight back to the app. Android has no such system session, so `expo-web-browser` opens a Custom Tab and waits for the provider's redirect to reopen the app as a deep link, catching it with a `Linking` `url` listener. If the scheme is not registered, the redirect never reaches the app.
- What does `expo-auth-session` add on top of `openAuthSessionAsync`?It builds the authorization URL from a config and discovery document, generates and checks `state`, creates the PKCE verifier and challenge, prevents concurrent sessions with a `locked` result, and parses the returned URL into `params` and an `error`.
saying these in an interview costs you the question
- A react-native-webview login page is fine if it uses HTTPS.
- openAuthSessionAsync rejects the promise when the user cancels.
- Android uses ASWebAuthenticationSession through a compatibility layer.
- The app can read the password typed into the system browser.
- Expo Go is enough to test the full OAuth redirect.