Expo

Get Started
Guides

Authentication

Here are some important rules that apply to all authentication providers:
  • Use WebBrowser.maybeCompleteAuthSession() to dismiss the web popup. If you forget to add this then the popup window will not close.
  • Create redirects with AuthSession.makeRedirectUri() this does a lot of the heavy lifting involved with universal platform support. Behind the scenes it uses expo-linking.
  • Build requests using AuthSession.useAuthRequest(), the hook allows for async setup which means mobile browsers won't block the authentication.
  • Be sure to disable the prompt until request is defined.
  • You can only invoke promptAsync in a user-interaction on web.

Guides

AuthSession can be used for any OAuth or OpenID Connect provider, we've assembled guides for using the most requested services! If you'd like to see more, you can open a PR or vote on canny.
IdentityServer 4

IdentityServer 4

OAuth 2 | OpenID

Azure

Azure

OAuth 2 | OpenID

Apple

Apple

iOS Only

Coinbase

Coinbase

OAuth 2

Dropbox

Dropbox

OAuth 2

Facebook

Facebook

OAuth 2

Fitbit

Fitbit

OAuth 2

Firebase Phone

Firebase Phone

Recaptcha

Github

Github

OAuth 2

Google

Google

OAuth 2 | OpenID

Imgur

Imgur

OAuth 2

Okta

Okta

OAuth 2 | OpenID

Reddit

Reddit

OAuth 2

Slack

Slack

OAuth 2

Spotify

Spotify

OAuth 2

Strava

Strava

OAuth 2

Twitch

Twitch

OAuth 2

Uber

Uber

OAuth 2


WebsiteProviderPKCEAuto Discovery
More InfoOpenIDRequiredAvailable
  • If 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>
  );
}

Create Azure App
WebsiteProviderPKCEAuto Discovery
Get Your ConfigOpenIDSupportedAvailable
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();
        }}
    />
  );
}

Create Coinbase App
WebsiteProviderPKCEAuto Discovery
Get Your ConfigOAuth 2.0SupportedNot Available
  • The redirectUri requires 2 slashes (://).
  • Scopes must be joined with ':' so just create one long string.
  • Setup redirect URIs: Your Project > Permitted Redirect URIs: (be sure to save after making changes).
    • Expo Go: exp://localhost:19000/--/
    • Web dev: https://localhost:19006
      • Run expo start:web --https to run with https, auth won't work otherwise.
      • Adding a slash to the end of the URL doesn't matter.
    • Standalone and Bare: your-scheme://
      • Scheme should be specified in app.json expo.scheme: 'your-scheme', then added to the app code with makeRedirectUri({ native: 'your-scheme://' }))
    • Proxy: Not Supported
      • You cannot use the Expo proxy (useProxy) because they don't allow @ in their redirect URIs.
    • Web production: https://yourwebsite.com
      • Set this to whatever your deployed website URL is.
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;
}
  • Coinbase does not support implicit grant.

Create Dropbox App
WebsiteProviderPKCEAuto Discovery
Get Your ConfigOAuth 2.0Not SupportedNot Available
  • Scopes must be an empty array.
  • PKCE must be disabled (usePKCE: false) otherwise you'll get an error about code_challenge being included in the query string.
  • Implicit auth is supported.
  • When 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.
Auth code responses (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();
        }}
    />
  );
}

Create Facebook App
WebsiteProviderPKCEAuto Discovery
More InfoOAuthSupportedNot Available

You must use the proxy service in the Expo Go app because exp:// cannot be added to your Facebook app as a redirect.
  • Go to: (Sidebar) Products > Facebook Login > Settings > Client OAuth Settings. The URL should be: https://developers.facebook.com/apps/<YOUR ID>/fb-login/settings/
