AuthSession
API, refer to those docs for more information on the API.WebBrowser.maybeCompleteAuthSession()
to dismiss the web popup. If you forget to add this then the popup window will not close.AuthSession.makeRedirectUri()
this does a lot of the heavy lifting involved with universal platform support. Behind the scenes it uses expo-linking
.AuthSession.useAuthRequest()
, the hook allows for async setup which means mobile browsers won't block the authentication.request
is defined.promptAsync
in a user-interaction on web.IdentityServer 4
OAuth 2 | OpenID
Azure
OAuth 2 | OpenID
Apple
iOS Only
Coinbase
OAuth 2
Dropbox
OAuth 2
OAuth 2
Fitbit
OAuth 2
Firebase Phone
Recaptcha
GitHub
OAuth 2
OAuth 2 | OpenID
Imgur
OAuth 2
Okta
OAuth 2 | OpenID
OAuth 2
Slack
OAuth 2
Spotify
OAuth 2
Strava
OAuth 2
Twitch
OAuth 2
OAuth 2
Uber
OAuth 2
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
More Info | OpenID | Required | Available |
offline_access
isn't included then no refresh token will be returned.import * as React from 'react'; import { Button, Text, View } from 'react-native'; import * as AuthSession from 'expo-auth-session'; import * as WebBrowser from 'expo-web-browser'; WebBrowser.maybeCompleteAuthSession(); const useProxy = true; const redirectUri = AuthSession.makeRedirectUri({ useProxy, }); export default function App() { const discovery = AuthSession.useAutoDiscovery('https://demo.identityserver.io'); // Create and load an auth request const [request, result, promptAsync] = AuthSession.useAuthRequest( { clientId: 'native.code', redirectUri, scopes: ['openid', 'profile', 'email', 'offline_access'], }, discovery ); return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Button title="Login!" disabled={!request} onPress={() => promptAsync({ useProxy })} /> {result && <Text>{JSON.stringify(result, null, 2)}</Text>} </View> ); }
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
Get Your Config | OpenID | Supported | Available |
import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); export default function App() { // Endpoint const discovery = useAutoDiscovery('https://login.microsoftonline.com/<TENANT_ID>/v2.0'); // Request const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', scopes: ['openid', 'profile', 'email', 'offline_access'], redirectUri: makeRedirectUri({ scheme: 'your.app' }), }, discovery ); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
Get Your Config | OAuth 2.0 | Supported | Not Available |
redirectUri
requires 2 slashes (://
).exp://localhost:19000/--/
https://localhost:19006
expo start:web --https
to run with https, auth won't work otherwise.your-scheme://
expo.scheme: 'your-scheme'
, then added to the app code with makeRedirectUri({ native: 'your-scheme://' })
)useProxy
) because they don't allow @
in their redirect URIs.https://yourwebsite.com
import { exchangeCodeAsync, makeRedirectUri, TokenResponse, useAuthRequest, } from "expo-auth-session"; import * as WebBrowser from "expo-web-browser"; import * as React from "react"; import { Button } from "react-native"; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: "https://www.coinbase.com/oauth/authorize", tokenEndpoint: "https://api.coinbase.com/oauth/token", revocationEndpoint: "https://api.coinbase.com/oauth/revoke", }; const redirectUri = makeRedirectUri({ scheme: 'your.app' }); const CLIENT_ID = "CLIENT_ID"; export default function App() { const [request, response, promptAsync] = useAuthRequest( { clientId: CLIENT_ID, scopes: ["wallet:accounts:read"], redirectUri, }, discovery ); const { // The token will be auto exchanged after auth completes. token, exchangeError, } = useAutoExchange( response?.type === "success" ? response.params.code : null ); React.useEffect(() => { if (token) { console.log("My Token:", token.accessToken); } }, [token]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); } type State = { token: TokenResponse | null; exchangeError: Error | null; }; // A hook to automatically exchange the auth token for an access token. // this should be performed in a server and not here in the application. // For educational purposes only: function useAutoExchange(code?: string): State { const [state, setState] = React.useReducer( (state: State, action: Partial<State>) => ({ ...state, ...action }), { token: null, exchangeError: null } ); const isMounted = useMounted(); React.useEffect(() => { if (!code) { setState({ token: null, exchangeError: null }); return; } exchangeCodeAsync( { clientId: CLIENT_ID, clientSecret: "CLIENT_SECRET", code, redirectUri, }, discovery ) .then((token) => { if (isMounted.current) { setState({ token, exchangeError: null }); } }) .catch((exchangeError) => { if (isMounted.current) { setState({ exchangeError, token: null }); } }); }, [code]); return state; } function useMounted() { const isMounted = React.useRef(true); React.useEffect(() => { return () => { isMounted.current = false; }; }, []); return isMounted; }
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
Get Your Config | OAuth 2.0 | Not Supported | Not Available |
usePKCE: false
) otherwise you'll get an error about code_challenge
being included in the query string.responseType: ResponseType.Code
is used (default behavior) the redirectUri
must be https
. This means that code exchange auth cannot be done on native without useProxy
enabled.ResponseType.Code
) will only work in native with useProxy: true
.import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest } from 'expo-auth-session'; import { Button, Platform } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://www.dropbox.com/oauth2/authorize', tokenEndpoint: 'https://www.dropbox.com/oauth2/token', }; const useProxy = Platform.select({ web: false, default: true }); export default function App() { const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', // There are no scopes so just pass an empty array scopes: [], // Dropbox doesn't support PKCE usePKCE: false, // For usage in managed apps using the proxy redirectUri: makeRedirectUri({ scheme: 'your.app', useProxy, }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync({ useProxy }); }} /> ); }
import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://www.dropbox.com/oauth2/authorize', tokenEndpoint: 'https://www.dropbox.com/oauth2/token', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { responseType: ResponseType.Token, clientId: 'CLIENT_ID', // There are no scopes so just pass an empty array scopes: [], // Dropbox doesn't support PKCE usePKCE: false, redirectUri: makeRedirectUri({ scheme: 'your.app' }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { access_token } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
More Info | OAuth | Supported | Not Available |
exp://
cannot be added to your Facebook app as a redirect.https://developers.facebook.com/apps/<YOUR ID>/fb-login/settings/
https://auth.expo.io/@username/slug
and any other web URLs you may want to add.expoClientId: '<YOUR FBID>'
. Ex: { expoClientId: '474614477183384' }
(no fb
prefix).⚠️If you forget to add the correct URL to the "Valid OAuth Redirect URIs", you will get an error like:Can't Load URL: The domain of this URL isn't included in the app's domains. To be able to load this URL, add all domains and subdomains of your app to the App Domains field in your app settings.
expo-facebook
module for native auth as it supports some nonstandard OAuth features implemented by Facebook.fb
followed by the project ID (ex: fb145668956753819
):facebookScheme: 'fb<YOUR FBID>'
to your app.config.js or app.json. Example: { facebookScheme: "fb145668956753819" }
(notice the fb
prefix).eas build
or expo build:ios
.eas build
or expo build:android
.expo prebuild
, or add the redirects manually with npx uri-scheme add fb<YOUR FBID>
yarn ios
& yarn android
https://developers.facebook.com/apps/<YOUR ID>/settings/basic/
+ Add Platform
, then select iOS
.expo.ios.bundleIdentifier
. If you don't have one set, run expo prebuild
to create one (then rebuild the native app).iosClientId: '<YOUR FBID>'
or clientId
. Ex: { iosClientId: '474614477183384' }
(no fb
prefix).https://developers.facebook.com/apps/<YOUR ID>/settings/basic/
+ Add Platform
, then select Android
.expo.android.package
. If you don't have one set, run expo prebuild
to create one (then rebuild the native app)..MainActivity
, i.e. com.bacon.yolo15.MainActivity
.keytool -exportcert -alias androiddebugkey -keystore android/app/debug.keystore | openssl sha1 -binary | openssl base64
you don't need a password, but it is recommended.XX2BBI1XXXXXXXX3XXXX44XX5XX=
into the console.androidClientId: '<YOUR FBID>'
or clientId
. Ex: { androidClientId: '474614477183384' }
(no fb
prefix).adb logcat
and look for any runtime Errors.Didn't find class "com.facebook.CustomTabActivity" on ...
then you may need to remove the native Facebook code for expo-facebook
from the AndroidManifest.xml:- <activity android:name="com.facebook.CustomTabActivity" android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.VIEW"/> - <category android:name="android.intent.category.DEFAULT"/> - <category android:name="android.intent.category.BROWSABLE"/> - <data android:scheme="fb<YOUR ID>"/> - </intent-filter> - </activity>
<data android:scheme="fb<YOUR ID>"/>
to the .MainActivity
intent-filter
or use npx uri-scheme add fb<YOUR ID> --android
.⚠️Thenative
redirect URI must be formatted likefb<YOUR FBID>://authorize
, ex:fb474614477183384://authorize
. Using the
No redirect URI in the params: No redirect present in URI
.://authorize
then you will get an error like: Can't Load URL: The domain of this URL isn't included in the app's domains. To be able to load this URL, add all domains and subdomains of your app to the App Domains field in your app settings.
https://developers.facebook.com/apps/<YOUR ID>/fb-login/settings/
https://localhost:19006
and any other web URLs you may want to add.webClientId: '<YOUR FBID>'
. Ex: { webClientId: '474614477183384' }
(no fb
prefix).expo start --https
, ensure this is the only server running, otherwise the port will not be 19006
.import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import * as Facebook from 'expo-auth-session/providers/facebook'; import { ResponseType } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); export default function App() { const [request, response, promptAsync] = Facebook.useAuthRequest({ clientId: '<YOUR FBID>', responseType: ResponseType.Code, }); React.useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import * as Facebook from 'expo-auth-session/providers/facebook'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); export default function App() { const [request, response, promptAsync] = Facebook.useAuthRequest({ clientId: '<YOUR FBID>', }); React.useEffect(() => { if (response?.type === 'success') { const { authentication } = response; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import * as Facebook from 'expo-auth-session/providers/facebook'; import { ResponseType } from 'expo-auth-session'; import { initializeApp } from 'firebase/app'; import { getAuth, FacebookAuthProvider, signInWithCredential } from 'firebase/auth'; import { Button } from 'react-native'; // Initialize Firebase initializeApp({ /* Config */ }); WebBrowser.maybeCompleteAuthSession(); export default function App() { const [request, response, promptAsync] = Facebook.useAuthRequest({ responseType: ResponseType.Token, clientId: '<YOUR FBID>', }); React.useEffect(() => { if (response?.type === 'success') { const { access_token } = response.params; const auth = getAuth(); const provider = new FacebookAuthProvider(); const credential = provider.credential(access_token); // Sign in with the credential from the Facebook user. signInWithCredential(auth, credential); } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
Get Your Config | OAuth 2.0 | Supported | Not Available |
exp://localhost:19000/--/*
https://auth.expo.io/@you/your-app
com.your.app://*
https://yourwebsite.com/*
redirectUri
requires 2 slashes (://
).import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest } from 'expo-auth-session'; import { Button, Platform } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://www.fitbit.com/oauth2/authorize', tokenEndpoint: 'https://api.fitbit.com/oauth2/token', revocationEndpoint: 'https://api.fitbit.com/oauth2/revoke', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', scopes: ['activity', 'sleep'], redirectUri: makeRedirectUri({ scheme: 'your.app' }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session'; import { Button, Platform } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); const useProxy = Platform.select({ web: false, default: true }); // Endpoint const discovery = { authorizationEndpoint: 'https://www.fitbit.com/oauth2/authorize', tokenEndpoint: 'https://api.fitbit.com/oauth2/token', revocationEndpoint: 'https://api.fitbit.com/oauth2/revoke', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { responseType: ResponseType.Token, clientId: 'CLIENT_ID', scopes: ['activity', 'sleep'], // For usage in managed apps using the proxy redirectUri: makeRedirectUri({ useProxy, scheme: 'your.app' }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { access_token } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync({ useProxy }); }} /> ); }
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
Get Your Config | OAuth 2.0 | Supported | Not Available |
exp://localhost:19000/--/*
https://auth.expo.io/@you/your-app
com.your.app://*
https://yourwebsite.com/*
redirectUri
requires 2 slashes (://
).revocationEndpoint
is dynamic and requires your config.clientId
.import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://github.com/login/oauth/authorize', tokenEndpoint: 'https://github.com/login/oauth/access_token', revocationEndpoint: 'https://github.com/settings/connections/applications/<CLIENT_ID>', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', scopes: ['identity'], redirectUri: makeRedirectUri({ scheme: 'your.app' }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
Get Your Config | OpenID | Supported | Available |
Be sure to install the peerDependencyyarn add expo-application
expoClientId
: Proxy client ID for use in the Expo Go on iOS and Android.iosClientId
: iOS native client ID for use in standalone and bare workflow.androidClientId
: Android native client ID for use in standalone, bare workflow.webClientId
: Expo web client ID for use in the browser.expo login
. This will be part of the redirect URL.expoClientId
.iosClientId
.ios.bundleIdentifier
in your app.json.npx uri-scheme add <your bundle id> --ios
expo prebuild
and run yarn ios
expo build:ios -t simulator
on eas build
expo build:ios
or eas build
Error 401: invalid_client
The OAuth client was not found
: Ensure the clientId is defined correctly in your React code. Either as 11111111-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com
or without the .apps.googleusercontent.com
extension.androidClientId
.android.package
in your app.json.android.package
(ex. com.myname.mycoolapp:/
).npx uri-scheme add <your android.package> --android
expo credentials:manager -p android
then select "Update upload Keystore" -> "Generate new keystore" -> "Go back to experience overview"A1:B2:C3
but longer.expo prebuild
and run yarn android
expo build:android
webClientId
.expo start:web --https
.import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import * as Google from 'expo-auth-session/providers/google'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); export default function App() { const [request, response, promptAsync] = Google.useAuthRequest({ expoClientId: 'GOOGLE_GUID.apps.googleusercontent.com', iosClientId: 'GOOGLE_GUID.apps.googleusercontent.com', androidClientId: 'GOOGLE_GUID.apps.googleusercontent.com', webClientId: 'GOOGLE_GUID.apps.googleusercontent.com', }); React.useEffect(() => { if (response?.type === 'success') { const { authentication } = response; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
YOUR_GUID
with your "Web client ID" and open this link:WebBrowser.maybeCompleteAuthSession();
from the root URL of your app.import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { ResponseType } from 'expo-auth-session'; import * as Google from 'expo-auth-session/providers/google'; import { initializeApp } from 'firebase/app'; import { getAuth, GoogleAuthProvider, signInWithCredential } from 'firebase/auth'; import { Button } from 'react-native'; // Initialize Firebase initializeApp({ /* Config */ }); WebBrowser.maybeCompleteAuthSession(); export default function App() { const [request, response, promptAsync] = Google.useIdTokenAuthRequest( { clientId: 'Your-Web-Client-ID.apps.googleusercontent.com', }, ); React.useEffect(() => { if (response?.type === 'success') { const { id_token } = response.params; const auth = getAuth(); const provider = new GoogleAuthProvider(); const credential = provider.credential(id_token); signInWithCredential(auth, credential); } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
Get Your Config | OAuth 2.0 | Supported | Not Available |
clientId
).import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest } from 'expo-auth-session'; import { Button, Platform } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); const discovery = { authorizationEndpoint: 'https://api.imgur.com/oauth2/authorize', tokenEndpoint: 'https://api.imgur.com/oauth2/token', }; export default function App() { // Request const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', clientSecret: 'CLIENT_SECRET', redirectUri: makeRedirectUri({ scheme: 'your.app', }), // imgur requires an empty array scopes: [], }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session'; import { Button, Platform } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); const discovery = { authorizationEndpoint: 'https://api.imgur.com/oauth2/authorize', tokenEndpoint: 'https://api.imgur.com/oauth2/token', }; export default function App() { // Request const [request, response, promptAsync] = useAuthRequest( { responseType: ResponseType.Token, clientId: 'CLIENT_ID', redirectUri: makeRedirectUri({ scheme: 'your.app', }), scopes: [], }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { access_token } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
Sign-up > Applications | OpenID | Supported | Available |
redirectUri
, Okta will provide you with one.import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session'; import { Button, Platform } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); const useProxy = Platform.select({ web: false, default: true }); export default function App() { // Endpoint const discovery = useAutoDiscovery('https://<OKTA_DOMAIN>.com/oauth2/default'); // Request const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', scopes: ['openid', 'profile'], // For usage in managed apps using the proxy redirectUri: makeRedirectUri({ // For usage in bare and standalone native: 'com.okta.<OKTA_DOMAIN>:/callback', useProxy, }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync({ useProxy }); }} /> ); }
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
Get Your Config | OAuth 2.0 | Supported | Not Available |
exp://localhost:19000/--/*
https://auth.expo.io/@you/your-app
com.your.app://*
https://yourwebsite.com/*
redirectUri
requires 2 slashes (://
).import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://www.reddit.com/api/v1/authorize.compact', tokenEndpoint: 'https://www.reddit.com/api/v1/access_token', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', scopes: ['identity'], redirectUri: makeRedirectUri({ // For usage in bare and standalone native: 'your.app://redirect', }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
installed
option for your app on Reddit to use implicit grant.import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://www.reddit.com/api/v1/authorize.compact', tokenEndpoint: 'https://www.reddit.com/api/v1/access_token', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { responseType: ResponseType.Token, clientId: 'CLIENT_ID', scopes: ['identity'], redirectUri: makeRedirectUri({ scheme: 'your.app' }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { access_token } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
Get Your Config | OAuth 2.0 | Supported | Not Available |
redirectUri
requires 2 slashes (://
).redirectUri
can be defined under the "OAuth & Permissions" section of the website.clientId
and clientSecret
can be found in the "App Credentials" section.revocationEndpoint
is not available.import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://slack.com/oauth/authorize', tokenEndpoint: 'https://slack.com/api/oauth.access', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', scopes: ['emoji:read'], redirectUri: makeRedirectUri({ scheme: 'your.app' }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
Get Your Config | OAuth 2.0 | Supported | Not Available |
exp://localhost:19000/--/
https://localhost:19006
makeRedirectUri({ path: '/' })
.expo start:web --https
to run with https, auth won't work otherwise.your-scheme://
expo.scheme: 'your-scheme'
, then added to the app code with makeRedirectUri({ native: 'your-scheme://' })
)https://auth.expo.io/@username/slug
useProxy
is enabled (native only), change username for your Expo username and slug for the slug
in your app.json. Try not to change the slug for your app after using it for auth as this can lead to versioning issues.https://yourwebsite.com
access_token
without the need for a code exchange server request.import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://accounts.spotify.com/authorize', tokenEndpoint: 'https://accounts.spotify.com/api/token', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', scopes: ['user-read-email', 'playlist-modify-public'], // In order to follow the "Authorization Code Flow" to fetch token after authorizationEndpoint // this must be set to false usePKCE: false, redirectUri: makeRedirectUri({ scheme: 'your.app' }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://accounts.spotify.com/authorize', tokenEndpoint: 'https://accounts.spotify.com/api/token', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { responseType: ResponseType.Token, clientId: 'CLIENT_ID', scopes: ['user-read-email', 'playlist-modify-public'], // In order to follow the "Authorization Code Flow" to fetch token after authorizationEndpoint // this must be set to false usePKCE: false, redirectUri: makeRedirectUri({ scheme: 'your.app' }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { access_token } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
Get Your Config | OAuth 2.0 | Supported | Not Available |
com.bacon.myapp://redirect
the domain would be redirect
.import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://www.strava.com/oauth/mobile/authorize', tokenEndpoint: 'https://www.strava.com/oauth/token', revocationEndpoint: 'https://www.strava.com/oauth/deauthorize', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', scopes: ['activity:read_all'], redirectUri: makeRedirectUri({ // For usage in bare and standalone // the "redirect" must match your "Authorization Callback Domain" in the Strava dev console. native: 'your.app://redirect', }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
const { accessToken } = await AuthSession.exchangeCodeAsync( { clientId: request?.clientId, redirectUri, code: result.params.code, extraParams: { // You must use the extraParams variation of clientSecret. // Never store your client secret on the client. client_secret: 'CLIENT_SECRET', }, }, { tokenEndpoint: 'https://www.strava.com/oauth/token' } );
Website | Provider | PKCE | Auto Discovery | Scopes |
---|---|---|---|---|
Get your Config | OAuth | Supported | Not Available | Info |
import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://id.twitch.tv/oauth2/authorize', tokenEndpoint: 'https://id.twitch.tv/oauth2/token', revocationEndpoint: 'https://id.twitch.tv/oauth2/revoke', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', redirectUri: makeRedirectUri({ scheme: 'your.app' }), scopes: ['openid', 'user_read', 'analytics:read:games'], }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://id.twitch.tv/oauth2/authorize', tokenEndpoint: 'https://id.twitch.tv/oauth2/token', revocationEndpoint: 'https://id.twitch.tv/oauth2/revoke', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { responseType: ResponseType.Token, clientId: 'CLIENT_ID', redirectUri: makeRedirectUri({ scheme: 'your.app' }), scopes: ['openid', 'user_read', 'analytics:read:games'], }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { access_token } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
Website | Provider | PKCE | Auto Discovery | Scopes |
---|---|---|---|---|
Get your Config | OAuth | Supported | Not Available | Info |
response
of useAuthRequest
to always be {type: 'dismiss'}
.https://auth.expo.io/@you/your-app
com.your.app://
expo start --https
): https://localhost:19006
(no ending slash)redirectUri
requires 2 slashes (://
).exp://localhost:19000
cannot be added to your Twitter app as a redirect.import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest } from 'expo-auth-session'; import { Button, Platform } from 'react-native'; const useProxy = Platform.select({ web: false, default: true }); WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: "https://twitter.com/i/oauth2/authorize", tokenEndpoint: "https://twitter.com/i/oauth2/token", revocationEndpoint: "https://twitter.com/i/oauth2/revoke", }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', redirectUri: makeRedirectUri({ scheme: 'your.app', useProxy }), usePKCE: true, scopes: [ "tweet.read", ], }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync({ useProxy }); }} /> ); }
Website | Provider | PKCE | Auto Discovery |
---|---|---|---|
Get Your Config | OAuth 2.0 | Supported | Not Available |
redirectUri
requires 2 slashes (://
).scopes
can be difficult to get approved.import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://login.uber.com/oauth/v2/authorize', tokenEndpoint: 'https://login.uber.com/oauth/v2/token', revocationEndpoint: 'https://login.uber.com/oauth/v2/revoke', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', scopes: ['profile', 'delivery'], redirectUri: makeRedirectUri({ scheme: 'your.app' }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://login.uber.com/oauth/v2/authorize', tokenEndpoint: 'https://login.uber.com/oauth/v2/token', revocationEndpoint: 'https://login.uber.com/oauth/v2/revoke', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { responseType: ResponseType.Token, clientId: 'CLIENT_ID', scopes: ['profile', 'delivery'], redirectUri: makeRedirectUri({ scheme: 'your.app' }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { access_token } = response.params; } }, [response]); return ( <Button disabled={!request} title="Login" onPress={() => { promptAsync(); }} /> ); }
https://auth.expo.io/@yourname/your-app
AuthSession.makeRedirectUri({ useProxy: true })
to create this URI.slug
, or owner
properties in the app.json. We highly recommend not using the proxy in custom apps (standalone, bare, custom).promptAsync({ useProxy: true, redirectUri })
exp://exp.host/@yourname/your-app
expo publish
'd and opened in the Expo Go app.AuthSession.makeRedirectUri({ useProxy: false })
to create this URI.Linking.makeUrl()
from expo-linking
.promptAsync({ redirectUri })
exp://localhost:19000
expo start
.AuthSession.makeRedirectUri({ useProxy: false })
to create this URI.port
+ host
.Linking.makeUrl()
from expo-linking
.promptAsync({ redirectUri })
yourscheme://path
/
).npx create-react-native-app
or expo prebuild
expo build:ios
or expo build:android
expo build:ios -t simulator
or expo build:android -t apk
AuthSession.makeRedirectUri({ native: '<YOUR_URI>' })
to select native when running in the correct environment.your.app://redirect
-> makeRedirectUri({ scheme: 'your.app', path: 'redirect' })
your.app:///
-> makeRedirectUri({ scheme: 'your.app', isTripleSlashed: true })
your.app:/authorize
-> makeRedirectUri({ native: 'your.app:/authorize' })
your.app://auth?foo=bar
-> makeRedirectUri({ scheme: 'your.app', path: 'auth', queryParams: { foo: 'bar' } })
exp://exp.host/@yourname/your-app
-> makeRedirectUri({ useProxy: true })
scheme
property at least. The entire URL can be overridden in custom apps by passing the native
property. Often this will be used for providers like Google or Okta which require you to use a custom native URI redirect. You can add, list, and open URI schemes using npx uri-scheme
.expo.scheme
after ejecting then you'll need to use the expo apply
command to apply the changes to your native project, then rebuild them (yarn ios
, yarn android
).promptAsync({ redirectUri })
import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; function App() { React.useEffect(() => { WebBrowser.warmUpAsync(); return () => { WebBrowser.coolDownAsync(); }; }, []); // Do authentication ... }
expo-auth-session
requests an exchange code as this is the most widely applicable login method.import * as React from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest, ResponseType } from 'expo-auth-session'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://accounts.spotify.com/authorize', }; function App() { const [request, response, promptAsync] = useAuthRequest( { responseType: ResponseType.Token, clientId: 'CLIENT_ID', scopes: ['user-read-email', 'playlist-modify-public'], redirectUri: makeRedirectUri({ scheme: 'your.app' }), }, discovery ); React.useEffect(() => { if (response && response.type === 'success') { const token = response.params.access_token; } }, [response]); return <Button disabled={!request} onPress={() => promptAsync()} title="Login" />; }
expo-secure-store
(This is different to AsyncStorage
which is not secure). This package provides native access to keychain services on iOS and encrypted SharedPreferences
on Android. There is no web equivalent to this functionality.import * as SecureStore from 'expo-secure-store'; const MY_SECURE_AUTH_STATE_KEY = 'MySecureAuthStateKey'; function App() { const [, response] = useAuthRequest({}); React.useEffect(() => { if (response && response.type === 'success') { const auth = response.params; const storageValue = JSON.stringify(auth); if (Platform.OS !== 'web') { // Securely store the auth on your device SecureStore.setItemAsync(MY_SECURE_AUTH_STATE_KEY, storageValue); } } }, [response]); // More login code... }