---
title: Authentication with OAuth or OpenID providers
---

import ImageSpotlight from '~/components/plugins/ImageSpotlight';
import { ASSETS, Grid, GridItem, Box } from '~/ui/components/Authentication';
import { Tab, Tabs } from '~/ui/components/Tabs';

Expo can be used to login to many popular providers on Android, iOS, and web. Most of these guides utilize the pure JS [`AuthSession` API](/versions/latest/sdk/auth-session), 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.

## 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](https://github.com/expo/expo/edit/main/docs/pages/guides/authentication.mdx) or [vote on canny](https://expo.canny.io/feature-requests).

<Grid>
  <GridItem title="IdentityServer 4" protocol={['OAuth 2', 'OpenID']} image={ASSETS.id4} />
  <GridItem title="Asgardeo" protocol={['OAuth 2', 'OpenID']} image={ASSETS.asgardeo} />
  <GridItem title="Azure" protocol={['OAuth 2', 'OpenID']} image={ASSETS.azure} />
  <GridItem
    title="Apple"
    protocol={['iOS Only']}
    href="/versions/latest/sdk/apple-authentication"
    image={ASSETS.apple}
  />
  <GridItem
    title="Beyond Identity"
    protocol={['OAuth 2', 'OpenID']}
    image={ASSETS.beyondidentity}
  />
  <GridItem title="Cognito" protocol={['OAuth 2', 'OpenID']} image={ASSETS.cognito} />
  <GridItem title="Coinbase" protocol={['OAuth 2']} image={ASSETS.coinbase} />
  <GridItem title="Dropbox" protocol={['OAuth 2']} image={ASSETS.dropbox} />
  <GridItem
    title="Facebook"
    protocol={['OAuth 2']}
    href="/guides/facebook-authentication/"
    image={ASSETS.facebook}
  />
  <GridItem title="Fitbit" protocol={['OAuth 2']} image={ASSETS.fitbit} />
  <GridItem
    title="Firebase Phone"
    protocol={['Recaptcha']}
    href="/versions/latest/sdk/firebase-recaptcha"
    image={ASSETS.firebase}
  />
  <GridItem title="GitHub" protocol={['OAuth 2']} image={ASSETS.github} />
  <GridItem
    title="Google"
    protocol={['OAuth 2', 'OpenID']}
    href="/guides/google-authentication/"
    image={ASSETS.google}
  />
  <GridItem title="Imgur" protocol={['OAuth 2']} image={ASSETS.imgur} />
  <GridItem title="Keycloak" protocol={['OAuth 2', 'OpenID']} image={ASSETS.keycloak} />
  <GridItem title="Okta" protocol={['OAuth 2', 'OpenID']} image={ASSETS.okta} />
  <GridItem title="Reddit" protocol={['OAuth 2']} image={ASSETS.reddit} />
  <GridItem title="Slack" protocol={['OAuth 2']} image={ASSETS.slack} />
  <GridItem title="Spotify" protocol={['OAuth 2']} image={ASSETS.spotify} />
  <GridItem title="Strava" protocol={['OAuth 2']} image={ASSETS.strava} />
  <GridItem title="Twitch" protocol={['OAuth 2']} image={ASSETS.twitch} />
  <GridItem title="Twitter" protocol={['OAuth 2']} image={ASSETS.twitter} />
  <GridItem title="Uber" protocol={['OAuth 2']} image={ASSETS.uber} />
</Grid>

<Box
  name="IdentityServer 4"
  image={ASSETS.id4}
>

| Website                  | Provider | PKCE     | Auto Discovery |
| ------------------------ | -------- | -------- | -------------- |
| [More Info][c-identity4] | OpenID   | Required | Available      |

[c-identity4]: https://demo.identityserver.io/

- If `offline_access` isn't included then no refresh token will be returned.

{/* prettier-ignore */}
```tsx IdentityServer 4 Example
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';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */
/* @info Using the Expo proxy will redirect the user through auth.expo.io enabling you to use web links when configuring your project with an OAuth provider. This is not available on web. */
const useProxy = true;
/* @end */
const redirectUri = AuthSession.makeRedirectUri({
  useProxy,
});

export default function App() {
  /* @info If the provider supports auto discovery then you can pass an issuer to the `useAutoDiscovery` hook to fetch the discovery document. */
  const discovery = AuthSession.useAutoDiscovery('https://demo.identityserver.io');
  /* @end */
  // Create and load an auth request
  const [request, result, promptAsync] = AuthSession.useAuthRequest(
    {
      clientId: 'native.code',
      /* @info After a user finishes authenticating, the server will redirect them to this URI. Learn more about <a href="../../guides/linking/">linking here</a>. */
      redirectUri,
      /* @end */
      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>
  );
}
```

</Box>
{/* End IdentityServer 4 */}

<Box
  name="Asgardeo"
  createUrl="https://wso2.com/asgardeo/docs/guides/#developer-guide"
  image={ASSETS.asgardeo}
>