Facebook Console for URIs
  • Under "Valid OAuth Redirect URIs" add https://auth.expo.io/@username/slug and any other web URLs you may want to add.
  • Press "Save Changes" in the footer.
  • Copy the "App ID" in the header into your expoClientId: '<YOUR FBID>'. Ex: { expoClientId: '474614477183384' } (no fb prefix).
  • Now you're ready to use the demo component in the Expo Go app on iOS and Android.
⚠️
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.

Consider using the expo-facebook module for native auth as it supports some nonstandard OAuth features implemented by Facebook.
  • The custom scheme provided by Facebook is fb followed by the project ID (ex: fb145668956753819):
  • Add facebookScheme: 'fb<YOUR FBID>' to your app.config.js or app.json. Example: { facebookScheme: "fb145668956753819" } (notice the fb prefix).
  • You'll need to make a new native build to add this redirect URI into your app's AndroidManifest.xml and Info.plist:
    • iOS: eas build or expo build:ios.
    • Android: eas build or expo build:android.
  • Bare:
    • Regenerate your native projects with expo eject, or add the redirects manually with npx uri-scheme add fb<YOUR FBID>
    • Rebuild the projects with yarn ios & yarn android

  • Go to: (Sidebar) Settings > Basic. The URL should be: https://developers.facebook.com/apps/<YOUR ID>/settings/basic/
  • Scroll all the way down and click + Add Platform, then select iOS.
Facebook Console for URIs
  • Under iOS > Bundle ID: Add your app's bundle identifier, this should match the value in your app.json - expo.ios.bundleIdentifier. If you don't have one set, run expo eject to create one (then rebuild the native app).
  • Press "Save Changes" in the footer.
  • Copy the "App ID" in the header into your iosClientId: '<YOUR FBID>' or clientId. Ex: { iosClientId: '474614477183384' } (no fb prefix).
  • Now you're ready to use the demo component in your native iOS app.

  • Go to: (Sidebar) Settings > Basic. The URL should be: https://developers.facebook.com/apps/<YOUR ID>/settings/basic/
  • Scroll all the way down and click + Add Platform, then select Android.
Facebook Console for URIs
  • Under Android > Google Play Package Name: Add your app's android package, this should match the value in your app.json - expo.android.package. If you don't have one set, run expo eject to create one (then rebuild the native app).
  • Under Android > Class Name: This should match the package name + .MainActivity, i.e. com.bacon.yolo15.MainActivity.
  • Under Android > Key Hashes: You'll need to create two different values, one for Debug and one for Release. Learn how to create the Key Hash here.
    • In your app root, run: keytool -exportcert -alias androiddebugkey -keystore android/app/debug.keystore | openssl sha1 -binary | openssl base64 you don't need a password, but it is recommended.
    • Copy the value formatted like XX2BBI1XXXXXXXX3XXXX44XX5XX= into the console.
  • Press "Save Changes" in the footer.
    • If you get a popup for the package name select "Use this package name".
  • Copy the "App ID" in the header into your androidClientId: '<YOUR FBID>' or clientId. Ex: { androidClientId: '474614477183384' } (no fb prefix).
  • Now you're ready to use the demo component in your native Android app.
If the App crashes upon authentication, then run adb logcat and look for any runtime Errors.
If the Android app crashes with 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>
Then add <data android:scheme="fb<YOUR ID>"/> to the .MainActivity intent-filter or use npx uri-scheme add fb<YOUR ID> --android.

⚠️
The native redirect URI must be formatted like fb<YOUR FBID>://authorize, ex: fb474614477183384://authorize. Using the Facebook provider will do this automatically.
  • If the protocol/suffix is not your FBID then you will get an error like: No redirect URI in the params: No redirect present in URI.
  • If the path is not ://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.

  • Go to: (Sidebar) Products > Facebook Login > Settings > Client OAuth Settings. The URL should be: https://developers.facebook.com/apps/<YOUR ID>/fb-login/settings/
