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
You can use the expo-facebook to authenticate via the Facebook app, however this functionality is limited.
  • 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
  • You can still test native auth in the client by using the Expo proxy useProxy
  • 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.
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.facebook.com/v6.0/dialog/oauth',
  tokenEndpoint: 'https://graph.facebook.com/v6.0/oauth/access_token',
};

const useProxy = Platform.select({ web: false, default: true });

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: '<YOUR FBID>',
      scopes: ['public_profile','email', 'user_likes'],
      // For usage in managed apps using the proxy
      redirectUri: makeRedirectUri({
        useProxy,
        // For usage in bare and standalone
        // Use your FBID here. The path MUST be `authorize`.
        native: 'fb111111111111://authorize',
      }),
      extraParams: {
        // Use `popup` on web for a better experience
        display: Platform.select({ web: 'popup' }),
        // Optionally you can use this to rerequest declined permissions
        auth_type: 'rerequest',
      },
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      const { code } = response.params;
      }
  }, [response]);

  return (
    <Button
      disabled={!request}
      title="Login"
      onPress={() => {
        promptAsync({
        useProxy,
          windowFeatures: { width: 700, height: 600 }
        });
      }}
    />
  );
}
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();

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://www.facebook.com/v6.0/dialog/oauth',
  tokenEndpoint: 'https://graph.facebook.com/v6.0/oauth/access_token',
};

const useProxy = Platform.select({ web: false, default: true });

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      responseType: ResponseType.Token,
      clientId: '<YOUR FBID>',
      scopes: ['public_profile','email', 'user_likes'],
      // For usage in managed apps using the proxy
      redirectUri: makeRedirectUri({
        useProxy,
        // For usage in bare and standalone
        // Use your FBID here. The path MUST be `authorize`.
        native: 'fb111111111111://authorize',
      }),
      extraParams: {
        // Use `popup` on web for a better experience
        display: Platform.select({ web: 'popup' }),
        // Optionally you can use this to rerequest declined permissions
        auth_type: 'rerequest',
      },
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      const { access_token } = response.params;
      }
  }, [response]);

  return (
    <Button
      disabled={!request}
      title="Login"
      onPress={() => {
        promptAsync({
        useProxy,
          windowFeatures: { width: 700, height: 600 }
        });
      }}
    />
  );
}
  • It's important that you request at least the ['public_profile', 'email'] scopes, otherwise Firebase won't display the user info correctly in the auth panel.
  • 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 { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session';
import firebase from 'firebase';
import { Button, Platform } from 'react-native';

// Initialize Firebase
if (!firebase.apps.length) {
  firebase.initializeApp({
    /* Config */
  });
}

WebBrowser.maybeCompleteAuthSession();

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://www.facebook.com/v6.0/dialog/oauth',
  tokenEndpoint: 'https://graph.facebook.com/v6.0/oauth/access_token',
};