| Website                                         | Provider | PKCE      | Auto Discovery |
| ----------------------------------------------- | -------- | --------- | -------------- |
| [Get Your Config](https://console.asgardeo.io/) | OpenID   | Supported | Available      |

- Make sure to check `Public Client` option in the console.
- Choose the intended grant in the Allowed grant types section.

<Tabs tabs={["Auth Code", "Implicit Flow"]}>

<Tab>

{/* prettier-ignore */}
```tsx
import { useState, useEffect } from 'react';
import { StyleSheet, Text, View, Button, Alert } from 'react-native';
import * as AuthSession from "expo-auth-session";
import * as WebBrowser from "expo-web-browser";
import jwtDecode from "jwt-decode";

WebBrowser.maybeCompleteAuthSession();

const useProxy = true;
const redirectUri = AuthSession.makeRedirectUri({ useProxy });

/* @info <strong>Client ID:</strong> This can be found on the protocol tab of Asgardeo Console */
const CLIENT_ID = "YOUR_CLIENT_ID";
/* @end */

export default function App() {

    /* @info <strong>Auto Discovery URL:</strong> This can be found on the info tab of Asgardeo Console. Copy the link under `Issuer` */
    const discovery = AuthSession.useAutoDiscovery('https://api.asgardeo.io/t/<YOUR_ORG_NAME>/oauth2/token');
    /* @end */
    const [tokenResponse, setTokenResponse] = useState({});
    const [decodedIdToken, setDecodedIdToken] = useState({});

    const [request, result, promptAsync] = AuthSession.useAuthRequest(
        {
            redirectUri,
            clientId: CLIENT_ID,
            responseType: "code",
            scopes: ["openid", "profile", "email"]
        },
        discovery
    );

    const getAccessToken = () => {
      if (result?.params?.code) {
        /* @info <strong>Token Endpoint:</strong> This can be found on the info tab of Asgardeo Console. Copy the link under `Token` */
        fetch(
        /* @end */
        "https://api.asgardeo.io/t/iamapptesting/oauth2/token",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/x-www-form-urlencoded"
            },
            body: `grant_type=authorization_code&code=${result?.params?.code}&redirect_uri=${redirectUri}&client_id=${CLIENT_ID}&code_verifier=${request?.codeVerifier}`
          }).then((response) => {
              return response.json();
            }).then((data) => {
              setTokenResponse(data);
              setDecodedIdToken(jwtDecode(data.id_token));
            }).catch((err) => {
              console.log(err);
            });
        }
    }

    useEffect(() => {
      (async function setResult() {
        if (result) {
          if (result.error) {
            Alert.alert(
              "Authentication error",
              result.params.error_description || "something went wrong"
            );
            return;
          }
          if (result.type === "success") {
            getAccessToken();
          }
        }
      })();
    }, [result]);


    return (
      <View style={styles.container}>
        <Button title="Login" disabled={!request} onPress={() => promptAsync({ useProxy })} />
        {decodedIdToken && <Text>Welcome {decodedIdToken.given_name || ""}!</Text>}
        {decodedIdToken && <Text>{decodedIdToken.email}</Text>}
        <View style={styles.accessTokenBlock}>
          decodedToken && <Text>Access Token: {tokenResponse.access_token}</Text>
        </View>
      </View>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
        alignItems: 'center',
        justifyContent: 'center',
    },
    accessTokenBlock: {
        width: 300,
        height: 500,
        overflow: "scroll"
    }
});
```

</Tab>

<Tab>

{/* prettier-ignore */}
```tsx
import { useState, useEffect } from 'react';
import { StyleSheet, Text, View, Button, Alert } from 'react-native';
import * as AuthSession from "expo-auth-session";
import * as WebBrowser from "expo-web-browser";
import jwtDecode from "jwt-decode";

WebBrowser.maybeCompleteAuthSession();

const useProxy = true;
const redirectUri = AuthSession.makeRedirectUri({ useProxy });

/* @info <strong>Client ID:</strong> This can be found on the protocol tab of Asgardeo Console */
const CLIENT_ID = "YOUR_CLIENT_ID";
/* @end */

export default function App() {

  /* @info <strong>Auto Discovery URL:</strong> This can be found on the info tab of Asgardeo Console. Copy the link under `Issuer` */
  const discovery = AuthSession.useAutoDiscovery('https://api.asgardeo.io/t/<YOUR_ORG_NAME>/oauth2/token');
  /* @end */
  const [decodedToken, setDecodedToken] = useState({});

  const [request, result, promptAsync] = AuthSession.useAuthRequest(
        {
            redirectUri,
            clientId: CLIENT_ID,
            responseType: "token",
            scopes: ["openid", "profile", "email"]
        },
        discovery
    );

  useEffect(() => {
    (async function setResult() {
      if (result) {
        if (result.error) {
          Alert.alert(
            "Authentication error",
            result.params.error_description || "something went wrong"
          );
          return;
        }

        if (result.type === "success") {
          const jwtToken = result.params.access_token;
          const decoded = jwtDecode(jwtToken);
          setDecodedToken(decoded);
        }
      }
    })();
  }, [result]);

  return (
    <View style={styles.container}>
      <Button title="Login" disabled={!request} onPress={() => promptAsync({ useProxy })} />
      {decodedToken && <Text>Welcome {decodedToken.given_name || ""}!</Text>}
      {decodedToken && <Text>{decodedToken.email}</Text>}
      <View style={styles.accessTokenBlock}>
        decodedToken && <Text>Access Token: {result?.params.access_token}</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
        alignItems: 'center',
        justifyContent: 'center',
    },
    accessTokenBlock: {
        width: 300,
        height: 500,
        overflow: "scroll"
    }
});
```

</Tab>

</Tabs>
</Box>
{/* End Asgardeo */}

<Box
  name="Azure"
  createUrl="https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-overview"
  image={ASSETS.azure}
>

| Website                     | Provider | PKCE      | Auto Discovery |
| --------------------------- | -------- | --------- | -------------- |
| [Get Your Config][c-azure2] | OpenID   | Supported | Available      |

[c-azure2]: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-overview

{/* prettier-ignore */}
```tsx Azure Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import {
  exchangeCodeAsync,
  makeRedirectUri,
  useAuthRequest,
  useAutoDiscovery,
} from 'expo-auth-session';
import { Button, Text, SafeAreaView } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

export default function App() {
  // Endpoint
  const discovery = useAutoDiscovery(
    'https://login.microsoftonline.com/<TENANT_ID>/v2.0',
  );
  const redirectUri = makeRedirectUri({
    /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
    scheme: undefined,
    /* @end */
    /* @info Azure requires there to be a path in your redirect URI. */
    path: 'auth',
    /* @end */
  });
  const clientId = '<CLIENT_ID>';

  // We store the JWT in here
  const [token, setToken] = React.useState<string | null>(null);

  // Request
  const [request, , promptAsync] = useAuthRequest(
    {
      clientId,
      scopes: ['openid', 'profile', 'email', 'offline_access'],
      redirectUri,
    },
    discovery,
  );

  return (
    <SafeAreaView>
      <Button
        /* @info Disable the button until the request is loaded asynchronously. */
        disabled={!request}
        /* @end */
        title="Login"
        onPress={() => {
          /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
          promptAsync().then((codeResponse) => {
            /* @end */
            if (request && codeResponse?.type === 'success' && discovery) {
              /* @info Exchange the code to get the JWT. */
              exchangeCodeAsync(
                /* @end */
                {
                  clientId,
                  code: codeResponse.params.code,
                  /* @info Reuse the codeVerifier for PCKE */
                  extraParams: request.codeVerifier
                    ? { code_verifier: request.codeVerifier }
                    : undefined,
                  /* @end */
                  redirectUri,
                },
                discovery,
              ).then((res) => {
                setToken(res.accessToken);
              });
            }
          });
        }}
      />
      <Text>{token}</Text>
    </SafeAreaView>
  );
}
```

</Box>
{/* End Azure */}

<Box
  name="Beyond Identity"
  createUrl="https://www.beyondidentity.com/developers/signup"
  image={ASSETS.beyondidentity}
>

| Website                                                             | Provider | PKCE      | Auto Discovery |
| ------------------------------------------------------------------- | -------- | --------- | -------------- |
| [Get your config](https://www.beyondidentity.com/developers/signup) | OpenID   | Supported | Available      |

- Beyond Identity allows developers to implement strong passwordless authentication based on public-private key pairs called Universal Passkeys. All keys are cryptographically linked to the user and can be centrally managed using the Beyond Identity APIs.
- You will need a Universal Passkey before you can authenticate. See [Beyond Identity documentation](https://developer.beyondidentity.com).
- Make sure to [create a development build](/develop/development-builds/create-a-build/) and follow instructions to [install required config plugins](https://github.com/gobeyondidentity/bi-sdk-react-native/tree/main#using-expo).
- For a complete example app, see [SDK's GitHub repository](https://github.com/gobeyondidentity/bi-sdk-react-native).

<Tabs tabs={["Auth Code with automatic invocation", "Auth Code with manual invocation"]}>
<Tab>
- Set your Beyond Identity [Authenticator Config's](https://developer.beyondidentity.com/docs/v1/platform-overview/authenticator-config) Invocation Type to **Automatic**.

- If **Automatic** is selected, Beyond Identity will automatically redirect to your application using the **Invoke URL** (the App Scheme or Univeral URL pointing to your application).
  {/* prettier-ignore */}

```jsx Auth Code
import { useEffect } from 'react';
import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
import { Button } from 'react-native';
/* @info Import the Beyond Identity Embedded SDK.*/
import { Embedded } from '@beyondidentity/bi-sdk-react-native';
/* @end */

export default function App() {
  // Endpoint
  const discovery = useAutoDiscovery(
    `https://auth-${region}.beyondidentity.com/v1/tenants/${tenant_id}/realms/${realm_id}/applications/${application_id}`
  );
  // Request
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: `${client_id}`,
      scopes: ['openid'],
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property in your app.json or app.config.js is used instead. */
        scheme: 'your.app',
        /* @end */
      }),
    },
    discovery
  );

  useEffect(() => {
    const authenticate = async url => {
      // Display UI for user to select a passwordless passkey
      const passkeys = await Embedded.getPasskeys();

      if (await Embedded.isAuthenticateUrl(url)) {
        // Pass url and a selected passkey ID into the Beyond Identity Embedded SDK authenticate function
        /* @info Parse query parameters from the 'redirectUrl' for a 'code' and then exchange that code for an access token */
        const { redirectUrl } = await Embedded.authenticate(url, passkeys[0].id);
        /* @end */
      }
    };

    /* @info The response does not need to be of type 'success' if it has a url. The state value is stored in a JWT in the url 'request' parameter, and the url will be validated through the Beyond Identity Embedded SDK. */
    if (response?.url) {
      /* @end */
      authenticate(url);
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Passwordless Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>

<Tab>
- Set your Beyond Identity [Authenticator Config's](https://developer.beyondidentity.com/docs/v1/platform-overview/authenticator-config) Invocation Type to **Manual**.
- If **Manual** is selected, an authentication URL is returned as part of a JSON response. No redirects are needed and do not require web service authentication. The result is a completely silent OAuth 2.0 authentication using Passkeys.

{/* prettier-ignore */}
```jsx Auth Code
import React from 'react';
import { Button } from 'react-native';
/* @info Import the Beyond Identity Embedded SDK.*/
import { Embedded } from '@beyondidentity/bi-sdk-react-native';
/* @end */

export default function App() {
  async function authenticate() {
    const BeyondIdentityAuthUrl = `https://auth-${region}.beyondidentity.com/v1/tenants/${tenant_od}/realms/${realm_id}/applications/${application_id}/authorize?response_type=code&client_id=${client_id}&redirect_uri=${uri_encoded_redirect_uri}&scope=openid&state=${state}&code_challenge_method=S256&code_challenge=${pkce_code_challenge}`;

    let response = await fetch(BeyondIdentityAuthUrl, {
      method: 'GET',
      headers: new Headers({
        'Content-Type': 'application/json',
      }),
    });
    const data = await response.json();

    // Display UI for user to select a passwordless passkey
    const passkeys = await Embedded.getPasskeys();

    if (await Embedded.isAuthenticateUrl(data.authenticate_url)) {
      // Pass url and selected Passkey ID into the Beyond Identity Embedded SDK authenticate function
      /* @info Parse query parameters from the 'redirectUrl' for a 'code' and then exchange that code for an access token */
      const { redirectUrl } = await Embedded.authenticate(data.authenticate_url, passkeys[0].id);
      /* @end */
    }
  }

  return (
    <Button
      title="Passwordless Login"
      onPress={authenticate}
    />
  );
}
```

</Tab>

</Tabs>

</Box>
{/* End Beyond Identity */}

<Box
  name="Cognito"
  createUrl="https://console.aws.amazon.com/cognito/v2/idp/user-pools"
  image={ASSETS.cognito}
>

| Website                      | Provider | PKCE      | Auto Discovery |
| ---------------------------- | -------- | --------- | -------------- |
| [Get Your Config][c-cognito] | OpenID   | Supported | Not Available  |

[c-cognito]: https://console.aws.amazon.com/cognito/v2/idp/user-pools
[c-cognito-api-docs]: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-userpools-server-contract-reference.html

- Leverages the Hosted UI in Cognito ([API documentation][c-cognito-api-docs])
- Requests code after successfully authenticating, followed by exchanging code for the auth tokens (PKCE)
- The `/token` endpoint requires a `code_verifier` parameter which you can retrieve from the request before calling `exchangeCodeAsync()`:

```tsx
extraParams: {
  code_verifier: request.codeVerifier,
}
```

<Tabs tabs={["Auth Code"]}>

<Tab>

{/* prettier-ignore */}
```tsx
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { useAuthRequest, exchangeCodeAsync, revokeAsync, ResponseType } from 'expo-auth-session';
import { Button, Alert } from 'react-native';

WebBrowser.maybeCompleteAuthSession();

const clientId = '<your-client-id-here>';
const userPoolUrl =
  'https://<your-user-pool-domain>.auth.<your-region>.amazoncognito.com';
const redirectUri = 'your-redirect-uri';

export default function App() {
  const [authTokens, setAuthTokens] = React.useState(null);
  const discoveryDocument = React.useMemo(() => ({
    authorizationEndpoint: userPoolUrl + '/oauth2/authorize',
    tokenEndpoint: userPoolUrl + '/oauth2/token',
    revocationEndpoint: userPoolUrl + '/oauth2/revoke',
  }), []);

  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId,
      responseType: ResponseType.Code,
      redirectUri,
      usePKCE: true,
    },
    discoveryDocument
  );

  React.useEffect(() => {
    const exchangeFn = async (exchangeTokenReq) => {
      try {
        const exchangeTokenResponse = await exchangeCodeAsync(
          exchangeTokenReq,
          discoveryDocument
        );
        setAuthTokens(exchangeTokenResponse);
      } catch (error) {
        console.error(error);
      }
    };
    if (response) {
      if (response.error) {
        Alert.alert(
          'Authentication error',
          response.params.error_description || 'something went wrong'
        );
        return;
      }
      if (response.type === 'success') {
        exchangeFn({
          clientId,
          code: response.params.code,
          redirectUri,
          extraParams: {
            code_verifier: request.codeVerifier,
          },
        });
      }
    }
  }, [discoveryDocument, request, response]);

  const logout = async () => {
    const revokeResponse = await revokeAsync(
      {
        clientId: clientId,
        token: authTokens.refreshToken,
      },
      discoveryDocument
    );
    if (revokeResponse) {
      setAuthTokens(null);
    }
  };
  console.log('authTokens: ' + JSON.stringify(authTokens));
  return authTokens ? (
    <Button title="Logout" onPress={() => logout()} />
  ) : (
    <Button disabled={!request} title="Login" onPress={() => promptAsync()} />
  );
}
```

</Tab>

</Tabs>

</Box>

{/* End Cognito */}

<Box
  name="Coinbase"
  createUrl="https://www.coinbase.com/oauth/applications/new"
  image={ASSETS.coinbase}
>

| Website                       | Provider  | PKCE      | Auto Discovery |
| ----------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-coinbase] | OAuth 2.0 | Supported | Not Available  |

[c-coinbase]: https://www.coinbase.com/oauth/applications/new

- 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:8081/--/`. For SDK 48 and lower, the port number is `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.

<Tabs tabs={["Auth Code", "Implicit Flow"]}>
<Tab>

{/* prettier-ignore */}
```tsx
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";

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// 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({ /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */ scheme: 'your.app'/* @end */});
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,
    /* @info If the auto exchange fails, you can display the error. */
    exchangeError,
    /* @end */
  } = useAutoExchange(
    /* @info The auth code will be exchanged for an access token as soon as it's available. */
    response?.type === "success" ? response.params.code : null
    /* @end */
  );

  React.useEffect(() => {
    if (token) {
      /* @info The access token is now ready to be used to make authenticated requests. */
      console.log("My Token:", token.accessToken);
      /* @end */
    }
  }, [token]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}

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

    /* @info Swap this method out for a fetch request to a server that exchanges your auth code securely. */
    exchangeCodeAsync(
      /* @end */
      {
        clientId: CLIENT_ID,
        /* @info <b>Never</b> store your client secret in the application code! */
        clientSecret: "CLIENT_SECRET",
        /* @end */
        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;
}
```

</Tab>

<Tab>

- Coinbase does not support implicit grant.

</Tab>
</Tabs>
</Box>
{/* End Coinbase */}

<Box
  name="Dropbox"
  createUrl="https://www.dropbox.com/developers/apps/create"
  image={ASSETS.dropbox}
>

| Website                      | Provider  | PKCE          | Auto Discovery |
| ---------------------------- | --------- | ------------- | -------------- |
| [Get Your Config][c-dropbox] | OAuth 2.0 | Not Supported | Not Available  |

[c-dropbox]: https://www.dropbox.com/developers/apps/create

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

<Tabs tabs={["Auth Code", "Implicit Flow"]}>

<Tab>

Auth code responses (`ResponseType.Code`) will only work in native with `useProxy: true`.

{/* prettier-ignore */}
```tsx
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';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://www.dropbox.com/oauth2/authorize',
  tokenEndpoint: 'https://www.dropbox.com/oauth2/token',
};