Facebook Console for URIs
  • Under "Valid OAuth Redirect URIs" add https://localhost:19006 and any other web URLs you may want to add.
  • Press "Save Changes" in the footer.
  • Copy the "App ID" in the header into your webClientId: '<YOUR FBID>'. Ex: { webClientId: '474614477183384' } (no fb prefix).
  • Now start the dev server with 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();
      }}
    />
  );
}
  • Be sure to setup Facebook auth as described above, this is basically identical.
  • 🔥 Create a new Firebase project
  • Enable Facebook auth, save the project.
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 firebase from 'firebase';
import { Button } from 'react-native';

// Initialize Firebase
if (!firebase.apps.length) {
  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 credential = firebase.auth.FacebookAuthProvider.credential(access_token);
      // Sign in with the credential from the Facebook user.
      firebase.auth().signInWithCredential(credential);
    }
  }, [response]);

  return (
    <Button
      disabled={!request}
      title="Login"
      onPress={() => {
        promptAsync();
      }}
    />
  );
}

Create FitBit App
WebsiteProviderPKCEAuto Discovery
Get Your ConfigOAuth 2.0SupportedNot Available
  • Provider only allows one redirect URI per app. You'll need an individual app for every method you want to use:
    • Expo Go: exp://localhost:19000/--/*
    • Expo Go + Proxy: https://auth.expo.io/@you/your-app
    • Standalone or Bare: com.your.app://*
    • Web: https://yourwebsite.com/*
  • The 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 });
        }}
    />
  );
}

Create Github App
WebsiteProviderPKCEAuto Discovery
Get Your ConfigOAuth 2.0SupportedNot Available
  • Provider only allows one redirect URI per app. You'll need an individual app for every method you want to use:
    • Expo Go: exp://localhost:19000/--/*
    • Expo Go + Proxy: https://auth.expo.io/@you/your-app
    • Standalone or Bare: com.your.app://*
    • Web: https://yourwebsite.com/*
  • The 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();
        }}
    />
  );
}

Create Google App
WebsiteProviderPKCEAuto Discovery
Get Your ConfigOpenIDSupportedAvailable
Be sure to install the peerDependency yarn add expo-application
There are 4 different types of client IDs you can provide:
  • 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.
To create a client ID, go to the Credentials Page:
  • Create an app for your project if you haven't already.
  • Once that's done, click "Create Credentials" and then "OAuth client ID." You will be prompted to set the product name on the consent screen, go ahead and do that.

While developing in Expo Go, you cannot use proper native authentication. Instead you must use web login during development. Be sure to follow the standalone guide below for setting up production apps.
First, be sure to login to your Expo account expo login. This will be part of the redirect URL.
Create a new Google Client ID that will be used with expoClientId.

This can only be used in standalone and bare workflow apps. This method cannot be used in the Expo Go app.
Create a new Google Client ID that will be used with iosClientId.
  • Application Type: iOS Application
  • Give it a name (e.g. "iOS App").
  • Bundle ID: Must match the value of ios.bundleIdentifier in your app.json.
  • Your app needs to conform to the URI scheme matching your bundle identifier.
    • Standalone: Automatically added, do nothing.
    • Bare workflow: Run npx uri-scheme add <your bundle id> --ios
  • To test this you can:
    1. Eject to bare: expo eject and run yarn ios
    2. Build a simulator app: expo build:ios -t simulator
    3. Build a production IPA: expo build:ios
  • Whenever you change the values in app.json you'll need to rebuild the native app.
Troubleshooting
  • If you get 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.

This can only be used in Standalone, and bare workflow apps. This method cannot be used in the Expo Go app.
Create a new Google Client ID that will be used with androidClientId.
  • Application Type: Android Application
  • Give it a name (e.g. "Android App").
  • Package name: Must match the value of android.package in your app.json.
  • Your app needs to conform to the URI scheme matching your android.package (ex. com.myname.mycoolapp:/).
    • Standalone: Automatically added, do nothing.
    • Bare workflow: Run npx uri-scheme add <your android.package> --android
  • Signing-certificate fingerprint:
    • Run expo credentials:manager -p android then select "Update upload Keystore" -> "Generate new keystore" -> "Go back to experience overview"
    • Copy your "Google Certificate Fingerprint", it will output a string that looks like A1:B2:C3 but longer.
  • To test this you can:
    1. Eject to bare: expo eject and run yarn android
    2. Build a production APK: expo build:android
  • Whenever you change the values in app.json you'll need to rebuild the native app.