const useProxy = Platform.select({ web: false, default: true });

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      responseType: ResponseType.Token,
      clientId: '<YOUR FBID>',
      scopes: ['public_profile', 'email', 'user_likes'],
      // For usage in managed apps using the proxy
      redirectUri: makeRedirectUri({
        useProxy,
        // For usage in bare and standalone
        // Use your FBID here. The path MUST be `authorize`.
        native: 'fb111111111111://authorize',
      }),
      extraParams: {
        // Use `popup` on web for a better experience
        display: Platform.select({ web: 'popup' }),
        // Optionally you can use this to rerequest declined permissions
        auth_type: 'rerequest',
      },
    },
    discovery
  );

  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({
        useProxy,
          windowFeatures: { width: 700, height: 600 }
        });
      }}
    />
  );
}

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
  • Google will provide you with a custom redirectUri which you cannot use in the Expo client.
    • URI schemes must be built into the app, you can do this with bare workflow, standalone, and custom clients.
    • You can still develop and test Google auth in the Expo client with the proxy service, just be sure to configure the project as a website in the Google developer console.
  • For a slightly more native experience in bare Android apps, you can use the expo-google-sign-in package.
  • You can change the UI language by setting extraParams.hl to an ISO language code (ex: fr, en-US). Defaults to the best estimation based on the users browser.
  • You can set which email address to use ahead of time by setting extraParams.login_hint.
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://accounts.google.com');
  // Request
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      redirectUri: makeRedirectUri({
        // For usage in bare and standalone
        native: 'com.googleusercontent.apps.GOOGLE_GUID://redirect',
        useProxy,
      }),
      scopes: ['openid', 'profile'],

      // Optionally should the user be prompted to select or switch accounts
      prompt: Prompt.SelectAccount,

      // Optional
      extraParams: {
        /// Change language
        // hl: 'fr',
        /// Select the user
        // login_hint: 'user@gmail.com',
      },
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      const { code } = response.params;
      }
  }, [response]);

  return (
    <Button
      disabled={!request}
      title="Login"
      onPress={() => {
        promptAsync({ useProxy });
        }}
    />
  );
}
  • PKCE must be disabled in implicit mode (usePKCE: false).
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, ResponseType, 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://accounts.google.com');
  // Request
  const [request, response, promptAsync] = useAuthRequest(
    {
      responseType: ResponseType.Token,
      // PKCE must be disabled in implicit mode
      usePKCE: false,
      clientId: 'CLIENT_ID',
      redirectUri: makeRedirectUri({
        // For usage in bare and standalone
        native: 'com.googleusercontent.apps.GOOGLE_GUID://redirect',
        useProxy,
      }),
      scopes: ['openid', 'profile'],

      // Optionally should the user be prompted to select or switch accounts
      prompt: Prompt.SelectAccount,

      // Optional
      extraParams: {
        /// Change language
        // hl: 'fr',
        /// Select the user
        // login_hint: 'user@gmail.com',
      },
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      const { access_token } = response.params;
      }
  }, [response]);

  return (
    <Button
      disabled={!request}
      title="Login"
      onPress={() => {
        promptAsync({ useProxy });
        }}
    />
  );
}
Google Firebase Console for URIs
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, ResponseType, useAuthRequest, useAutoDiscovery, generateHexStringAsync } from 'expo-auth-session';
import firebase from 'firebase';
import { Button, Platform } from 'react-native';

// Initialize Firebase
if (!firebase.apps.length) {
  firebase.initializeApp({
    /* Config */
  });
}

WebBrowser.maybeCompleteAuthSession();

const useProxy = Platform.select({ web: false, default: true });

// Generate a random hex string for the nonce parameter
function useNonce() {
  const [nonce, setNonce] = React.useState(null);
  React.useEffect(() => {
    generateHexStringAsync(16).then(value => setNonce(value));
  }, []);
  return nonce;
}

export default function App() {
  const nonce = useNonce();
  // Endpoint
  const discovery = useAutoDiscovery('https://accounts.google.com');
  // Request
  const [request, response, promptAsync] = useAuthRequest(
    {
      responseType: ResponseType.IdToken,
      clientId: 'Your-Web-Client-ID.apps.googleusercontent.com',
      redirectUri: makeRedirectUri({
        // For usage in bare and standalone
        native: 'com.googleusercontent.apps.GOOGLE_GUID://redirect',
        useProxy,
      }),
      scopes: [
        'openid',
        'profile',
        'email',
      ],
      extraParams: {
        nonce,
      }
    },
    discovery
  );

  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 || !nonce)}
      title="Login"
      onPress={() => {
        promptAsync({ useProxy });
        }}
    />
  );
}
  • 💡 This auth is different because it requires the following to retrieve the id_token parameter:
    • openid in the scopes
    • responseType set to ResponseType.IdToken ('id_token')
    • extraParams.nonce must be defined.

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 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...
}