/* @info Implicit auth is universal, <code>.Code</code> will only work in native with <code>useProxy: true</code>. */
const useProxy = Platform.select({ web: false, default: true });
/* @end */

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({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app',
        /* @end */
        useProxy,
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync({ useProxy });
        /* @end */
      }}
    />
  );
}
```

</Tab>

<Tab>

{/* prettier-ignore */}
```tsx
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';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// 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(
    {
      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
      responseType: ResponseType.Token,
      /* @end */
      clientId: 'CLIENT_ID',
      // There are no scopes so just pass an empty array
      scopes: [],
      // Dropbox doesn't support PKCE
      usePKCE: false,
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Use this access token to interact with user data on the provider's server. */
      const { access_token } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>
</Tabs>
</Box>
{/* End Dropbox */}

<Box
  name="Fitbit"
  createUrl="https://dev.fitbit.com/apps/new"
  image={ASSETS.fitbit}
>

| Website                     | Provider  | PKCE      | Auto Discovery |
| --------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-fitbit] | OAuth 2.0 | Supported | Not Available  |

[c-fitbit]: https://dev.fitbit.com/apps/new

- 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:8081/--/*`. For SDK 48 and lower, the port number is `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 (`://`).

<Tabs tabs={["Auth Code", "Implicit Flow"]}>
<Tab>

{/* prettier-ignore */}
```tsx
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';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// 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({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>

<Tab>

{/* prettier-ignore */}
```tsx
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';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

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(
    {
      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
      responseType: ResponseType.Token,
      /* @end */
      clientId: 'CLIENT_ID',
      scopes: ['activity', 'sleep'],
      // For usage in managed apps using the proxy
      redirectUri: makeRedirectUri({
        useProxy,
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Use this access token to interact with user data on the provider's server. */
      const { access_token } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync({ useProxy });
        /* @end */
      }}
    />
  );
}
```

</Tab>
</Tabs>
</Box>
{/* End FitBit */}

<Box
  name="GitHub"
  createUrl="https://github.com/settings/developers"
  image={ASSETS.github}
>

| Website                     | Provider  | PKCE      | Auto Discovery |
| --------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-github] | OAuth 2.0 | Supported | Not Available  |

[c-github]: https://github.com/settings/developers

- 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:8081/--/*`. For SDK 48 and lower, the port number is `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`.

