Expo Auth Session
Expo Auth Session is a foundational module within the Expo ecosystem for implementing web browser-based authentication flows, such as OAuth 2.0 and OpenID Connect, across Android, iOS, and web platforms. It provides a unified API to manage the complexities of these authentication methods, leveraging `expo-web-browser` for browser interaction and `expo-crypto` for secure operations like Proof Key for Code Exchange (PKCE), which is now the recommended grant type over implicit flow due to enhanced security. The current stable version is 55.0.15, with releases typically synchronized with major Expo SDK updates, occurring approximately three times per year. Key differentiators include its seamless integration with Expo development builds and managed workflow capabilities, simplifying the setup of deep linking and redirect URIs across various environments, and providing hooks like `useAuthRequest` for easy React component integration.
Common errors
-
Error 400: redirect_uri_mismatch
cause The redirect URI registered with the OAuth provider does not exactly match the URI generated by `expo-auth-session`.fixDouble-check the redirect URI generated by `AuthSession.makeRedirectUri()` in your app's logs and ensure it is precisely added to the 'Authorized redirect URIs' list in your OAuth provider's settings (e.g., Google Cloud Console, GitHub OAuth App settings). Ensure your app's scheme in `app.json` is correctly set and matches for native builds. -
Cannot use the AuthSession proxy because the project full name is not defined.
cause You are attempting to use the deprecated `useProxy: true` option without providing the `projectNameForProxy` or after the proxy service has been removed/disabled for your Expo SDK version. [23]fixRemove `useProxy: true` from your `makeRedirectUri` options and configure direct deep linking/universal links with your OAuth provider. If forced to use an older SDK and proxy, ensure your project's full name (`@owner/slug`) is provided to `projectNameForProxy`. [23] -
Something went wrong trying to finish signing in. Please close this screen to go back to the app.
cause This generic error often indicates a problem with the deep linking mechanism preventing the browser from redirecting back to your application, especially after authentication on the proxy server. This can be due to incorrect scheme setup, issues with `useProxy`, or browser cookie policies. [19, 21]fixEnsure your app's scheme is correctly defined in `app.json` and in your native project configurations. Test with a custom Expo Development Build or standalone app, rather than Expo Go, as it provides more control over deep linking. Verify `WebBrowser.maybeCompleteAuthSession()` is called. -
OAuth flow canceled
cause The user closed the browser or cancelled the authentication prompt, or the redirect URI was misconfigured leading to an inability to return to the app.fixWhile user cancellation is expected, if it happens unexpectedly, review redirect URI configurations, network connectivity, and ensure deep linking is correctly set up. For iOS, ensure `WebBrowser.maybeCompleteAuthSession()` is active and prompt for permissions is clear.
Warnings
- breaking The `useProxy` option for `makeRedirectUri` was deprecated in SDK 48 and removed in subsequent versions. Relying on `auth.expo.io` as a proxy for redirects is no longer recommended due to security and reliability concerns, especially with evolving browser cookie policies. [13]
- gotcha Testing OAuth flows reliably in Expo Go can be challenging or impossible for providers that strictly validate redirect URIs (e.g., Google). Expo Go uses a generic `exp://` URI which often differs from what can be registered directly with OAuth providers, leading to `redirect_uri_mismatch` errors. [4, 5, 21]
- gotcha It is critical to properly configure your redirect URIs with your OAuth provider (e.g., GitHub, Google) to exactly match what `makeRedirectUri` generates for your specific environment (development, production, web, iOS, Android). Mismatches will result in `Error 400: redirect_uri_mismatch` or similar. [5, 14, 20]
- gotcha Client secrets should *never* be exposed in client-side code. For OAuth flows requiring a client secret (e.g., Authorization Code Grant with PKCE, exchanging code for token), this exchange must happen on a secure backend server. [4]
- gotcha On web platforms, `WebBrowser.maybeCompleteAuthSession()` must be called to ensure the authentication popup window closes correctly after the OAuth flow. Forgetting this can leave the browser window open. [2, 4]
Install
-
npm install expo-auth-session -
yarn add expo-auth-session -
pnpm add expo-auth-session
Imports
- useAuthRequest
const { useAuthRequest } = require('expo-auth-session');import { useAuthRequest } from 'expo-auth-session'; - makeRedirectUri
import AuthSession from 'expo-auth-session'; const redirectUri = AuthSession.makeRedirectUri();
import { makeRedirectUri } from 'expo-auth-session'; - startAsync
import AuthSession from 'expo-auth-session'; AuthSession.startAsync(...);
import { startAsync } from 'expo-auth-session'; - maybeCompleteAuthSession
import { maybeCompleteAuthSession } from 'expo-auth-session';import * as WebBrowser from 'expo-web-browser'; WebBrowser.maybeCompleteAuthSession();
Quickstart
import React, { useEffect } from 'react';
import { Button, View, Text, StyleSheet } from 'react-native';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
// Required for WebBrowser.maybeCompleteAuthSession() on web
WebBrowser.maybeCompleteAuthSession();
const discovery = {
authorizationEndpoint: 'https://github.com/login/oauth/authorize',
tokenEndpoint: 'https://github.com/login/oauth/access_token',
revocationEndpoint: 'https://api.github.com/applications/YOUR_CLIENT_ID/token',
};
export default function GitHubLogin() {
const redirectUri = makeRedirectUri({
scheme: 'your-app-scheme',
// useProxy: true, // Only for testing with Expo Go, deprecated since SDK 48
});
const [request, response, promptAsync] = useAuthRequest(
{
clientId: process.env.GITHUB_CLIENT_ID ?? '', // Replace with your GitHub OAuth App Client ID
scopes: ['user', 'repo', 'gist'],
redirectUri,
},
discovery
);
useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
console.log('Authorization code:', code);
// In a real app, you would exchange the 'code' for an access token on your backend server.
// For local testing, you might fetch it directly if safe and for demonstration.
} else if (response?.type === 'cancel') {
console.log('Authentication flow canceled.');
} else if (response?.type === 'error') {
console.error('Authentication error:', response.error?.message);
}
}, [response]);
return (
<View style={styles.container}>
<Text style={styles.header}>GitHub OAuth Example</Text>
<Button
disabled={!request}
title="Login with GitHub"
onPress={() => promptAsync()}
/>
{response && response.type === 'success' && (
<Text style={styles.status}>Logged in successfully!</Text>
)}
{response && response.type === 'error' && (
<Text style={styles.error}>Login failed: {response.error?.message}</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
header: {
fontSize: 24,
marginBottom: 20,
},
status: {
marginTop: 20,
color: 'green',
},
error: {
marginTop: 20,
color: 'red',
},
});