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