<Tabs tabs={["Auth Code", "Implicit Flow"]}>
<Tab>

{/* prettier-ignore */}
```tsx
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// 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({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>

<Tab>

- Implicit grant is [not supported for GitHub](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/).

</Tab>
</Tabs>
</Box>

<Box
  name="Imgur"
  createUrl="https://api.imgur.com/oauth2/addclient"
  image={ASSETS.imgur}
>

| Website                    | Provider  | PKCE      | Auto Discovery |
| -------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-imgur] | OAuth 2.0 | Supported | Not Available  |

[c-imgur]: https://api.imgur.com/oauth2/addclient

- You will need to create a different provider app for each platform (dynamically choosing your `clientId`).
- Learn more here: [imgur.com/oauth2](https://api.imgur.com/oauth2)

<Tabs tabs={["Auth Code", "Implicit Flow"]}>
<Tab>

```tsx
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';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

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({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app',
        /* @end */
      }),
      // imgur requires an empty array
      scopes: [],
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>

<Tab>

```tsx
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';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

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(
    {
      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
      responseType: ResponseType.Token,
      /* @end */
      clientId: 'CLIENT_ID',
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app',
        /* @end */
      }),
      scopes: [],
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Use this access token to interact with user data on the provider's server. */
      const { access_token } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>
</Tabs>
</Box>
{/* End Imgur */}

<Box
  name="Keycloak"
  image={ASSETS.keycloak}
>

| Website | Provider | PKCE     | Auto Discovery |
| ------- | -------- | -------- | -------------- |
| -       | OpenID   | Required | Available      |

{/* prettier-ignore */}
```tsx Keycloak Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
import { Button, Text, View } from 'react-native';

