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