Expo web client ID for use in the browser.
Create a new Google Client ID that will be used with webClientId.
  • Application Type: Web Application
  • Give it a name (e.g. "Web App").
  • URIs (Authorized JavaScript origins): https://localhost:19006 & https://yourwebsite.com
  • Authorized redirect URIs: https://localhost:19006 & https://yourwebsite.com
  • To test this be sure to start your app with 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();
        }}
    />
  );
}
Google Firebase Console for URIs
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 firebase from 'firebase';
import { Button } from 'react-native';

// Initialize Firebase
if (!firebase.apps.length) {
  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 credential = firebase.auth.GoogleAuthProvider.credential(id_token);
      firebase.auth().signInWithCredential(credential);
    }
  }, [response]);

  return (
    <Button
      disabled={!request}
      title="Login"
      onPress={() => {
        promptAsync();
        }}
    />
  );
}

Create Imgur App
WebsiteProviderPKCEAuto Discovery
Get Your ConfigOAuth 2.0SupportedNot Available
  • You will need to create a different provider app for each platform (dynamically choosing your clientId).
  • Learn more here: imgur.com/oauth2
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();
        }}
    />
  );
}

Create Okta App
WebsiteProviderPKCEAuto Discovery
Sign-up > ApplicationsOpenIDSupportedAvailable
  • You cannot define a custom redirectUri, Okta will provide you with one.
  • You can use the Expo proxy to test this without a native rebuild, just be sure to configure the project as a website.
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 });
        }}
    />
  );
}

Create Reddit App
WebsiteProviderPKCEAuto Discovery
Get Your ConfigOAuth 2.0SupportedNot Available
  • Provider only allows one redirect URI per app. You'll need an individual app for every method you want to use:
    • Expo Go: exp://localhost:19000/--/*
    • Expo Go + Proxy: https://auth.expo.io/@you/your-app
    • Standalone or Bare: com.your.app://*
    • Web: https://yourwebsite.com/*
  • The 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();
        }}
    />
  );
}
  • You must select the 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();
        }}
    />
  );
}

Create Slack App
WebsiteProviderPKCEAuto Discovery
Get Your ConfigOAuth 2.0SupportedNot Available
  • The 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.
  • Scopes must be joined with ':' so just create one long string.
  • Navigate to the "Scopes" section to enable scopes.
  • 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();
        }}
    />
  );
}
  • Slack does not support implicit grant.

Create Spotify App
WebsiteProviderPKCEAuto Discovery
Get Your ConfigOAuth 2.0SupportedNot Available
  • Setup your redirect URIs: Your project > Edit Settings > Redirect URIs (be sure to save after making changes).
    • Expo Go: exp://localhost:19000/--/
    • Web dev: https://localhost:19006
      • Important: Ensure there's no slash at the end of the URL unless manually changed in the app code with makeRedirectUri({ path: '/' }).
      • Run expo start:web --https to run with https, auth won't work otherwise.
    • Custom app: your-scheme://
      • Scheme should be specified in app.json expo.scheme: 'your-scheme', then added to the app code with makeRedirectUri({ native: 'your-scheme://' }))
    • Proxy: https://auth.expo.io/@username/slug
      • If 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.
    • Web production: https://yourwebsite.com
      • Set this to whatever your deployed website URL is.
  • For simpler authentication, use the Implicit Flow as it'll return an access_token without the need for a code exchange server request.
  • Learn more about the Spotify API.
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();
        }}
    />
  );
}