WebBrowser.maybeCompleteAuthSession();

export default function App() {
  /* @info If the provider supports auto discovery then you can pass an issuer to the `useAutoDiscovery` hook to fetch the discovery document. */
  const discovery = useAutoDiscovery('https://YOUR_KEYCLOAK/realms/YOUR_REALM');
  /* @end */

// Create and load an auth request
  const [request, result, promptAsync] = useAuthRequest(
    {
      clientId: 'YOUR_CLIENT_NAME',
      /* @info After a user finishes authenticating, the server will redirect them to this URI. Learn more about <a href="../../guides/linking/">linking here</a>. */
      redirectUri: makeRedirectUri({
        scheme: 'YOUR_SCHEME'
      }),
      /* @end */
      scopes: ['openid', 'profile'],
    },
    discovery
  );

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Button title="Login!" disabled={!request} onPress={() => promptAsync()} />
      {result && <Text>{JSON.stringify(result, null, 2)}</Text>}
    </View>
  );
}
```

</Box>
{/* End Keycloak */}

<Box
  name="Okta"
  createUrl="https://developer.okta.com/signup"
  image={ASSETS.okta}
>

| Website                          | Provider | PKCE      | Auto Discovery |
| -------------------------------- | -------- | --------- | -------------- |
| [Sign-up][c-okta] > Applications | OpenID   | Supported | Available      |

[c-okta]: https://developer.okta.com/signup/

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

<Tabs tabs={["Auth Code", "Implicit Flow"]}>
<Tab>

{/* prettier-ignore */}
```tsx
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';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

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') {
      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync({ useProxy });
        /* @end */
      }}
    />
  );
}
```

</Tab>

<Tab>

- This flow is not documented yet, learn more [from the Okta website](https://developer.okta.com/docs/guides/implement-implicit/use-flow/).

</Tab>
</Tabs>
</Box>
{/* End Okta */}

<Box
  name="Reddit"
  createUrl="https://www.reddit.com/prefs/apps"
  image={ASSETS.reddit}
>

| Website                     | Provider  | PKCE      | Auto Discovery |
| --------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-reddit] | OAuth 2.0 | Supported | Not Available  |

[c-reddit]: https://www.reddit.com/prefs/apps

- 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:8081/--/*`. For SDK 48 and lower, the port number is `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 (`://`).

