xref: /expo/docs/pages/guides/authentication.mdx (revision a16ac082)
1---
2title: Authentication with OAuth or OpenID providers
3---
4
5import ImageSpotlight from '~/components/plugins/ImageSpotlight';
6import { ASSETS, Grid, GridItem, Box } from '~/ui/components/Authentication';
7import { Tab, Tabs } from '~/ui/components/Tabs';
8
9Expo 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.
10
11Here are some **important rules** that apply to all authentication providers:
12
13- Use `WebBrowser.maybeCompleteAuthSession()` to dismiss the web popup. If you forget to add this then the popup window will not close.
14- 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`.
15- Build requests using `AuthSession.useAuthRequest()`, the hook allows for async setup which means mobile browsers won't block the authentication.
16- Be sure to disable the prompt until `request` is defined.
17- You can only invoke `promptAsync` in a user-interaction on web.
18
19## Guides
20
21**AuthSession** can be used for any OAuth or OpenID Connect provider, we've assembled guides for using the most requested services!
22If 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).
23
24<Grid>
25  <GridItem title="IdentityServer 4" protocol={['OAuth 2', 'OpenID']} image={ASSETS.id4} />
26  <GridItem title="Asgardeo" protocol={['OAuth 2', 'OpenID']} image={ASSETS.asgardeo} />
27  <GridItem title="Azure" protocol={['OAuth 2', 'OpenID']} image={ASSETS.azure} />
28  <GridItem
29    title="Apple"
30    protocol={['iOS Only']}
31    href="/versions/latest/sdk/apple-authentication"
32    image={ASSETS.apple}
33  />
34  <GridItem
35    title="Beyond Identity"
36    protocol={['OAuth 2', 'OpenID']}
37    image={ASSETS.beyondidentity}
38  />
39  <GridItem title="Cognito" protocol={['OAuth 2', 'OpenID']} image={ASSETS.cognito} />
40  <GridItem title="Coinbase" protocol={['OAuth 2']} image={ASSETS.coinbase} />
41  <GridItem title="Dropbox" protocol={['OAuth 2']} image={ASSETS.dropbox} />
42  <GridItem
43    title="Facebook"
44    protocol={['OAuth 2']}
45    href="/guides/facebook-authentication/"
46    image={ASSETS.facebook}
47  />
48  <GridItem title="Fitbit" protocol={['OAuth 2']} image={ASSETS.fitbit} />
49  <GridItem
50    title="Firebase Phone"
51    protocol={['Recaptcha']}
52    href="/versions/latest/sdk/firebase-recaptcha"
53    image={ASSETS.firebase}
54  />
55  <GridItem title="GitHub" protocol={['OAuth 2']} image={ASSETS.github} />
56  <GridItem
57    title="Google"
58    protocol={['OAuth 2', 'OpenID']}
59    href="/guides/google-authentication/"
60    image={ASSETS.google}
61  />
62  <GridItem title="Imgur" protocol={['OAuth 2']} image={ASSETS.imgur} />
63  <GridItem title="Keycloak" protocol={['OAuth 2', 'OpenID']} image={ASSETS.keycloak} />
64  <GridItem title="Okta" protocol={['OAuth 2', 'OpenID']} image={ASSETS.okta} />
65  <GridItem title="Reddit" protocol={['OAuth 2']} image={ASSETS.reddit} />
66  <GridItem title="Slack" protocol={['OAuth 2']} image={ASSETS.slack} />
67  <GridItem title="Spotify" protocol={['OAuth 2']} image={ASSETS.spotify} />
68  <GridItem title="Strava" protocol={['OAuth 2']} image={ASSETS.strava} />
69  <GridItem title="Twitch" protocol={['OAuth 2']} image={ASSETS.twitch} />
70  <GridItem title="Twitter" protocol={['OAuth 2']} image={ASSETS.twitter} />
71  <GridItem title="Uber" protocol={['OAuth 2']} image={ASSETS.uber} />
72</Grid>
73
74<Box
75  name="IdentityServer 4"
76  image={ASSETS.id4}
77>
78
79| Website                  | Provider | PKCE     | Auto Discovery |
80| ------------------------ | -------- | -------- | -------------- |
81| [More Info][c-identity4] | OpenID   | Required | Available      |
82
83[c-identity4]: https://demo.identityserver.io/
84
85- If `offline_access` isn't included then no refresh token will be returned.
86
87{/* prettier-ignore */}
88```tsx IdentityServer 4 Example
89import * as React from 'react';
90import { Button, Text, View } from 'react-native';
91import * as AuthSession from 'expo-auth-session';
92import * as WebBrowser from 'expo-web-browser';
93
94/* @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. */
95WebBrowser.maybeCompleteAuthSession();
96/* @end */
97/* @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. */
98const useProxy = true;
99/* @end */
100const redirectUri = AuthSession.makeRedirectUri({
101  useProxy,
102});
103
104export default function App() {
105  /* @info If the provider supports auto discovery then you can pass an issuer to the `useAutoDiscovery` hook to fetch the discovery document. */
106  const discovery = AuthSession.useAutoDiscovery('https://demo.identityserver.io');
107  /* @end */
108  // Create and load an auth request
109  const [request, result, promptAsync] = AuthSession.useAuthRequest(
110    {
111      clientId: 'native.code',
112      /* @info After a user finishes authenticating, the server will redirect them to this URI. Learn more about <a href="../../guides/linking/">linking here</a>. */
113      redirectUri,
114      /* @end */
115      scopes: ['openid', 'profile', 'email', 'offline_access'],
116    },
117    discovery
118  );
119
120  return (
121    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
122      <Button title="Login!" disabled={!request} onPress={() => promptAsync({ useProxy })} />
123      {result && <Text>{JSON.stringify(result, null, 2)}</Text>}
124    </View>
125  );
126}
127```
128
129</Box>
130{/* End IdentityServer 4 */}
131
132<Box
133  name="Asgardeo"
134  createUrl="https://wso2.com/asgardeo/docs/guides/#developer-guide"
135  image={ASSETS.asgardeo}
136>
137
138| Website                                         | Provider | PKCE      | Auto Discovery |
139| ----------------------------------------------- | -------- | --------- | -------------- |
140| [Get Your Config](https://console.asgardeo.io/) | OpenID   | Supported | Available      |
141
142- Make sure to check `Public Client` option in the console.
143- Choose the intended grant in the Allowed grant types section.
144
145<Tabs tabs={["Auth Code", "Implicit Flow"]}>
146
147<Tab>
148
149{/* prettier-ignore */}
150```tsx
151import { useState, useEffect } from 'react';
152import { StyleSheet, Text, View, Button, Alert } from 'react-native';
153import * as AuthSession from "expo-auth-session";
154import * as WebBrowser from "expo-web-browser";
155import jwtDecode from "jwt-decode";
156
157WebBrowser.maybeCompleteAuthSession();
158
159const useProxy = true;
160const redirectUri = AuthSession.makeRedirectUri({ useProxy });
161
162/* @info <strong>Client ID:</strong> This can be found on the protocol tab of Asgardeo Console */
163const CLIENT_ID = "YOUR_CLIENT_ID";
164/* @end */
165
166export default function App() {
167
168    /* @info <strong>Auto Discovery URL:</strong> This can be found on the info tab of Asgardeo Console. Copy the link under `Issuer` */
169    const discovery = AuthSession.useAutoDiscovery('https://api.asgardeo.io/t/<YOUR_ORG_NAME>/oauth2/token');
170    /* @end */
171    const [tokenResponse, setTokenResponse] = useState({});
172    const [decodedIdToken, setDecodedIdToken] = useState({});
173
174    const [request, result, promptAsync] = AuthSession.useAuthRequest(
175        {
176            redirectUri,
177            clientId: CLIENT_ID,
178            responseType: "code",
179            scopes: ["openid", "profile", "email"]
180        },
181        discovery
182    );
183
184    const getAccessToken = () => {
185      if (result?.params?.code) {
186        /* @info <strong>Token Endpoint:</strong> This can be found on the info tab of Asgardeo Console. Copy the link under `Token` */
187        fetch(
188        /* @end */
189        "https://api.asgardeo.io/t/iamapptesting/oauth2/token",
190          {
191            method: "POST",
192            headers: {
193              "Content-Type": "application/x-www-form-urlencoded"
194            },
195            body: `grant_type=authorization_code&code=${result?.params?.code}&redirect_uri=${redirectUri}&client_id=${CLIENT_ID}&code_verifier=${request?.codeVerifier}`
196          }).then((response) => {
197              return response.json();
198            }).then((data) => {
199              setTokenResponse(data);
200              setDecodedIdToken(jwtDecode(data.id_token));
201            }).catch((err) => {
202              console.log(err);
203            });
204        }
205    }
206
207    useEffect(() => {
208      (async function setResult() {
209        if (result) {
210          if (result.error) {
211            Alert.alert(
212              "Authentication error",
213              result.params.error_description || "something went wrong"
214            );
215            return;
216          }
217          if (result.type === "success") {
218            getAccessToken();
219          }
220        }
221      })();
222    }, [result]);
223
224
225    return (
226      <View style={styles.container}>
227        <Button title="Login" disabled={!request} onPress={() => promptAsync({ useProxy })} />
228        {decodedIdToken && <Text>Welcome {decodedIdToken.given_name || ""}!</Text>}
229        {decodedIdToken && <Text>{decodedIdToken.email}</Text>}
230        <View style={styles.accessTokenBlock}>
231          decodedToken && <Text>Access Token: {tokenResponse.access_token}</Text>
232        </View>
233      </View>
234    );
235}
236
237const styles = StyleSheet.create({
238    container: {
239        flex: 1,
240        backgroundColor: '#fff',
241        alignItems: 'center',
242        justifyContent: 'center',
243    },
244    accessTokenBlock: {
245        width: 300,
246        height: 500,
247        overflow: "scroll"
248    }
249});
250```
251
252</Tab>
253
254<Tab>
255
256{/* prettier-ignore */}
257```tsx
258import { useState, useEffect } from 'react';
259import { StyleSheet, Text, View, Button, Alert } from 'react-native';
260import * as AuthSession from "expo-auth-session";
261import * as WebBrowser from "expo-web-browser";
262import jwtDecode from "jwt-decode";
263
264WebBrowser.maybeCompleteAuthSession();
265
266const useProxy = true;
267const redirectUri = AuthSession.makeRedirectUri({ useProxy });
268
269/* @info <strong>Client ID:</strong> This can be found on the protocol tab of Asgardeo Console */
270const CLIENT_ID = "YOUR_CLIENT_ID";
271/* @end */
272
273export default function App() {
274
275  /* @info <strong>Auto Discovery URL:</strong> This can be found on the info tab of Asgardeo Console. Copy the link under `Issuer` */
276  const discovery = AuthSession.useAutoDiscovery('https://api.asgardeo.io/t/<YOUR_ORG_NAME>/oauth2/token');
277  /* @end */
278  const [decodedToken, setDecodedToken] = useState({});
279
280  const [request, result, promptAsync] = AuthSession.useAuthRequest(
281        {
282            redirectUri,
283            clientId: CLIENT_ID,
284            responseType: "token",
285            scopes: ["openid", "profile", "email"]
286        },
287        discovery
288    );
289
290  useEffect(() => {
291    (async function setResult() {
292      if (result) {
293        if (result.error) {
294          Alert.alert(
295            "Authentication error",
296            result.params.error_description || "something went wrong"
297          );
298          return;
299        }
300
301        if (result.type === "success") {
302          const jwtToken = result.params.access_token;
303          const decoded = jwtDecode(jwtToken);
304          setDecodedToken(decoded);
305        }
306      }
307    })();
308  }, [result]);
309
310  return (
311    <View style={styles.container}>
312      <Button title="Login" disabled={!request} onPress={() => promptAsync({ useProxy })} />
313      {decodedToken && <Text>Welcome {decodedToken.given_name || ""}!</Text>}
314      {decodedToken && <Text>{decodedToken.email}</Text>}
315      <View style={styles.accessTokenBlock}>
316        decodedToken && <Text>Access Token: {result?.params.access_token}</Text>
317      </View>
318    </View>
319  );
320}
321
322const styles = StyleSheet.create({
323    container: {
324        flex: 1,
325        backgroundColor: '#fff',
326        alignItems: 'center',
327        justifyContent: 'center',
328    },
329    accessTokenBlock: {
330        width: 300,
331        height: 500,
332        overflow: "scroll"
333    }
334});
335```
336
337</Tab>
338
339</Tabs>
340</Box>
341{/* End Asgardeo */}
342
343<Box
344  name="Azure"
345  createUrl="https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-overview"
346  image={ASSETS.azure}
347>
348
349| Website                     | Provider | PKCE      | Auto Discovery |
350| --------------------------- | -------- | --------- | -------------- |
351| [Get Your Config][c-azure2] | OpenID   | Supported | Available      |
352
353[c-azure2]: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-overview
354
355{/* prettier-ignore */}
356```tsx Azure Example
357import * as React from 'react';
358import * as WebBrowser from 'expo-web-browser';
359import {
360  exchangeCodeAsync,
361  makeRedirectUri,
362  useAuthRequest,
363  useAutoDiscovery,
364} from 'expo-auth-session';
365import { Button, Text, SafeAreaView } from 'react-native';
366
367/* @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. */
368WebBrowser.maybeCompleteAuthSession();
369/* @end */
370
371export default function App() {
372  // Endpoint
373  const discovery = useAutoDiscovery(
374    'https://login.microsoftonline.com/<TENANT_ID>/v2.0',
375  );
376  const redirectUri = makeRedirectUri({
377    /* @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. */
378    scheme: undefined,
379    /* @end */
380    /* @info Azure requires there to be a path in your redirect URI. */
381    path: 'auth',
382    /* @end */
383  });
384  const clientId = '<CLIENT_ID>';
385
386  // We store the JWT in here
387  const [token, setToken] = React.useState<string | null>(null);
388
389  // Request
390  const [request, , promptAsync] = useAuthRequest(
391    {
392      clientId,
393      scopes: ['openid', 'profile', 'email', 'offline_access'],
394      redirectUri,
395    },
396    discovery,
397  );
398
399  return (
400    <SafeAreaView>
401      <Button
402        /* @info Disable the button until the request is loaded asynchronously. */
403        disabled={!request}
404        /* @end */
405        title="Login"
406        onPress={() => {
407          /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
408          promptAsync().then((codeResponse) => {
409            /* @end */
410            if (request && codeResponse?.type === 'success' && discovery) {
411              /* @info Exchange the code to get the JWT. */
412              exchangeCodeAsync(
413                /* @end */
414                {
415                  clientId,
416                  code: codeResponse.params.code,
417                  /* @info Reuse the codeVerifier for PCKE */
418                  extraParams: request.codeVerifier
419                    ? { code_verifier: request.codeVerifier }
420                    : undefined,
421                  /* @end */
422                  redirectUri,
423                },
424                discovery,
425              ).then((res) => {
426                setToken(res.accessToken);
427              });
428            }
429          });
430        }}
431      />
432      <Text>{token}</Text>
433    </SafeAreaView>
434  );
435}
436```
437
438</Box>
439{/* End Azure */}
440
441<Box
442  name="Beyond Identity"
443  createUrl="https://www.beyondidentity.com/developers/signup"
444  image={ASSETS.beyondidentity}
445>
446
447| Website                                                             | Provider | PKCE      | Auto Discovery |
448| ------------------------------------------------------------------- | -------- | --------- | -------------- |
449| [Get your config](https://www.beyondidentity.com/developers/signup) | OpenID   | Supported | Available      |
450
451- 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.
452- You will need a Universal Passkey before you can authenticate. See [Beyond Identity documentation](https://developer.beyondidentity.com).
453- 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).
454- For a complete example app, see [SDK's GitHub repository](https://github.com/gobeyondidentity/bi-sdk-react-native).
455
456<Tabs tabs={["Auth Code with automatic invocation", "Auth Code with manual invocation"]}>
457<Tab>
458- Set your Beyond Identity [Authenticator Config's](https://developer.beyondidentity.com/docs/v1/platform-overview/authenticator-config) Invocation Type to **Automatic**.
459
460- 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).
461  {/* prettier-ignore */}
462
463```jsx Auth Code
464import { useEffect } from 'react';
465import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
466import { Button } from 'react-native';
467/* @info Import the Beyond Identity Embedded SDK.*/
468import { Embedded } from '@beyondidentity/bi-sdk-react-native';
469/* @end */
470
471export default function App() {
472  // Endpoint
473  const discovery = useAutoDiscovery(
474    `https://auth-${region}.beyondidentity.com/v1/tenants/${tenant_id}/realms/${realm_id}/applications/${application_id}`
475  );
476  // Request
477  const [request, response, promptAsync] = useAuthRequest(
478    {
479      clientId: `${client_id}`,
480      scopes: ['openid'],
481      redirectUri: makeRedirectUri({
482        /* @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. */
483        scheme: 'your.app',
484        /* @end */
485      }),
486    },
487    discovery
488  );
489
490  useEffect(() => {
491    const authenticate = async url => {
492      // Display UI for user to select a passwordless passkey
493      const passkeys = await Embedded.getPasskeys();
494
495      if (await Embedded.isAuthenticateUrl(url)) {
496        // Pass url and a selected passkey ID into the Beyond Identity Embedded SDK authenticate function
497        /* @info Parse query parameters from the 'redirectUrl' for a 'code' and then exchange that code for an access token */
498        const { redirectUrl } = await Embedded.authenticate(url, passkeys[0].id);
499        /* @end */
500      }
501    };
502
503    /* @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. */
504    if (response?.url) {
505      /* @end */
506      authenticate(url);
507    }
508  }, [response]);
509
510  return (
511    <Button
512      /* @info Disable the button until the request is loaded asynchronously. */
513      disabled={!request}
514      /* @end */
515      title="Passwordless Login"
516      onPress={() => {
517        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
518        promptAsync();
519        /* @end */
520      }}
521    />
522  );
523}
524```
525
526</Tab>
527
528<Tab>
529- Set your Beyond Identity [Authenticator Config's](https://developer.beyondidentity.com/docs/v1/platform-overview/authenticator-config) Invocation Type to **Manual**.
530- 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.
531
532{/* prettier-ignore */}
533```jsx Auth Code
534import React from 'react';
535import { Button } from 'react-native';
536/* @info Import the Beyond Identity Embedded SDK.*/
537import { Embedded } from '@beyondidentity/bi-sdk-react-native';
538/* @end */
539
540export default function App() {
541  async function authenticate() {
542    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}`;
543
544    let response = await fetch(BeyondIdentityAuthUrl, {
545      method: 'GET',
546      headers: new Headers({
547        'Content-Type': 'application/json',
548      }),
549    });
550    const data = await response.json();
551
552    // Display UI for user to select a passwordless passkey
553    const passkeys = await Embedded.getPasskeys();
554
555    if (await Embedded.isAuthenticateUrl(data.authenticate_url)) {
556      // Pass url and selected Passkey ID into the Beyond Identity Embedded SDK authenticate function
557      /* @info Parse query parameters from the 'redirectUrl' for a 'code' and then exchange that code for an access token */
558      const { redirectUrl } = await Embedded.authenticate(data.authenticate_url, passkeys[0].id);
559      /* @end */
560    }
561  }
562
563  return (
564    <Button
565      title="Passwordless Login"
566      onPress={authenticate}
567    />
568  );
569}
570```
571
572</Tab>
573
574</Tabs>
575
576</Box>
577{/* End Beyond Identity */}
578
579<Box
580  name="Cognito"
581  createUrl="https://console.aws.amazon.com/cognito/v2/idp/user-pools"
582  image={ASSETS.cognito}
583>
584
585| Website                      | Provider | PKCE      | Auto Discovery |
586| ---------------------------- | -------- | --------- | -------------- |
587| [Get Your Config][c-cognito] | OpenID   | Supported | Not Available  |
588
589[c-cognito]: https://console.aws.amazon.com/cognito/v2/idp/user-pools
590[c-cognito-api-docs]: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-userpools-server-contract-reference.html
591
592- Leverages the Hosted UI in Cognito ([API documentation][c-cognito-api-docs])
593- Requests code after successfully authenticating, followed by exchanging code for the auth tokens (PKCE)
594- The `/token` endpoint requires a `code_verifier` parameter which you can retrieve from the request before calling `exchangeCodeAsync()`:
595
596```tsx
597extraParams: {
598  code_verifier: request.codeVerifier,
599}
600```
601
602<Tabs tabs={["Auth Code"]}>
603
604<Tab>
605
606{/* prettier-ignore */}
607```tsx
608import * as React from 'react';
609import * as WebBrowser from 'expo-web-browser';
610import { useAuthRequest, exchangeCodeAsync, revokeAsync, ResponseType } from 'expo-auth-session';
611import { Button, Alert } from 'react-native';
612
613WebBrowser.maybeCompleteAuthSession();
614
615const clientId = '<your-client-id-here>';
616const userPoolUrl =
617  'https://<your-user-pool-domain>.auth.<your-region>.amazoncognito.com';
618const redirectUri = 'your-redirect-uri';
619
620export default function App() {
621  const [authTokens, setAuthTokens] = React.useState(null);
622  const discoveryDocument = React.useMemo(() => ({
623    authorizationEndpoint: userPoolUrl + '/oauth2/authorize',
624    tokenEndpoint: userPoolUrl + '/oauth2/token',
625    revocationEndpoint: userPoolUrl + '/oauth2/revoke',
626  }), []);
627
628  const [request, response, promptAsync] = useAuthRequest(
629    {
630      clientId,
631      responseType: ResponseType.Code,
632      redirectUri,
633      usePKCE: true,
634    },
635    discoveryDocument
636  );
637
638  React.useEffect(() => {
639    const exchangeFn = async (exchangeTokenReq) => {
640      try {
641        const exchangeTokenResponse = await exchangeCodeAsync(
642          exchangeTokenReq,
643          discoveryDocument
644        );
645        setAuthTokens(exchangeTokenResponse);
646      } catch (error) {
647        console.error(error);
648      }
649    };
650    if (response) {
651      if (response.error) {
652        Alert.alert(
653          'Authentication error',
654          response.params.error_description || 'something went wrong'
655        );
656        return;
657      }
658      if (response.type === 'success') {
659        exchangeFn({
660          clientId,
661          code: response.params.code,
662          redirectUri,
663          extraParams: {
664            code_verifier: request.codeVerifier,
665          },
666        });
667      }
668    }
669  }, [discoveryDocument, request, response]);
670
671  const logout = async () => {
672    const revokeResponse = await revokeAsync(
673      {
674        clientId: clientId,
675        token: authTokens.refreshToken,
676      },
677      discoveryDocument
678    );
679    if (revokeResponse) {
680      setAuthTokens(null);
681    }
682  };
683  console.log('authTokens: ' + JSON.stringify(authTokens));
684  return authTokens ? (
685    <Button title="Logout" onPress={() => logout()} />
686  ) : (
687    <Button disabled={!request} title="Login" onPress={() => promptAsync()} />
688  );
689}
690```
691
692</Tab>
693
694</Tabs>
695
696</Box>
697
698{/* End Cognito */}
699
700<Box
701  name="Coinbase"
702  createUrl="https://www.coinbase.com/oauth/applications/new"
703  image={ASSETS.coinbase}
704>
705
706| Website                       | Provider  | PKCE      | Auto Discovery |
707| ----------------------------- | --------- | --------- | -------------- |
708| [Get Your Config][c-coinbase] | OAuth 2.0 | Supported | Not Available  |
709
710[c-coinbase]: https://www.coinbase.com/oauth/applications/new
711
712- The `redirectUri` requires 2 slashes (`://`).
713- Scopes must be joined with ':' so just create one long string.
714- Setup redirect URIs: Your Project > Permitted Redirect URIs: (be sure to save after making changes).
715  - _Expo Go_: `exp://localhost:8081/--/`. For SDK 48 and lower, the port number is `19000`.
716  - _Web dev_: `https://localhost:19006`
717    - Run `expo start --web --https` to run with **https**, auth won't work otherwise.
718    - Adding a slash to the end of the URL doesn't matter.
719  - _Standalone and Bare_: `your-scheme://`
720    - Scheme should be specified in app.json `expo.scheme: 'your-scheme'`, then added to the app code with `makeRedirectUri({ native: 'your-scheme://' })`)
721  - _Proxy_: **Not Supported**
722    - You cannot use the Expo proxy (`useProxy`) because they don't allow `@` in their redirect URIs.
723  - _Web production_: `https://yourwebsite.com`
724    - Set this to whatever your deployed website URL is.
725
726<Tabs tabs={["Auth Code", "Implicit Flow"]}>
727<Tab>
728
729{/* prettier-ignore */}
730```tsx
731import {
732  exchangeCodeAsync,
733  makeRedirectUri,
734  TokenResponse,
735  useAuthRequest,
736} from "expo-auth-session";
737import * as WebBrowser from "expo-web-browser";
738import * as React from "react";
739import { Button } from "react-native";
740
741/* @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. */
742WebBrowser.maybeCompleteAuthSession();
743/* @end */
744
745// Endpoint
746const discovery = {
747  authorizationEndpoint: "https://www.coinbase.com/oauth/authorize",
748  tokenEndpoint: "https://api.coinbase.com/oauth/token",
749  revocationEndpoint: "https://api.coinbase.com/oauth/revoke",
750};
751
752const 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 */});
753const CLIENT_ID = "CLIENT_ID";
754
755export default function App() {
756  const [request, response, promptAsync] = useAuthRequest(
757    {
758      clientId: CLIENT_ID,
759      scopes: ["wallet:accounts:read"],
760      redirectUri,
761    },
762    discovery
763  );
764  const {
765    // The token will be auto exchanged after auth completes.
766    token,
767    /* @info If the auto exchange fails, you can display the error. */
768    exchangeError,
769    /* @end */
770  } = useAutoExchange(
771    /* @info The auth code will be exchanged for an access token as soon as it's available. */
772    response?.type === "success" ? response.params.code : null
773    /* @end */
774  );
775
776  React.useEffect(() => {
777    if (token) {
778      /* @info The access token is now ready to be used to make authenticated requests. */
779      console.log("My Token:", token.accessToken);
780      /* @end */
781    }
782  }, [token]);
783
784  return (
785    <Button
786      /* @info Disable the button until the request is loaded asynchronously. */
787      disabled={!request}
788      /* @end */
789      title="Login"
790      onPress={() => {
791        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
792        promptAsync();
793        /* @end */
794      }}
795    />
796  );
797}
798
799type State = {
800  token: TokenResponse | null;
801  exchangeError: Error | null;
802};
803
804// A hook to automatically exchange the auth token for an access token.
805// this should be performed in a server and not here in the application.
806// For educational purposes only:
807function useAutoExchange(code?: string): State {
808  const [state, setState] = React.useReducer(
809    (state: State, action: Partial<State>) => ({ ...state, ...action }),
810    { token: null, exchangeError: null }
811  );
812  const isMounted = useMounted();
813
814  React.useEffect(() => {
815    if (!code) {
816      setState({ token: null, exchangeError: null });
817      return;
818    }
819
820    /* @info Swap this method out for a fetch request to a server that exchanges your auth code securely. */
821    exchangeCodeAsync(
822      /* @end */
823      {
824        clientId: CLIENT_ID,
825        /* @info <b>Never</b> store your client secret in the application code! */
826        clientSecret: "CLIENT_SECRET",
827        /* @end */
828        code,
829        redirectUri,
830      },
831      discovery
832    )
833      .then((token) => {
834        if (isMounted.current) {
835          setState({ token, exchangeError: null });
836        }
837      })
838      .catch((exchangeError) => {
839        if (isMounted.current) {
840          setState({ exchangeError, token: null });
841        }
842      });
843  }, [code]);
844
845  return state;
846}
847
848function useMounted() {
849  const isMounted = React.useRef(true);
850  React.useEffect(() => {
851    return () => {
852      isMounted.current = false;
853    };
854  }, []);
855  return isMounted;
856}
857```
858
859</Tab>
860
861<Tab>
862
863- Coinbase does not support implicit grant.
864
865</Tab>
866</Tabs>
867</Box>
868{/* End Coinbase */}
869
870<Box
871  name="Dropbox"
872  createUrl="https://www.dropbox.com/developers/apps/create"
873  image={ASSETS.dropbox}
874>
875
876| Website                      | Provider  | PKCE          | Auto Discovery |
877| ---------------------------- | --------- | ------------- | -------------- |
878| [Get Your Config][c-dropbox] | OAuth 2.0 | Not Supported | Not Available  |
879
880[c-dropbox]: https://www.dropbox.com/developers/apps/create
881
882- Scopes must be an empty array.
883- PKCE must be disabled (`usePKCE: false`) otherwise you'll get an error about `code_challenge` being included in the query string.
884- Implicit auth is supported.
885- 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.
886
887<Tabs tabs={["Auth Code", "Implicit Flow"]}>
888
889<Tab>
890
891Auth code responses (`ResponseType.Code`) will only work in native with `useProxy: true`.
892
893{/* prettier-ignore */}
894```tsx
895import * as React from 'react';
896import * as WebBrowser from 'expo-web-browser';
897import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
898import { Button, Platform } from 'react-native';
899
900/* @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. */
901WebBrowser.maybeCompleteAuthSession();
902/* @end */
903
904// Endpoint
905const discovery = {
906  authorizationEndpoint: 'https://www.dropbox.com/oauth2/authorize',
907  tokenEndpoint: 'https://www.dropbox.com/oauth2/token',
908};
909
910/* @info Implicit auth is universal, <code>.Code</code> will only work in native with <code>useProxy: true</code>. */
911const useProxy = Platform.select({ web: false, default: true });
912/* @end */
913
914export default function App() {
915  const [request, response, promptAsync] = useAuthRequest(
916    {
917      clientId: 'CLIENT_ID',
918      // There are no scopes so just pass an empty array
919      scopes: [],
920      // Dropbox doesn't support PKCE
921      usePKCE: false,
922      // For usage in managed apps using the proxy
923      redirectUri: makeRedirectUri({
924        /* @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. */
925        scheme: 'your.app',
926        /* @end */
927        useProxy,
928      }),
929    },
930    discovery
931  );
932
933  React.useEffect(() => {
934    if (response?.type === 'success') {
935      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
936      const { code } = response.params;
937      /* @end */
938    }
939  }, [response]);
940
941  return (
942    <Button
943      /* @info Disable the button until the request is loaded asynchronously. */
944      disabled={!request}
945      /* @end */
946      title="Login"
947      onPress={() => {
948        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
949        promptAsync({ useProxy });
950        /* @end */
951      }}
952    />
953  );
954}
955```
956
957</Tab>
958
959<Tab>
960
961{/* prettier-ignore */}
962```tsx
963import * as React from 'react';
964import * as WebBrowser from 'expo-web-browser';
965import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session';
966import { Button } from 'react-native';
967
968/* @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. */
969WebBrowser.maybeCompleteAuthSession();
970/* @end */
971
972// Endpoint
973const discovery = {
974  authorizationEndpoint: 'https://www.dropbox.com/oauth2/authorize',
975  tokenEndpoint: 'https://www.dropbox.com/oauth2/token',
976};
977
978export default function App() {
979  const [request, response, promptAsync] = useAuthRequest(
980    {
981      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
982      responseType: ResponseType.Token,
983      /* @end */
984      clientId: 'CLIENT_ID',
985      // There are no scopes so just pass an empty array
986      scopes: [],
987      // Dropbox doesn't support PKCE
988      usePKCE: false,
989      redirectUri: makeRedirectUri({
990        /* @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. */
991        scheme: 'your.app'
992        /* @end */
993      }),
994    },
995    discovery
996  );
997
998  React.useEffect(() => {
999    if (response?.type === 'success') {
1000      /* @info Use this access token to interact with user data on the provider's server. */
1001      const { access_token } = response.params;
1002      /* @end */
1003    }
1004  }, [response]);
1005
1006  return (
1007    <Button
1008      /* @info Disable the button until the request is loaded asynchronously. */
1009      disabled={!request}
1010      /* @end */
1011      title="Login"
1012      onPress={() => {
1013        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
1014        promptAsync();
1015        /* @end */
1016      }}
1017    />
1018  );
1019}
1020```
1021
1022</Tab>
1023</Tabs>
1024</Box>
1025{/* End Dropbox */}
1026
1027<Box
1028  name="Fitbit"
1029  createUrl="https://dev.fitbit.com/apps/new"
1030  image={ASSETS.fitbit}
1031>
1032
1033| Website                     | Provider  | PKCE      | Auto Discovery |
1034| --------------------------- | --------- | --------- | -------------- |
1035| [Get Your Config][c-fitbit] | OAuth 2.0 | Supported | Not Available  |
1036
1037[c-fitbit]: https://dev.fitbit.com/apps/new
1038
1039- Provider only allows one redirect URI per app. You'll need an individual app for every method you want to use:
1040  - Expo Go: `exp://localhost:8081/--/*`. For SDK 48 and lower, the port number is `19000`.
1041  - Expo Go + Proxy: `https://auth.expo.io/@you/your-app`
1042  - Standalone or Bare: `com.your.app://*`
1043  - Web: `https://yourwebsite.com/*`
1044- The `redirectUri` requires 2 slashes (`://`).
1045
1046<Tabs tabs={["Auth Code", "Implicit Flow"]}>
1047<Tab>
1048
1049{/* prettier-ignore */}
1050```tsx
1051import * as React from 'react';
1052import * as WebBrowser from 'expo-web-browser';
1053import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
1054import { Button, Platform } from 'react-native';
1055
1056/* @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. */
1057WebBrowser.maybeCompleteAuthSession();
1058/* @end */
1059
1060// Endpoint
1061const discovery = {
1062  authorizationEndpoint: 'https://www.fitbit.com/oauth2/authorize',
1063  tokenEndpoint: 'https://api.fitbit.com/oauth2/token',
1064  revocationEndpoint: 'https://api.fitbit.com/oauth2/revoke',
1065};
1066
1067export default function App() {
1068  const [request, response, promptAsync] = useAuthRequest(
1069    {
1070      clientId: 'CLIENT_ID',
1071      scopes: ['activity', 'sleep'],
1072      redirectUri: makeRedirectUri({
1073        /* @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. */
1074        scheme: 'your.app'
1075        /* @end */
1076      }),
1077    },
1078    discovery
1079  );
1080
1081  React.useEffect(() => {
1082    if (response?.type === 'success') {
1083      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
1084      const { code } = response.params;
1085      /* @end */
1086    }
1087  }, [response]);
1088
1089  return (
1090    <Button
1091      /* @info Disable the button until the request is loaded asynchronously. */
1092      disabled={!request}
1093      /* @end */
1094      title="Login"
1095      onPress={() => {
1096        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
1097        promptAsync();
1098        /* @end */
1099      }}
1100    />
1101  );
1102}
1103```
1104
1105</Tab>
1106
1107<Tab>
1108
1109{/* prettier-ignore */}
1110```tsx
1111import * as React from 'react';
1112import * as WebBrowser from 'expo-web-browser';
1113import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session';
1114import { Button, Platform } from 'react-native';
1115
1116/* @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. */
1117WebBrowser.maybeCompleteAuthSession();
1118/* @end */
1119
1120const useProxy = Platform.select({ web: false, default: true });
1121
1122// Endpoint
1123const discovery = {
1124  authorizationEndpoint: 'https://www.fitbit.com/oauth2/authorize',
1125  tokenEndpoint: 'https://api.fitbit.com/oauth2/token',
1126  revocationEndpoint: 'https://api.fitbit.com/oauth2/revoke',
1127};
1128
1129export default function App() {
1130  const [request, response, promptAsync] = useAuthRequest(
1131    {
1132      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
1133      responseType: ResponseType.Token,
1134      /* @end */
1135      clientId: 'CLIENT_ID',
1136      scopes: ['activity', 'sleep'],
1137      // For usage in managed apps using the proxy
1138      redirectUri: makeRedirectUri({
1139        useProxy,
1140        /* @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. */
1141        scheme: 'your.app'
1142        /* @end */
1143      }),
1144    },
1145    discovery
1146  );
1147
1148  React.useEffect(() => {
1149    if (response?.type === 'success') {
1150      /* @info Use this access token to interact with user data on the provider's server. */
1151      const { access_token } = response.params;
1152      /* @end */
1153    }
1154  }, [response]);
1155
1156  return (
1157    <Button
1158      /* @info Disable the button until the request is loaded asynchronously. */
1159      disabled={!request}
1160      /* @end */
1161      title="Login"
1162      onPress={() => {
1163        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
1164        promptAsync({ useProxy });
1165        /* @end */
1166      }}
1167    />
1168  );
1169}
1170```
1171
1172</Tab>
1173</Tabs>
1174</Box>
1175{/* End FitBit */}
1176
1177<Box
1178  name="GitHub"
1179  createUrl="https://github.com/settings/developers"
1180  image={ASSETS.github}
1181>
1182
1183| Website                     | Provider  | PKCE      | Auto Discovery |
1184| --------------------------- | --------- | --------- | -------------- |
1185| [Get Your Config][c-github] | OAuth 2.0 | Supported | Not Available  |
1186
1187[c-github]: https://github.com/settings/developers
1188
1189- Provider only allows one redirect URI per app. You'll need an individual app for every method you want to use:
1190  - Expo Go: `exp://localhost:8081/--/*`. For SDK 48 and lower, the port number is `19000`.
1191  - Expo Go + Proxy: `https://auth.expo.io/@you/your-app`
1192  - Standalone or Bare: `com.your.app://*`
1193  - Web: `https://yourwebsite.com/*`
1194- The `redirectUri` requires 2 slashes (`://`).
1195- `revocationEndpoint` is dynamic and requires your `config.clientId`.
1196
1197<Tabs tabs={["Auth Code", "Implicit Flow"]}>
1198<Tab>
1199
1200{/* prettier-ignore */}
1201```tsx
1202import * as React from 'react';
1203import * as WebBrowser from 'expo-web-browser';
1204import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
1205import { Button } from 'react-native';
1206
1207/* @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. */
1208WebBrowser.maybeCompleteAuthSession();
1209/* @end */
1210
1211// Endpoint
1212const discovery = {
1213  authorizationEndpoint: 'https://github.com/login/oauth/authorize',
1214  tokenEndpoint: 'https://github.com/login/oauth/access_token',
1215  revocationEndpoint: 'https://github.com/settings/connections/applications/<CLIENT_ID>',
1216};
1217
1218export default function App() {
1219  const [request, response, promptAsync] = useAuthRequest(
1220    {
1221      clientId: 'CLIENT_ID',
1222      scopes: ['identity'],
1223      redirectUri: makeRedirectUri({
1224        /* @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. */
1225        scheme: 'your.app'
1226        /* @end */
1227      }),
1228    },
1229    discovery
1230  );
1231
1232  React.useEffect(() => {
1233    if (response?.type === 'success') {
1234      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
1235      const { code } = response.params;
1236      /* @end */
1237    }
1238  }, [response]);
1239
1240  return (
1241    <Button
1242      /* @info Disable the button until the request is loaded asynchronously. */
1243      disabled={!request}
1244      /* @end */
1245      title="Login"
1246      onPress={() => {
1247        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
1248        promptAsync();
1249        /* @end */
1250      }}
1251    />
1252  );
1253}
1254```
1255
1256</Tab>
1257
1258<Tab>
1259
1260- Implicit grant is [not supported for GitHub](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/).
1261
1262</Tab>
1263</Tabs>
1264</Box>
1265
1266<Box
1267  name="Imgur"
1268  createUrl="https://api.imgur.com/oauth2/addclient"
1269  image={ASSETS.imgur}
1270>
1271
1272| Website                    | Provider  | PKCE      | Auto Discovery |
1273| -------------------------- | --------- | --------- | -------------- |
1274| [Get Your Config][c-imgur] | OAuth 2.0 | Supported | Not Available  |
1275
1276[c-imgur]: https://api.imgur.com/oauth2/addclient
1277
1278- You will need to create a different provider app for each platform (dynamically choosing your `clientId`).
1279- Learn more here: [imgur.com/oauth2](https://api.imgur.com/oauth2)
1280
1281<Tabs tabs={["Auth Code", "Implicit Flow"]}>
1282<Tab>
1283
1284```tsx
1285import * as React from 'react';
1286import * as WebBrowser from 'expo-web-browser';
1287import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
1288import { Button, Platform } from 'react-native';
1289
1290/* @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. */
1291WebBrowser.maybeCompleteAuthSession();
1292/* @end */
1293
1294const discovery = {
1295  authorizationEndpoint: 'https://api.imgur.com/oauth2/authorize',
1296  tokenEndpoint: 'https://api.imgur.com/oauth2/token',
1297};
1298
1299export default function App() {
1300  // Request
1301  const [request, response, promptAsync] = useAuthRequest(
1302    {
1303      clientId: 'CLIENT_ID',
1304      clientSecret: 'CLIENT_SECRET',
1305      redirectUri: makeRedirectUri({
1306        /* @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. */
1307        scheme: 'your.app',
1308        /* @end */
1309      }),
1310      // imgur requires an empty array
1311      scopes: [],
1312    },
1313    discovery
1314  );
1315
1316  React.useEffect(() => {
1317    if (response?.type === 'success') {
1318      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
1319      const { code } = response.params;
1320      /* @end */
1321    }
1322  }, [response]);
1323
1324  return (
1325    <Button
1326      /* @info Disable the button until the request is loaded asynchronously. */
1327      disabled={!request}
1328      /* @end */
1329      title="Login"
1330      onPress={() => {
1331        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
1332        promptAsync();
1333        /* @end */
1334      }}
1335    />
1336  );
1337}
1338```
1339
1340</Tab>
1341
1342<Tab>
1343
1344```tsx
1345import * as React from 'react';
1346import * as WebBrowser from 'expo-web-browser';
1347import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session';
1348import { Button, Platform } from 'react-native';
1349
1350/* @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. */
1351WebBrowser.maybeCompleteAuthSession();
1352/* @end */
1353
1354const discovery = {
1355  authorizationEndpoint: 'https://api.imgur.com/oauth2/authorize',
1356  tokenEndpoint: 'https://api.imgur.com/oauth2/token',
1357};
1358
1359export default function App() {
1360  // Request
1361  const [request, response, promptAsync] = useAuthRequest(
1362    {
1363      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
1364      responseType: ResponseType.Token,
1365      /* @end */
1366      clientId: 'CLIENT_ID',
1367      redirectUri: makeRedirectUri({
1368        /* @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. */
1369        scheme: 'your.app',
1370        /* @end */
1371      }),
1372      scopes: [],
1373    },
1374    discovery
1375  );
1376
1377  React.useEffect(() => {
1378    if (response?.type === 'success') {
1379      /* @info Use this access token to interact with user data on the provider's server. */
1380      const { access_token } = response.params;
1381      /* @end */
1382    }
1383  }, [response]);
1384
1385  return (
1386    <Button
1387      /* @info Disable the button until the request is loaded asynchronously. */
1388      disabled={!request}
1389      /* @end */
1390      title="Login"
1391      onPress={() => {
1392        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
1393        promptAsync();
1394        /* @end */
1395      }}
1396    />
1397  );
1398}
1399```
1400
1401</Tab>
1402</Tabs>
1403</Box>
1404{/* End Imgur */}
1405
1406<Box
1407  name="Keycloak"
1408  image={ASSETS.keycloak}
1409>
1410
1411| Website | Provider | PKCE     | Auto Discovery |
1412| ------- | -------- | -------- | -------------- |
1413| -       | OpenID   | Required | Available      |
1414
1415{/* prettier-ignore */}
1416```tsx Keycloak Example
1417import * as React from 'react';
1418import * as WebBrowser from 'expo-web-browser';
1419import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
1420import { Button, Text, View } from 'react-native';
1421
1422WebBrowser.maybeCompleteAuthSession();
1423
1424export default function App() {
1425  /* @info If the provider supports auto discovery then you can pass an issuer to the `useAutoDiscovery` hook to fetch the discovery document. */
1426  const discovery = useAutoDiscovery('https://YOUR_KEYCLOAK/realms/YOUR_REALM');
1427  /* @end */
1428
1429// Create and load an auth request
1430  const [request, result, promptAsync] = useAuthRequest(
1431    {
1432      clientId: 'YOUR_CLIENT_NAME',
1433      /* @info After a user finishes authenticating, the server will redirect them to this URI. Learn more about <a href="../../guides/linking/">linking here</a>. */
1434      redirectUri: makeRedirectUri({
1435        scheme: 'YOUR_SCHEME'
1436      }),
1437      /* @end */
1438      scopes: ['openid', 'profile'],
1439    },
1440    discovery
1441  );
1442
1443  return (
1444    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
1445      <Button title="Login!" disabled={!request} onPress={() => promptAsync()} />
1446      {result && <Text>{JSON.stringify(result, null, 2)}</Text>}
1447    </View>
1448  );
1449}
1450```
1451
1452</Box>
1453{/* End Keycloak */}
1454
1455<Box
1456  name="Okta"
1457  createUrl="https://developer.okta.com/signup"
1458  image={ASSETS.okta}
1459>
1460
1461| Website                          | Provider | PKCE      | Auto Discovery |
1462| -------------------------------- | -------- | --------- | -------------- |
1463| [Sign-up][c-okta] > Applications | OpenID   | Supported | Available      |
1464
1465[c-okta]: https://developer.okta.com/signup/
1466
1467- You cannot define a custom `redirectUri`, Okta will provide you with one.
1468- You can use the Expo proxy to test this without a native rebuild, just be sure to configure the project as a website.
1469
1470<Tabs tabs={["Auth Code", "Implicit Flow"]}>
1471<Tab>
1472
1473{/* prettier-ignore */}
1474```tsx
1475import * as React from 'react';
1476import * as WebBrowser from 'expo-web-browser';
1477import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
1478import { Button, Platform } from 'react-native';
1479
1480/* @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. */
1481WebBrowser.maybeCompleteAuthSession();
1482/* @end */
1483
1484const useProxy = Platform.select({ web: false, default: true });
1485
1486export default function App() {
1487  // Endpoint
1488  const discovery = useAutoDiscovery('https://<OKTA_DOMAIN>.com/oauth2/default');
1489  // Request
1490  const [request, response, promptAsync] = useAuthRequest(
1491    {
1492      clientId: 'CLIENT_ID',
1493      scopes: ['openid', 'profile'],
1494      // For usage in managed apps using the proxy
1495      redirectUri: makeRedirectUri({
1496        // For usage in bare and standalone
1497        native: 'com.okta.<OKTA_DOMAIN>:/callback',
1498        useProxy,
1499      }),
1500    },
1501    discovery
1502  );
1503
1504  React.useEffect(() => {
1505    if (response?.type === 'success') {
1506      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
1507      const { code } = response.params;
1508      /* @end */
1509    }
1510  }, [response]);
1511
1512  return (
1513    <Button
1514      /* @info Disable the button until the request is loaded asynchronously. */
1515      disabled={!request}
1516      /* @end */
1517      title="Login"
1518      onPress={() => {
1519        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
1520        promptAsync({ useProxy });
1521        /* @end */
1522      }}
1523    />
1524  );
1525}
1526```
1527
1528</Tab>
1529
1530<Tab>
1531
1532- This flow is not documented yet, learn more [from the Okta website](https://developer.okta.com/docs/guides/implement-implicit/use-flow/).
1533
1534</Tab>
1535</Tabs>
1536</Box>
1537{/* End Okta */}
1538
1539<Box
1540  name="Reddit"
1541  createUrl="https://www.reddit.com/prefs/apps"
1542  image={ASSETS.reddit}
1543>
1544
1545| Website                     | Provider  | PKCE      | Auto Discovery |
1546| --------------------------- | --------- | --------- | -------------- |
1547| [Get Your Config][c-reddit] | OAuth 2.0 | Supported | Not Available  |
1548
1549[c-reddit]: https://www.reddit.com/prefs/apps
1550
1551- Provider only allows one redirect URI per app. You'll need an individual app for every method you want to use:
1552  - Expo Go: `exp://localhost:8081/--/*`. For SDK 48 and lower, the port number is `19000`.
1553  - Expo Go + Proxy: `https://auth.expo.io/@you/your-app`
1554  - Standalone or Bare: `com.your.app://*`
1555  - Web: `https://yourwebsite.com/*`
1556- The `redirectUri` requires 2 slashes (`://`).
1557
1558<Tabs tabs={["Auth Code", "Implicit Flow"]}>
1559
1560<Tab>
1561
1562{/* prettier-ignore */}
1563```tsx
1564import * as React from 'react';
1565import * as WebBrowser from 'expo-web-browser';
1566import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
1567import { Button } from 'react-native';
1568
1569/* @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. */
1570WebBrowser.maybeCompleteAuthSession();
1571/* @end */
1572
1573// Endpoint
1574const discovery = {
1575  authorizationEndpoint: 'https://www.reddit.com/api/v1/authorize.compact',
1576  tokenEndpoint: 'https://www.reddit.com/api/v1/access_token',
1577};
1578
1579export default function App() {
1580  const [request, response, promptAsync] = useAuthRequest(
1581    {
1582      clientId: 'CLIENT_ID',
1583      scopes: ['identity'],
1584      redirectUri: makeRedirectUri({
1585        // For usage in bare and standalone
1586        native: 'your.app://redirect',
1587      }),
1588    },
1589    discovery
1590  );
1591
1592  React.useEffect(() => {
1593    if (response?.type === 'success') {
1594      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
1595      const { code } = response.params;
1596      /* @end */
1597    }
1598  }, [response]);
1599
1600  return (
1601    <Button
1602      /* @info Disable the button until the request is loaded asynchronously. */
1603      disabled={!request}
1604      /* @end */
1605      title="Login"
1606      onPress={() => {
1607        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
1608        promptAsync();
1609        /* @end */
1610      }}
1611    />
1612  );
1613}
1614```
1615
1616</Tab>
1617
1618<Tab>
1619
1620- You must select the `installed` option for your app on Reddit to use implicit grant.
1621
1622{/* prettier-ignore */}
1623```tsx
1624import * as React from 'react';
1625import * as WebBrowser from 'expo-web-browser';
1626import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session';
1627import { Button } from 'react-native';
1628
1629/* @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. */
1630WebBrowser.maybeCompleteAuthSession();
1631/* @end */
1632
1633// Endpoint
1634const discovery = {
1635  authorizationEndpoint: 'https://www.reddit.com/api/v1/authorize.compact',
1636  tokenEndpoint: 'https://www.reddit.com/api/v1/access_token',
1637};
1638
1639export default function App() {
1640  const [request, response, promptAsync] = useAuthRequest(
1641    {
1642      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
1643      responseType: ResponseType.Token,
1644      /* @end */
1645      clientId: 'CLIENT_ID',
1646      scopes: ['identity'],
1647      redirectUri: makeRedirectUri({
1648        /* @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. */
1649        scheme: 'your.app'
1650        /* @end */
1651      }),
1652    },
1653    discovery
1654  );
1655
1656  React.useEffect(() => {
1657    if (response?.type === 'success') {
1658      /* @info Use this access token to interact with user data on the provider's server. */
1659      const { access_token } = response.params;
1660      /* @end */
1661    }
1662  }, [response]);
1663
1664  return (
1665    <Button
1666      /* @info Disable the button until the request is loaded asynchronously. */
1667      disabled={!request}
1668      /* @end */
1669      title="Login"
1670      onPress={() => {
1671        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
1672        promptAsync();
1673        /* @end */
1674      }}
1675    />
1676  );
1677}
1678```
1679
1680</Tab>
1681</Tabs>
1682</Box>
1683{/* End Reddit */}
1684
1685<Box
1686  name="Slack"
1687  createUrl="https://api.slack.com/apps"
1688  image={ASSETS.slack}
1689>
1690
1691| Website                    | Provider  | PKCE      | Auto Discovery |
1692| -------------------------- | --------- | --------- | -------------- |
1693| [Get Your Config][c-slack] | OAuth 2.0 | Supported | Not Available  |
1694
1695[c-slack]: https://api.slack.com/apps
1696
1697- The `redirectUri` requires 2 slashes (`://`).
1698- `redirectUri` can be defined under the "OAuth & Permissions" section of the website.
1699- `clientId` and `clientSecret` can be found in the **"App Credentials"** section.
1700- Scopes must be joined with ':' so just create one long string.
1701- Navigate to the **"Scopes"** section to enable scopes.
1702- `revocationEndpoint` is not available.
1703
1704<Tabs tabs={["Auth Code", "Implicit Flow"]}>
1705
1706<Tab>
1707
1708{/* prettier-ignore */}
1709```tsx
1710import * as React from 'react';
1711import * as WebBrowser from 'expo-web-browser';
1712import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
1713import { Button } from 'react-native';
1714
1715/* @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. */
1716WebBrowser.maybeCompleteAuthSession();
1717/* @end */
1718
1719// Endpoint
1720const discovery = {
1721  authorizationEndpoint: 'https://slack.com/oauth/authorize',
1722  tokenEndpoint: 'https://slack.com/api/oauth.access',
1723};
1724
1725export default function App() {
1726  const [request, response, promptAsync] = useAuthRequest(
1727    {
1728      clientId: 'CLIENT_ID',
1729      scopes: ['emoji:read'],
1730      redirectUri: makeRedirectUri({
1731        /* @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. */
1732        scheme: 'your.app'
1733        /* @end */
1734      }),
1735    },
1736    discovery
1737  );
1738
1739  React.useEffect(() => {
1740    if (response?.type === 'success') {
1741      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
1742      const { code } = response.params;
1743      /* @end */
1744    }
1745  }, [response]);
1746
1747  return (
1748    <Button
1749      /* @info Disable the button until the request is loaded asynchronously. */
1750      disabled={!request}
1751      /* @end */
1752      title="Login"
1753      onPress={() => {
1754        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
1755        promptAsync();
1756        /* @end */
1757      }}
1758    />
1759  );
1760}
1761```
1762
1763</Tab>
1764
1765<Tab>
1766
1767- Slack does not support implicit grant.
1768
1769</Tab>
1770</Tabs>
1771</Box>
1772{/* End Slack */}
1773
1774<Box
1775  name="Spotify"
1776  createUrl="https://developer.spotify.com/dashboard/applications"
1777  image={ASSETS.spotify}
1778>
1779
1780| Website                      | Provider  | PKCE      | Auto Discovery |
1781| ---------------------------- | --------- | --------- | -------------- |
1782| [Get Your Config][c-spotify] | OAuth 2.0 | Supported | Not Available  |
1783
1784[c-spotify]: https://developer.spotify.com/dashboard/applications
1785
1786- Setup your redirect URIs: Your project > Edit Settings > Redirect URIs (be sure to save after making changes).
1787  - _Expo Go_: `exp://localhost:8081/--/`. For SDK 48 and lower, the port number is `19000`.
1788  - _Web dev_: `https://localhost:19006`
1789    - Important: Ensure there's no slash at the end of the URL unless manually changed in the app code with `makeRedirectUri({ path: '/' })`.
1790    - Run `expo start --web --https` to run with **https**, auth won't work otherwise.
1791  - _Custom app_: `your-scheme://`
1792    - Scheme should be specified in app.json `expo.scheme: 'your-scheme'`, then added to the app code with `makeRedirectUri({ native: 'your-scheme://' })`)
1793  - _Proxy_: `https://auth.expo.io/@username/slug`
1794    - 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.
1795  - _Web production_: `https://yourwebsite.com`
1796    - Set this to whatever your deployed website URL is.
1797- For simpler authentication, use the **Implicit Flow** as it'll return an `access_token` without the need for a code exchange server request.
1798- Learn more about the [Spotify API](https://developer.spotify.com/documentation/web-api/).
1799
1800<Tabs tabs={["Auth Code", "Implicit Flow"]}>
1801<Tab>
1802
1803{/* prettier-ignore */}
1804```tsx
1805import * as React from 'react';
1806import * as WebBrowser from 'expo-web-browser';
1807import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
1808import { Button } from 'react-native';
1809
1810/* @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. */
1811WebBrowser.maybeCompleteAuthSession();
1812/* @end */
1813
1814// Endpoint
1815const discovery = {
1816  authorizationEndpoint: 'https://accounts.spotify.com/authorize',
1817  tokenEndpoint: 'https://accounts.spotify.com/api/token',
1818};
1819
1820export default function App() {
1821  const [request, response, promptAsync] = useAuthRequest(
1822    {
1823      clientId: 'CLIENT_ID',
1824      scopes: ['user-read-email', 'playlist-modify-public'],
1825      // To follow the "Authorization Code Flow" to fetch token after authorizationEndpoint
1826      // this must be set to false
1827      usePKCE: false,
1828      redirectUri: makeRedirectUri({
1829        /* @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. */
1830        scheme: 'your.app'
1831        /* @end */
1832      }),
1833    },
1834    discovery
1835  );
1836
1837  React.useEffect(() => {
1838    if (response?.type === 'success') {
1839      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
1840      const { code } = response.params;
1841      /* @end */
1842    }
1843  }, [response]);
1844
1845  return (
1846    <Button
1847      /* @info Disable the button until the request is loaded asynchronously. */
1848      disabled={!request}
1849      /* @end */
1850      title="Login"
1851      onPress={() => {
1852        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
1853        promptAsync();
1854        /* @end */
1855      }}
1856    />
1857  );
1858}
1859```
1860
1861</Tab>
1862
1863<Tab>
1864
1865{/* prettier-ignore */}
1866```tsx
1867import * as React from 'react';
1868import * as WebBrowser from 'expo-web-browser';
1869import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session';
1870import { Button } from 'react-native';
1871
1872/* @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. */
1873WebBrowser.maybeCompleteAuthSession();
1874/* @end */
1875
1876// Endpoint
1877const discovery = {
1878  authorizationEndpoint: 'https://accounts.spotify.com/authorize',
1879  tokenEndpoint: 'https://accounts.spotify.com/api/token',
1880};
1881
1882export default function App() {
1883  const [request, response, promptAsync] = useAuthRequest(
1884    {
1885      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
1886      responseType: ResponseType.Token,
1887      /* @end */
1888      clientId: 'CLIENT_ID',
1889      scopes: ['user-read-email', 'playlist-modify-public'],
1890      // To follow the "Authorization Code Flow" to fetch token after authorizationEndpoint
1891      // this must be set to false
1892      usePKCE: false,
1893      redirectUri: makeRedirectUri({
1894        /* @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. */
1895        scheme: 'your.app'
1896        /* @end */
1897      }),
1898    },
1899    discovery
1900  );
1901
1902  React.useEffect(() => {
1903    if (response?.type === 'success') {
1904      /* @info Use this access token to interact with user data on the provider's server. */
1905      const { access_token } = response.params;
1906      /* @end */
1907    }
1908  }, [response]);
1909
1910  return (
1911    <Button
1912      /* @info Disable the button until the request is loaded asynchronously. */
1913      disabled={!request}
1914      /* @end */
1915      title="Login"
1916      onPress={() => {
1917        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
1918        promptAsync();
1919        /* @end */
1920      }}
1921    />
1922  );
1923}
1924```
1925
1926</Tab>
1927</Tabs>
1928</Box>
1929{/* End Spotify */}
1930
1931<Box
1932  name="Strava"
1933  createUrl="https://www.strava.com/settings/api"
1934  image={ASSETS.strava}
1935>
1936
1937| Website                     | Provider  | PKCE      | Auto Discovery |
1938| --------------------------- | --------- | --------- | -------------- |
1939| [Get Your Config][c-strava] | OAuth 2.0 | Supported | Not Available  |
1940
1941[c-strava]: https://www.strava.com/settings/api
1942
1943- Learn more about the [Strava API](http://developers.strava.com/docs/reference/).
1944- 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`.
1945- No Implicit auth flow is provided by Strava.
1946
1947<Tabs tabs={["Auth Code"]}>
1948<Tab>
1949
1950{/* prettier-ignore */}
1951```tsx
1952import * as React from 'react';
1953import * as WebBrowser from 'expo-web-browser';
1954import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
1955import { Button } from 'react-native';
1956
1957/* @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. */
1958WebBrowser.maybeCompleteAuthSession();
1959/* @end */
1960
1961// Endpoint
1962const discovery = {
1963  authorizationEndpoint: 'https://www.strava.com/oauth/mobile/authorize',
1964  tokenEndpoint: 'https://www.strava.com/oauth/token',
1965  revocationEndpoint: 'https://www.strava.com/oauth/deauthorize',
1966};
1967
1968export default function App() {
1969  const [request, response, promptAsync] = useAuthRequest(
1970    {
1971      clientId: 'CLIENT_ID',
1972      scopes: ['activity:read_all'],
1973      redirectUri: makeRedirectUri({
1974        // For usage in bare and standalone
1975        // the "redirect" must match your "Authorization Callback Domain" in the Strava dev console.
1976        native: 'your.app://redirect',
1977      }),
1978    },
1979    discovery
1980  );
1981
1982  React.useEffect(() => {
1983    if (response?.type === 'success') {
1984      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
1985      const { code } = response.params;
1986      /* @end */
1987    }
1988  }, [response]);
1989
1990  return (
1991    <Button
1992      /* @info Disable the button until the request is loaded asynchronously. */
1993      disabled={!request}
1994      /* @end */
1995      title="Login"
1996      onPress={() => {
1997        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
1998        promptAsync();
1999        /* @end */
2000      }}
2001    />
2002  );
2003}
2004```
2005
2006Strava doesn't provide an implicit auth flow, you should send the code to a server or serverless function to perform the access token exchange.
2007For **debugging** purposes, you can perform the exchange client-side using the following method:
2008
2009{/* prettier-ignore */}
2010```tsx
2011const { accessToken } = await AuthSession.exchangeCodeAsync(
2012  {
2013    clientId: request?.clientId,
2014    redirectUri,
2015    code: result.params.code,
2016    extraParams: {
2017      // You must use the extraParams variation of clientSecret.
2018      // Never store your client secret on the client.
2019      client_secret: 'CLIENT_SECRET',
2020    },
2021  },
2022  { tokenEndpoint: 'https://www.strava.com/oauth/token' }
2023);
2024```
2025
2026</Tab>
2027</Tabs>
2028</Box>
2029{/* End Strava */}
2030
2031<Box
2032  name="Twitch"
2033  createUrl="https://dev.twitch.tv/console/apps/create"
2034  image={ASSETS.twitch}
2035>
2036
2037| Website                     | Provider | PKCE      | Auto Discovery | Scopes           |
2038| --------------------------- | -------- | --------- | -------------- | ---------------- |
2039| [Get your Config][c-twitch] | OAuth    | Supported | Not Available  | [Info][s-twitch] |
2040
2041[c-twitch]: https://dev.twitch.tv/console/apps/create
2042[s-twitch]: https://dev.twitch.tv/docs/authentication#scopes
2043
2044- You will need to enable 2FA on your Twitch account to create an application.
2045
2046<Tabs tabs={["Auth Code", "Implicit Flow"]}>
2047
2048<Tab>
2049
2050{/* prettier-ignore */}
2051```tsx
2052import * as React from 'react';
2053import * as WebBrowser from 'expo-web-browser';
2054import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
2055import { Button } from 'react-native';
2056
2057/* @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. */
2058WebBrowser.maybeCompleteAuthSession();
2059/* @end */
2060
2061// Endpoint
2062const discovery = {
2063  authorizationEndpoint: 'https://id.twitch.tv/oauth2/authorize',
2064  tokenEndpoint: 'https://id.twitch.tv/oauth2/token',
2065  revocationEndpoint: 'https://id.twitch.tv/oauth2/revoke',
2066};
2067
2068export default function App() {
2069  const [request, response, promptAsync] = useAuthRequest(
2070    {
2071      clientId: 'CLIENT_ID',
2072      redirectUri: makeRedirectUri({
2073        /* @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. */
2074        scheme: 'your.app'
2075        /* @end */
2076      }),
2077      scopes: ['user:read:email', 'analytics:read:games'],
2078    },
2079    discovery
2080  );
2081
2082  React.useEffect(() => {
2083    if (response?.type === 'success') {
2084      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
2085      const { code } = response.params;
2086      /* @end */
2087    }
2088  }, [response]);
2089
2090  return (
2091    <Button
2092      /* @info Disable the button until the request is loaded asynchronously. */
2093      disabled={!request}
2094      /* @end */
2095      title="Login"
2096      onPress={() => {
2097        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
2098        promptAsync();
2099        /* @end */
2100      }}
2101    />
2102  );
2103}
2104```
2105
2106</Tab>
2107
2108<Tab>
2109
2110{/* prettier-ignore */}
2111```tsx
2112import * as React from 'react';
2113import * as WebBrowser from 'expo-web-browser';
2114import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session';
2115import { Button } from 'react-native';
2116
2117/* @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. */
2118WebBrowser.maybeCompleteAuthSession();
2119/* @end */
2120
2121// Endpoint
2122const discovery = {
2123  authorizationEndpoint: 'https://id.twitch.tv/oauth2/authorize',
2124  tokenEndpoint: 'https://id.twitch.tv/oauth2/token',
2125  revocationEndpoint: 'https://id.twitch.tv/oauth2/revoke',
2126};
2127
2128export default function App() {
2129  const [request, response, promptAsync] = useAuthRequest(
2130    {
2131      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
2132      responseType: ResponseType.Token,
2133      /* @end */
2134      clientId: 'CLIENT_ID',
2135      redirectUri: makeRedirectUri({
2136        /* @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. */
2137        scheme: 'your.app'
2138        /* @end */
2139      }),
2140      scopes: ['user:read:email', 'analytics:read:games'],
2141    },
2142    discovery
2143  );
2144
2145  React.useEffect(() => {
2146    if (response?.type === 'success') {
2147      /* @info Use this access token to interact with user data on the provider's server. */
2148      const { access_token } = response.params;
2149      /* @end */
2150    }
2151  }, [response]);
2152
2153  return (
2154    <Button
2155      /* @info Disable the button until the request is loaded asynchronously. */
2156      disabled={!request}
2157      /* @end */
2158      title="Login"
2159      onPress={() => {
2160        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
2161        promptAsync();
2162        /* @end */
2163      }}
2164    />
2165  );
2166}
2167```
2168
2169</Tab>
2170</Tabs>
2171</Box>
2172{/* End Twitch */}
2173
2174<Box
2175  name="Twitter"
2176  createUrl="https://developer.twitter.com/en/portal/projects/new"
2177  image={ASSETS.twitter}
2178>
2179
2180| Website                      | Provider | PKCE      | Auto Discovery | Scopes            |
2181| ---------------------------- | -------- | --------- | -------------- | ----------------- |
2182| [Get your Config][c-twitter] | OAuth    | Supported | Not Available  | [Info][s-twitter] |
2183
2184[c-twitter]: https://developer.twitter.com/en/portal/projects/new
2185[s-twitter]: https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code
2186
2187- You will need to be approved by Twitter support before you can use the Twitter v2 API.
2188- 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'}`.
2189- Example redirects:
2190  - Expo Go + Proxy: `https://auth.expo.io/@you/your-app`
2191  - Standalone or Bare: `com.your.app://`
2192  - Web (dev `expo start --https`): `https://localhost:19006` (no ending slash)
2193- The `redirectUri` requires 2 slashes (`://`).
2194
2195#### Expo Go
2196
2197You must use the proxy service in the Expo Go app because `exp://localhost:8081` cannot be added to your Twitter app as a redirect.
2198
2199<Tabs tabs={["Auth Code"]}>
2200
2201<Tab>
2202
2203{/* prettier-ignore */}
2204```tsx
2205import * as React from 'react';
2206import * as WebBrowser from 'expo-web-browser';
2207import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
2208import { Button, Platform } from 'react-native';
2209
2210const useProxy = Platform.select({ web: false, default: true });
2211
2212/* @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. */
2213WebBrowser.maybeCompleteAuthSession();
2214/* @end */
2215
2216// Endpoint
2217const discovery = {
2218  authorizationEndpoint: "https://twitter.com/i/oauth2/authorize",
2219  tokenEndpoint: "https://twitter.com/i/oauth2/token",
2220  revocationEndpoint: "https://twitter.com/i/oauth2/revoke",
2221};
2222
2223export default function App() {
2224  const [request, response, promptAsync] = useAuthRequest(
2225    {
2226      clientId: 'CLIENT_ID',
2227      redirectUri: makeRedirectUri({
2228        /* @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. */
2229        scheme: 'your.app',
2230        /* @end */
2231        useProxy
2232      }),
2233      usePKCE: true,
2234      scopes: [
2235        "tweet.read",
2236      ],
2237    },
2238    discovery
2239  );
2240
2241  React.useEffect(() => {
2242    if (response?.type === 'success') {
2243      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
2244      const { code } = response.params;
2245      /* @end */
2246    }
2247  }, [response]);
2248
2249  return (
2250    <Button
2251      /* @info Disable the button until the request is loaded asynchronously. */
2252      disabled={!request}
2253      /* @end */
2254      title="Login"
2255      onPress={() => {
2256        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
2257        promptAsync({ useProxy });
2258        /* @end */
2259      }}
2260    />
2261  );
2262}
2263```
2264
2265</Tab>
2266</Tabs>
2267</Box>
2268{/* End Twitter */}
2269
2270<Box
2271  name="Uber"
2272  createUrl="https://developer.uber.com/docs/riders/guides/authentication/introduction"
2273  image={ASSETS.uber}
2274>
2275
2276| Website                   | Provider  | PKCE      | Auto Discovery |
2277| ------------------------- | --------- | --------- | -------------- |
2278| [Get Your Config][c-uber] | OAuth 2.0 | Supported | Not Available  |
2279
2280[c-uber]: https://developer.uber.com/docs/riders/guides/authentication/introduction
2281
2282- The `redirectUri` requires 2 slashes (`://`).
2283- `scopes` can be difficult to get approved.
2284
2285<Tabs tabs={["Auth Code", "Implicit Flow"]}>
2286
2287<Tab>
2288
2289{/* prettier-ignore */}
2290```tsx
2291import * as React from 'react';
2292import * as WebBrowser from 'expo-web-browser';
2293import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
2294import { Button } from 'react-native';
2295
2296/* @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. */
2297WebBrowser.maybeCompleteAuthSession();
2298/* @end */
2299
2300// Endpoint
2301const discovery = {
2302  authorizationEndpoint: 'https://login.uber.com/oauth/v2/authorize',
2303  tokenEndpoint: 'https://login.uber.com/oauth/v2/token',
2304  revocationEndpoint: 'https://login.uber.com/oauth/v2/revoke',
2305};
2306
2307export default function App() {
2308  const [request, response, promptAsync] = useAuthRequest(
2309    {
2310      clientId: 'CLIENT_ID',
2311      scopes: ['profile', 'delivery'],
2312      redirectUri: makeRedirectUri({
2313        /* @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. */
2314        scheme: 'your.app'
2315        /* @end */
2316      }),
2317    },
2318    discovery
2319  );
2320
2321  React.useEffect(() => {
2322    if (response?.type === 'success') {
2323      /* @info Exchange the code for an access token in a server. Alternatively you can use the <b>Implicit</b> auth method. */
2324      const { code } = response.params;
2325      /* @end */
2326    }
2327  }, [response]);
2328
2329  return (
2330    <Button
2331      /* @info Disable the button until the request is loaded asynchronously. */
2332      disabled={!request}
2333      /* @end */
2334      title="Login"
2335      onPress={() => {
2336        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
2337        promptAsync();
2338        /* @end */
2339      }}
2340    />
2341  );
2342}
2343```
2344
2345</Tab>
2346
2347<Tab>
2348
2349{/* prettier-ignore */}
2350```tsx
2351import * as React from 'react';
2352import * as WebBrowser from 'expo-web-browser';
2353import { makeRedirectUri, ResponseType, useAuthRequest } from 'expo-auth-session';
2354import { Button } from 'react-native';
2355
2356/* @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. */
2357WebBrowser.maybeCompleteAuthSession();
2358/* @end */
2359
2360// Endpoint
2361const discovery = {
2362  authorizationEndpoint: 'https://login.uber.com/oauth/v2/authorize',
2363  tokenEndpoint: 'https://login.uber.com/oauth/v2/token',
2364  revocationEndpoint: 'https://login.uber.com/oauth/v2/revoke',
2365};
2366
2367export default function App() {
2368  const [request, response, promptAsync] = useAuthRequest(
2369    {
2370      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
2371      responseType: ResponseType.Token,
2372      /* @end */
2373      clientId: 'CLIENT_ID',
2374      scopes: ['profile', 'delivery'],
2375      redirectUri: makeRedirectUri({
2376        /* @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. */
2377        scheme: 'your.app'
2378        /* @end */
2379      }),
2380    },
2381    discovery
2382  );
2383
2384  React.useEffect(() => {
2385    if (response?.type === 'success') {
2386      /* @info Use this access token to interact with user data on the provider's server. */
2387      const { access_token } = response.params;
2388      /* @end */
2389    }
2390  }, [response]);
2391
2392  return (
2393    <Button
2394      /* @info Disable the button until the request is loaded asynchronously. */
2395      disabled={!request}
2396      /* @end */
2397      title="Login"
2398      onPress={() => {
2399        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
2400        promptAsync();
2401        /* @end */
2402      }}
2403    />
2404  );
2405}
2406```
2407
2408</Tab>
2409</Tabs>
2410</Box>
2411{/* End Uber */}
2412
2413{/* End Guides */}
2414
2415## Redirect URI patterns
2416
2417Here are a few examples of some common redirect URI patterns you may end up using.
2418
2419### Standalone and bare
2420
2421> `yourscheme://path`
2422
2423In some cases there will be anywhere between 1 to 3 slashes (`/`).
2424
2425- **Environment:**
2426
2427  - Bare workflow
2428    - `npx expo prebuild`
2429  - Standalone builds in the App or Play Store or testing locally
2430    - Android: `eas build` or `npx expo run:android`
2431    - iOS: `eas build` or `npx expo run:ios`
2432
2433- **Create:** Use `AuthSession.makeRedirectUri({ native: '<YOUR_URI>' })` to select native when running in the correct environment.
2434  - `your.app://redirect` -> `makeRedirectUri({ scheme: 'your.app', path: 'redirect' })`
2435  - `your.app:///` -> `makeRedirectUri({ scheme: 'your.app', isTripleSlashed: true })`
2436  - `your.app:/authorize` -> `makeRedirectUri({ native: 'your.app:/authorize' })`
2437  - `your.app://auth?foo=bar` -> `makeRedirectUri({ scheme: 'your.app', path: 'auth', queryParams: { foo: 'bar' } })`
2438  - `exp://u.expo.dev/[project-id]?channel-name=[channel-name]&runtime-version=[runtime-version]` -> `makeRedirectUri()`
2439  - 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`.
2440  - 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`).
2441- **Usage:** `promptAsync({ redirectUri })`
2442
2443## Improving user experience
2444
2445The "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.
2446
2447Here are a few tips you can use to make authentication quick, easy, and secure for your users!
2448
2449### Warming the browser
2450
2451On 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.
2452
2453{/* prettier-ignore */}
2454```tsx
2455import * as React from 'react';
2456import * as WebBrowser from 'expo-web-browser';
2457
2458function App() {
2459  React.useEffect(() => {
2460    /* @info <strong>Android only:</strong> Start loading the default browser app in the background to improve transition time. */
2461    WebBrowser.warmUpAsync();
2462    /* @end */
2463
2464    return () => {
2465      /* @info <strong>Android only:</strong> Cool down the browser when the component unmounts to help improve memory on low-end Android devices. */
2466      WebBrowser.coolDownAsync();
2467      /* @end */
2468    };
2469  }, []);
2470
2471  // Do authentication ...
2472}
2473```
2474
2475### Implicit login
2476
2477You 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.
2478
2479Here is an example of logging into Spotify without using a client secret.
2480
2481{/* prettier-ignore */}
2482```tsx
2483import * as React from 'react';
2484import * as WebBrowser from 'expo-web-browser';
2485import { makeRedirectUri, useAuthRequest, ResponseType } from 'expo-auth-session';
2486
2487/* @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. */
2488WebBrowser.maybeCompleteAuthSession();
2489/* @end */
2490
2491// Endpoint
2492const discovery = {
2493  authorizationEndpoint: 'https://accounts.spotify.com/authorize',
2494};
2495
2496function App() {
2497  const [request, response, promptAsync] = useAuthRequest(
2498    {
2499      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
2500      responseType: ResponseType.Token,
2501      /* @end */
2502      clientId: 'CLIENT_ID',
2503      scopes: ['user-read-email', 'playlist-modify-public'],
2504      redirectUri: makeRedirectUri({
2505        /* @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. */
2506        scheme: 'your.app'
2507        /* @end */
2508      }),
2509    },
2510    discovery
2511  );
2512
2513  React.useEffect(() => {
2514    if (response && response.type === 'success') {
2515      /* @info You can use this access token to make calls into the Spotify API. */
2516      const token = response.params.access_token;
2517      /* @end */
2518    }
2519  }, [response]);
2520
2521  return <Button disabled={!request} onPress={() => promptAsync()} title="Login" />;
2522}
2523```
2524
2525### Storing data
2526
2527On 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.
2528
2529You can store your authentication results and rehydrate them later to avoid having to prompt the user to login again.
2530
2531{/* prettier-ignore */}
2532```tsx
2533import * as SecureStore from 'expo-secure-store';
2534
2535const MY_SECURE_AUTH_STATE_KEY = 'MySecureAuthStateKey';
2536
2537function App() {
2538  const [, response] = useAuthRequest({});
2539
2540  React.useEffect(() => {
2541    if (response && response.type === 'success') {
2542      const auth = response.params;
2543      const storageValue = JSON.stringify(auth);
2544
2545      if (Platform.OS !== 'web') {
2546        // Securely store the auth on your device
2547        SecureStore.setItemAsync(MY_SECURE_AUTH_STATE_KEY, storageValue);
2548      }
2549    }
2550  }, [response]);
2551
2552  // More login code...
2553}
2554```
2555
2556[userinfo]: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
2557[provider-meta]: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
2558[oidc-dcr]: https://openid.net/specs/openid-connect-discovery-1_0.html#OpenID.Registration
2559[oidc-autherr]: https://openid.net/specs/openid-connect-core-1_0.html#AuthError
2560[oidc-authreq]: https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationRequest
2561