Authentication

Expo can be used to login to many popular providers on iOS, Android, and web! Most of these guides utilize the pure JS AuthSession API, refer to those docs for more information on the API.
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.

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.

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({
  native: 'your.app://redirect',
  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'],
      // For usage in managed apps using the proxy
      redirectUri: makeRedirectUri({
        // For usage in bare and standalone
        native: 'your.app://redirect',
      }),
    },
    discovery
  );

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

Create Coinbase App
WebsiteProviderPKCEAuto Discovery
Get Your ConfigOAuth 2.0SupportedNot Available
  • You cannot use the Expo proxy because they don't allow @ in their redirect URIs.
  • The redirectUri requires 2 slashes (://).
  • Scopes must be joined with ':' so just create one long string.
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.coinbase.com/oauth/authorize',
  tokenEndpoint: 'https://api.coinbase.com/oauth/token',
  revocationEndpoint: 'https://api.coinbase.com/oauth/revoke',
};

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      scopes: ['wallet:accounts:read'],
      // For usage in managed apps using the proxy
      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();
        }}
    />
  );
}
  • 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({
        // For usage in bare and standalone
        native: 'your.app://redirect',
        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,
      // For usage in managed apps using the proxy
      redirectUri: makeRedirectUri({
        // For usage in bare and standalone
        native: 'your.app://redirect',
      }),
    },
    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
  • Native auth isn't available in the App/Play Store client because you need a custom URI scheme built into the bundle. The custom scheme provided by Facebook is fb followed by the project ID (ex: fb145668956753819):
    • Standalone:
      • Add facebookScheme: 'fb<YOUR FBID>' to your app.config.js or app.json
      • You'll need to make a new production build to bundle these values expo build:ios & expo build:android.
    • Bare:
      • Run npx uri-scheme add fb<YOUR FBID>
      • Rebuild with yarn ios & yarn android
  • The native redirect URI must be formatted like fbYOUR_NUMERIC_ID://authorize
    • 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.
  • Learn more about manually building a login flow.
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 { ResponseType } from 'expo-auth-session';
import { Button } from 'react-native';

WebBrowser.maybeCompleteAuthSession();

export default function App() {
  const [request, response, promptAsync] = 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] = 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 Client: exp://localhost:19000/--/*
    • Expo Client + 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'],
      // For usage in managed apps using the proxy
      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();
        }}
    />
  );
}
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,
        // For usage in bare and standalone
        native: 'your.app://redirect',
      }),
    },
    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 Client: exp://localhost:19000/--/*
    • Expo Client + 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'],
      // For usage in managed apps using the proxy
      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();
        }}
    />
  );
}

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 client on iOS and Android.
  • iosClientId: iOS native client ID for use in standalone, bare-workflow, and custom clients.
  • androidClientId: Android native client ID for use in standalone, bare-workflow, and custom clients.
  • 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 the Expo client, 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, custom clients, and bare workflow apps. This method cannot be used in the Expo client.
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. Create a custom client: expo client:ios
    3. Build a production IPA: expo build:ios
  • Whenever you change the values in app.json you'll need to rebuild the native app.

This can only be used in Standalone, and bare workflow apps. This method cannot be used in the Expo client.
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 ios
    2. Build a production IPA: 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 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 Client: exp://localhost:19000/--/*
    • Expo Client + 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'],
      // For usage in managed apps using the proxy
      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'],
      // For usage in managed apps using the proxy
      redirectUri: makeRedirectUri({
        // For usage in bare and standalone
        native: 'your.app://redirect',
      }),
    },
    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'],
      // For usage in managed apps using the proxy
      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();
        }}
    />
  );
}
  • Slack does not support implicit grant.

Create Spotify App
WebsiteProviderPKCEAuto Discovery
Get Your ConfigOAuth 2.0SupportedNot 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://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,
      // For usage in managed apps using the proxy
      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();
        }}
    />
  );
}
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,
      // For usage in managed apps using the proxy
      redirectUri: makeRedirectUri({
        // For usage in bare and standalone
        native: 'your.app://redirect',
      }),
    },
    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'],
      // For usage in managed apps using the proxy
      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',
      // For usage in managed apps using the proxy
      redirectUri: makeRedirectUri({
        // For usage in bare and standalone
        native: 'your.app://redirect',
      }),
      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',
      // For usage in managed apps using the proxy
      redirectUri: makeRedirectUri({
        // For usage in bare and standalone
        native: 'your.app://redirect',
      }),
      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'],
      // For usage in managed apps using the proxy
      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();
        }}
    />
  );
}
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'],
      // For usage in managed apps using the proxy
      redirectUri: makeRedirectUri({
        // For usage in bare and standalone
        native: 'your.app://redirect',
      }),
    },
    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 the Expo client, 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.
  • Usage: promptAsync({ useProxy: true, redirectUri })

exp://exp.host/@yourname/your-app
  • Environment: Production projects that you expo publish'd and opened in the Expo client.
  • 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 client 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 client 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 - React Native + Unimodules.
      • npx create-react-native-app or expo eject
    • Standalone builds in the App or Play Store
      • expo build:ios or expo build:android
    • Custom Expo client builds
      • expo client:ios
  • Create: Use AuthSession.makeRedirectUri({ native: '<YOUR_URI>' }) to select native when running in the correct environment.
    • This link must be hard coded because it cannot be inferred from the config reliably, with exception for Standalone builds using scheme from app.config.js or app.json. 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({
        native: 'your.app://redirect',
      }),
    },
    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...
}