<Tabs tabs={["Auth Code", "Implicit Flow"]}>

<Tab>

{/* prettier-ignore */}
```tsx
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// 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') {
      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>

<Tab>

- You must select the `installed` option for your app on Reddit to use implicit grant.

{/* prettier-ignore */}
```tsx
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';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// 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(
    {
      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
      responseType: ResponseType.Token,
      /* @end */
      clientId: 'CLIENT_ID',
      scopes: ['identity'],
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Use this access token to interact with user data on the provider's server. */
      const { access_token } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>
</Tabs>
</Box>
{/* End Reddit */}

<Box
  name="Slack"
  createUrl="https://api.slack.com/apps"
  image={ASSETS.slack}
>

| Website                    | Provider  | PKCE      | Auto Discovery |
| -------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-slack] | OAuth 2.0 | Supported | Not Available  |

[c-slack]: https://api.slack.com/apps

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

<Tabs tabs={["Auth Code", "Implicit Flow"]}>

<Tab>

{/* prettier-ignore */}
```tsx
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// 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({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>

<Tab>

- Slack does not support implicit grant.

</Tab>
</Tabs>
</Box>
{/* End Slack */}

<Box
  name="Spotify"
  createUrl="https://developer.spotify.com/dashboard/applications"
  image={ASSETS.spotify}
>

| Website                      | Provider  | PKCE      | Auto Discovery |
| ---------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-spotify] | OAuth 2.0 | Supported | Not Available  |

[c-spotify]: https://developer.spotify.com/dashboard/applications

- Setup your redirect URIs: Your project > Edit Settings > Redirect URIs (be sure to save after making changes).
  - _Expo Go_: `exp://localhost:8081/--/`. For SDK 48 and lower, the port number is `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](https://developer.spotify.com/documentation/web-api/).