Create Strava App
WebsiteProviderPKCEAuto Discovery
Get Your ConfigOAuth 2.0SupportedNot Available
  • Learn more about the Strava API.
  • The "Authorization Callback Domain" refers to the final path component of your redirect URI. Ex: In the URI com.bacon.myapp://redirect the domain would be redirect.
  • No Implicit auth flow is provided by Strava.
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();
        }}
    />
  );
}
Strava doesn't provide an implicit auth flow, you should send the code to a server or serverless function to perform the access token exchange. For debugging purposes, you can perform the exchange client-side using the following method:
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' }
);

Create Twitch App
WebsiteProviderPKCEAuto DiscoveryScopes
Get your ConfigOAuthSupportedNot AvailableInfo
  • You will need to enable 2FA on your Twitch account to create an application.
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();
        }}
    />
  );
}

Create Uber App
WebsiteProviderPKCEAuto Discovery
Get Your ConfigOAuth 2.0SupportedNot Available
  • The 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();
        }}
    />
  );
}

Here are a few examples of some common redirect URI patterns you may end up using.

https://auth.expo.io/@yourname/your-app
  • Environment: Development or production projects in Expo Go, or in a standalone build.
  • Create: Use AuthSession.makeRedirectUri({ useProxy: true }) to create this URI.
    • The link is constructed from your Expo username and the Expo app name, which are appended to the proxy website.
    • For custom apps, you'll need to rebuild the native app if you change users or if you reassign your slug, or owner properties in the app.json. We highly recommend not using the proxy in custom apps (standalone, bare, custom).
  • Usage: promptAsync({ useProxy: true, redirectUri })

exp://exp.host/@yourname/your-app
  • Environment: Production projects that you expo publish'd and opened in the Expo Go app.
  • Create: Use AuthSession.makeRedirectUri({ useProxy: false }) to create this URI.
    • The link is constructed from your Expo username and the Expo app name, which are appended to the Expo Go app URI scheme.
    • You could also create this link with using Linking.makeUrl() from expo-linking.
  • Usage: promptAsync({ redirectUri })

exp://localhost:19000
  • Environment: Development projects in the Expo Go app when you run expo start.
  • Create: Use AuthSession.makeRedirectUri({ useProxy: false }) to create this URI.
    • This link is built from your Expo server's port + host.
    • You could also create this link with using Linking.makeUrl() from expo-linking.
  • Usage: promptAsync({ redirectUri })

yourscheme://path
In some cases there will be anywhere between 1 to 3 slashes (/).
  • Environment:
    • Bare workflow
      • npx create-react-native-app or expo eject
    • Standalone builds in the App or Play Store
      • expo build:ios or expo build:android
    • Standalone builds for local testing
      • expo build:ios -t simulator or expo build:android -t apk
  • Create: Use 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 })
    • This link can often be created automatically but we recommend you define the 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.
    • If you change the 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).
  • Usage: promptAsync({ redirectUri })

The "login flow" is an important thing to get right, in a lot of cases this is where the user will commit to using your app again. A bad experience can cause users to give up on your app before they've really gotten to use it.
Here are a few tips you can use to make authentication quick, easy, and secure for your users!

On Android you can optionally warm up the web browser before it's used. This allows the browser app to pre-initialize itself in the background. Doing this can significantly speed up prompting the user for authentication.
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';

function App() {
  React.useEffect(() => {
    WebBrowser.warmUpAsync();
    
    return () => {
      WebBrowser.coolDownAsync();
      };
  }, []);

  // Do authentication ...
}

You should never store your client secret locally in your bundle because there's no secure way to do this. Luckily a lot of providers have an "Implicit flow" which enables you to request an access token without the client secret. By default expo-auth-session requests an exchange code as this is the most widely applicable login method.
Here is an example of logging into Spotify without using a client secret.
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { 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" />;
}

On native platforms like iOS, and Android you can secure things like access tokens locally using a package called 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.
You can store your authentication results and rehydrate them later to avoid having to prompt the user to login again.
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...
}