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