<Tabs tabs={["Auth Code", "Implicit Flow"]}>
<Tab>

{/* prettier-ignore */}
```tsx
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// 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'],
      // To follow the "Authorization Code Flow" to fetch token after authorizationEndpoint
      // this must be set to false
      usePKCE: false,
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>

<Tab>

{/* prettier-ignore */}
```tsx
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';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// 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(
    {
      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
      responseType: ResponseType.Token,
      /* @end */
      clientId: 'CLIENT_ID',
      scopes: ['user-read-email', 'playlist-modify-public'],
      // To follow the "Authorization Code Flow" to fetch token after authorizationEndpoint
      // this must be set to false
      usePKCE: false,
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Use this access token to interact with user data on the provider's server. */
      const { access_token } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>
</Tabs>
</Box>
{/* End Spotify */}

<Box
  name="Strava"
  createUrl="https://www.strava.com/settings/api"
  image={ASSETS.strava}
>

| Website                     | Provider  | PKCE      | Auto Discovery |
| --------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-strava] | OAuth 2.0 | Supported | Not Available  |

[c-strava]: https://www.strava.com/settings/api

- Learn more about the [Strava API](http://developers.strava.com/docs/reference/).
- 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.

<Tabs tabs={["Auth Code"]}>
<Tab>

{/* prettier-ignore */}
```tsx
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// 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') {
      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

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:

{/* prettier-ignore */}
```tsx
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' }
);
```

</Tab>
</Tabs>
</Box>
{/* End Strava */}

<Box
  name="Twitch"
  createUrl="https://dev.twitch.tv/console/apps/create"
  image={ASSETS.twitch}
>

| Website                     | Provider | PKCE      | Auto Discovery | Scopes           |
| --------------------------- | -------- | --------- | -------------- | ---------------- |
| [Get your Config][c-twitch] | OAuth    | Supported | Not Available  | [Info][s-twitch] |

[c-twitch]: https://dev.twitch.tv/console/apps/create
[s-twitch]: https://dev.twitch.tv/docs/authentication#scopes

- You will need to enable 2FA on your Twitch account to create an application.

<Tabs tabs={["Auth Code", "Implicit Flow"]}>

<Tab>

{/* prettier-ignore */}
```tsx
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// 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({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
      scopes: ['user:read:email', 'analytics:read:games'],
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>

<Tab>

{/* prettier-ignore */}
```tsx
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';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// 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(
    {
      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
      responseType: ResponseType.Token,
      /* @end */
      clientId: 'CLIENT_ID',
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
      scopes: ['user:read:email', 'analytics:read:games'],
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Use this access token to interact with user data on the provider's server. */
      const { access_token } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>
</Tabs>
</Box>
{/* End Twitch */}

<Box
  name="Twitter"
  createUrl="https://developer.twitter.com/en/portal/projects/new"
  image={ASSETS.twitter}
>

| Website                      | Provider | PKCE      | Auto Discovery | Scopes            |
| ---------------------------- | -------- | --------- | -------------- | ----------------- |
| [Get your Config][c-twitter] | OAuth    | Supported | Not Available  | [Info][s-twitter] |

[c-twitter]: https://developer.twitter.com/en/portal/projects/new
[s-twitter]: https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code

- You will need to be approved by Twitter support before you can use the Twitter v2 API.
- Web does not appear to work, the Twitter authentication website appears to block the popup, causing the `response` of `useAuthRequest` to always be `{type: 'dismiss'}`.
- Example redirects:
  - Expo Go + Proxy: `https://auth.expo.io/@you/your-app`
  - Standalone or Bare: `com.your.app://`
  - Web (dev `expo start --https`): `https://localhost:19006` (no ending slash)
- The `redirectUri` requires 2 slashes (`://`).

#### Expo Go

You must use the proxy service in the Expo Go app because `exp://localhost:8081` cannot be added to your Twitter app as a redirect.

<Tabs tabs={["Auth Code"]}>

<Tab>

{/* prettier-ignore */}
```tsx
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button, Platform } from 'react-native';

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

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: "https://twitter.com/i/oauth2/authorize",
  tokenEndpoint: "https://twitter.com/i/oauth2/token",
  revocationEndpoint: "https://twitter.com/i/oauth2/revoke",
};

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app',
        /* @end */
        useProxy
      }),
      usePKCE: true,
      scopes: [
        "tweet.read",
      ],
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync({ useProxy });
        /* @end */
      }}
    />
  );
}
```

</Tab>
</Tabs>
</Box>
{/* End Twitter */}

<Box
  name="Uber"
  createUrl="https://developer.uber.com/docs/riders/guides/authentication/introduction"
  image={ASSETS.uber}
>

| Website                   | Provider  | PKCE      | Auto Discovery |
| ------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-uber] | OAuth 2.0 | Supported | Not Available  |

[c-uber]: https://developer.uber.com/docs/riders/guides/authentication/introduction

- The `redirectUri` requires 2 slashes (`://`).
- `scopes` can be difficult to get approved.

<Tabs tabs={["Auth Code", "Implicit Flow"]}>

<Tab>

{/* prettier-ignore */}
```tsx
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// 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({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>

<Tab>

{/* prettier-ignore */}
```tsx
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';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// 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(
    {
      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
      responseType: ResponseType.Token,
      /* @end */
      clientId: 'CLIENT_ID',
      scopes: ['profile', 'delivery'],
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Use this access token to interact with user data on the provider's server. */
      const { access_token } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>
</Tabs>
</Box>
{/* End Uber */}

{/* End Guides */}

## Redirect URI patterns

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

### Standalone and bare

> `yourscheme://path`

In some cases there will be anywhere between 1 to 3 slashes (`/`).

- **Environment:**

  - Bare workflow
    - `npx expo prebuild`
  - Standalone builds in the App or Play Store or testing locally
    - Android: `eas build` or `npx expo run:android`
    - iOS: `eas build` or `npx expo run:ios`

- **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://u.expo.dev/[project-id]?channel-name=[channel-name]&runtime-version=[runtime-version]` -> `makeRedirectUri()`
  - 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 })`

## Improving user experience

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!

### Warming the browser

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.

{/* prettier-ignore */}
```tsx
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';

function App() {
  React.useEffect(() => {
    /* @info <strong>Android only:</strong> Start loading the default browser app in the background to improve transition time. */
    WebBrowser.warmUpAsync();
    /* @end */

    return () => {
      /* @info <strong>Android only:</strong> Cool down the browser when the component unmounts to help improve memory on low-end Android devices. */
      WebBrowser.coolDownAsync();
      /* @end */
    };
  }, []);

  // Do authentication ...
}
```

### Implicit login

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.

{/* prettier-ignore */}
```tsx
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest, ResponseType } from 'expo-auth-session';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://accounts.spotify.com/authorize',
};

function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
      responseType: ResponseType.Token,
      /* @end */
      clientId: 'CLIENT_ID',
      scopes: ['user-read-email', 'playlist-modify-public'],
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used in bare and standalone. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response && response.type === 'success') {
      /* @info You can use this access token to make calls into the Spotify API. */
      const token = response.params.access_token;
      /* @end */
    }
  }, [response]);

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

### Storing data

On native platforms like iOS, and Android you can secure things like access tokens locally using a package called [`expo-secure-store`](/versions/latest/sdk/securestore) (This is different to `AsyncStorage` which is not secure). This package provides native access to [keychain services](https://developer.apple.com/documentation/security/keychain_services) on iOS and encrypted [`SharedPreferences`](https://developer.android.com/training/basics/data-storage/shared-preferences.html) 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.

{/* prettier-ignore */}
```tsx
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...
}
```

[userinfo]: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
[provider-meta]: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
[oidc-dcr]: https://openid.net/specs/openid-connect-discovery-1_0.html#OpenID.Registration
[oidc-autherr]: https://openid.net/specs/openid-connect-core-1_0.html#AuthError
[oidc-authreq]: https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationRequest
