1import { H2 } from '@expo/html-elements'; 2import * as AuthSession from 'expo-auth-session'; 3import { useAuthRequest } from 'expo-auth-session'; 4import Constants from 'expo-constants'; 5import { maybeCompleteAuthSession } from 'expo-web-browser'; 6import React from 'react'; 7import { Platform, ScrollView, View } from 'react-native'; 8 9import { getGUID } from '../../api/guid'; 10import TitledSwitch from '../../components/TitledSwitch'; 11import { AuthSection } from './AuthResult'; 12import LegacyAuthSession from './LegacyAuthSession'; 13 14maybeCompleteAuthSession(); 15 16const isInClient = Platform.OS !== 'web' && Constants.appOwnership === 'expo'; 17 18export default function AuthSessionScreen() { 19 const [useProxy, setProxy] = React.useState<boolean>(false); 20 const [usePKCE, setPKCE] = React.useState<boolean>(true); 21 const [prompt, setSwitch] = React.useState<undefined | AuthSession.Prompt>(undefined); 22 23 return ( 24 <View style={{ flex: 1, alignItems: 'center' }}> 25 <ScrollView 26 contentContainerStyle={{ 27 maxWidth: 640, 28 paddingHorizontal: 12, 29 }}> 30 <View style={{ marginBottom: 8 }}> 31 <H2>Settings</H2> 32 <TitledSwitch 33 disabled={!isInClient} 34 title="Use Proxy" 35 value={useProxy} 36 setValue={setProxy} 37 /> 38 <TitledSwitch 39 title="Switch Accounts" 40 value={!!prompt} 41 setValue={value => setSwitch(value ? AuthSession.Prompt.SelectAccount : undefined)} 42 /> 43 <TitledSwitch title="Use PKCE" value={usePKCE} setValue={setPKCE} /> 44 </View> 45 <H2>Services</H2> 46 <AuthSessionProviders prompt={prompt} usePKCE={usePKCE} useProxy={useProxy} /> 47 <H2>Legacy</H2> 48 <LegacyAuthSession /> 49 </ScrollView> 50 </View> 51 ); 52} 53 54AuthSessionScreen.navigationOptions = { 55 title: 'AuthSession', 56}; 57 58function AuthSessionProviders(props: { 59 useProxy: boolean; 60 usePKCE: boolean; 61 prompt?: AuthSession.Prompt; 62}) { 63 const { useProxy, usePKCE, prompt } = props; 64 65 const redirectUri = AuthSession.makeRedirectUri({ 66 native: 'bareexpo://redirect', 67 path: 'redirect', 68 preferLocalhost: true, 69 useProxy, 70 }); 71 const options = { 72 useProxy, 73 usePKCE, 74 prompt, 75 redirectUri, 76 }; 77 78 const providers = [ 79 Facebook, 80 Spotify, 81 Twitch, 82 Dropbox, 83 Google, 84 Reddit, 85 Github, 86 Coinbase, 87 Uber, 88 Slack, 89 FitBit, 90 Okta, 91 Identity, 92 // Azure, 93 ]; 94 return ( 95 <View style={{ flex: 1 }}> 96 {providers.map((Provider, index) => ( 97 <Provider key={`-${index}`} {...options} /> 98 ))} 99 </View> 100 ); 101} 102 103function Google({ useProxy, prompt, usePKCE }: any) { 104 const redirectUri = AuthSession.makeRedirectUri({ 105 path: 'redirect', 106 preferLocalhost: true, 107 useProxy, 108 native: `com.googleusercontent.apps.${getGUID()}:/oauthredirect`, 109 }); 110 const discovery = AuthSession.useAutoDiscovery('https://accounts.google.com'); 111 112 const [request, result, promptAsync] = useAuthRequest( 113 { 114 clientId: useProxy 115 ? '29635966244-bc5tjrdacdaktqorhinsbtda80tchl7n.apps.googleusercontent.com' 116 : getGUID(), 117 redirectUri, 118 prompt, 119 scopes: ['profile', 'email', 'openid'], 120 usePKCE, 121 }, 122 discovery 123 ); 124 125 return ( 126 <AuthSection 127 disabled={!useProxy && isInClient} 128 request={request} 129 title="google" 130 result={result} 131 promptAsync={() => promptAsync({ useProxy, windowFeatures: { width: 515, height: 680 } })} 132 useProxy={useProxy} 133 /> 134 ); 135} 136 137// Couldn't get this working. API is really confusing. 138function Azure({ useProxy, prompt, usePKCE }: any) { 139 const redirectUri = AuthSession.makeRedirectUri({ 140 path: 'redirect', 141 preferLocalhost: true, 142 useProxy, 143 native: Platform.select<string>({ 144 ios: 'msauth.dev.expo.Payments://auth', 145 android: 'msauth://dev.expo.payments/sZs4aocytGUGvP1%2BgFAavaPMPN0%3D', 146 }), 147 }); 148 149 // 'https://login.microsoftonline.com/your-tenant-id/v2.0', 150 const discovery = AuthSession.useAutoDiscovery( 151 'https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0' 152 ); 153 const [request, result, promptAsync] = useAuthRequest( 154 // config 155 { 156 clientId: '96891596-721b-4ae1-8e67-674809373165', 157 redirectUri, 158 prompt, 159 extraParams: { 160 domain_hint: 'live.com', 161 }, 162 // redirectUri: 'msauth.{bundleId}://auth', 163 scopes: ['openid', 'profile', 'email', 'offline_access'], 164 usePKCE, 165 }, 166 // discovery 167 discovery 168 ); 169 170 return ( 171 <AuthSection 172 title="azure" 173 disabled={isInClient} 174 request={request} 175 result={result} 176 promptAsync={promptAsync} 177 useProxy={useProxy} 178 /> 179 ); 180} 181 182function Okta({ redirectUri, usePKCE, useProxy }: any) { 183 const discovery = AuthSession.useAutoDiscovery('https://dev-720924.okta.com/oauth2/default'); 184 const [request, result, promptAsync] = useAuthRequest( 185 { 186 clientId: '0oa4su9fhp4F2F4Eg4x6', 187 redirectUri, 188 scopes: ['openid', 'profile'], 189 usePKCE, 190 }, 191 discovery 192 ); 193 194 return ( 195 <AuthSection 196 title="okta" 197 request={request} 198 result={result} 199 promptAsync={promptAsync} 200 useProxy={useProxy} 201 /> 202 ); 203} 204 205// Reddit only allows one redirect uri per client Id 206// We'll only support bare, and proxy in this example 207// If the redirect is invalid with http instead of https on web, then the provider 208// will let you authenticate but it will redirect with no data and the page will appear broken. 209function Reddit({ redirectUri, prompt, usePKCE, useProxy }: any) { 210 let clientId: string; 211 212 if (isInClient) { 213 if (useProxy) { 214 // Using the proxy in the client. 215 // This expects the URI to be 'https://auth.expo.io/@community/native-component-list' 216 // so you'll need to be signed into community or be using the public demo 217 clientId = 'IlgcZIpcXF1eKw'; 218 } else { 219 // // Normalize the host to `localhost` for other testers 220 clientId = 'CPc_adCUQGt9TA'; 221 } 222 } else { 223 if (Platform.OS === 'web') { 224 // web apps with uri scheme `https://localhost:19006` 225 clientId = '9k_oYNO97ly-5w'; 226 } else { 227 // Native bare apps with uri scheme `bareexpo` 228 clientId = '2OFsAA7h63LQJQ'; 229 } 230 } 231 232 const [request, result, promptAsync] = useAuthRequest( 233 { 234 clientId, 235 clientSecret: '', 236 redirectUri, 237 prompt, 238 scopes: ['identity'], 239 usePKCE, 240 }, 241 { 242 authorizationEndpoint: 'https://www.reddit.com/api/v1/authorize.compact', 243 tokenEndpoint: 'https://www.reddit.com/api/v1/access_token', 244 } 245 ); 246 247 return ( 248 <AuthSection 249 title="reddit" 250 request={request} 251 result={result} 252 promptAsync={promptAsync} 253 useProxy={useProxy} 254 /> 255 ); 256} 257 258// TODO: Add button to test using an invalid redirect URI. This is a good example of AuthError. 259// Works for all platforms 260function Github({ redirectUri, prompt, usePKCE, useProxy }: any) { 261 let clientId: string; 262 263 if (isInClient) { 264 if (useProxy) { 265 // Using the proxy in the client. 266 clientId = '2e4298cafc7bc93ceab8'; 267 } else { 268 clientId = '7eb5d82d8f160a434564'; 269 } 270 } else { 271 if (Platform.OS === 'web') { 272 // web apps 273 clientId = 'fd9b07204f9d325e8f0e'; 274 } else { 275 // Native bare apps with uri scheme `bareexpo` 276 clientId = '498f1fae3ae16f066f34'; 277 } 278 } 279 280 const [request, result, promptAsync] = useAuthRequest( 281 { 282 clientId, 283 redirectUri, 284 scopes: ['identity'], 285 usePKCE, 286 prompt, 287 }, 288 // discovery 289 { 290 authorizationEndpoint: 'https://github.com/login/oauth/authorize', 291 tokenEndpoint: 'https://github.com/login/oauth/access_token', 292 revocationEndpoint: 293 'https://github.com/settings/connections/applications/d529db5d7d81c2d50adf', 294 } 295 ); 296 297 return ( 298 <AuthSection 299 title="github" 300 request={request} 301 result={result} 302 promptAsync={() => promptAsync({ useProxy, windowFeatures: { width: 500, height: 750 } })} 303 useProxy={useProxy} 304 /> 305 ); 306} 307 308// I couldn't get access to any scopes 309// This never returns to the app after authenticating 310function Uber({ redirectUri, prompt, usePKCE, useProxy }: any) { 311 // https://developer.uber.com/docs/riders/guides/authentication/introduction 312 const [request, result, promptAsync] = useAuthRequest( 313 { 314 clientId: 'kTpT4xf8afVxifoWjx5Nhn-IFamZKp2x', 315 redirectUri, 316 scopes: [], 317 usePKCE, 318 prompt, 319 // Enable to test invalid_scope error 320 // scopes: ['invalid'], 321 }, 322 // discovery 323 { 324 authorizationEndpoint: 'https://login.uber.com/oauth/v2/authorize', 325 tokenEndpoint: 'https://login.uber.com/oauth/v2/token', 326 revocationEndpoint: 'https://login.uber.com/oauth/v2/revoke', 327 } 328 ); 329 330 return ( 331 <AuthSection 332 title="uber" 333 request={request} 334 result={result} 335 promptAsync={promptAsync} 336 useProxy={useProxy} 337 /> 338 ); 339} 340 341// https://dev.fitbit.com/apps/new 342// Easy to setup 343// Only allows one redirect URI per app (clientId) 344// Refresh doesn't seem to return a new access token :[ 345function FitBit({ redirectUri, prompt, usePKCE, useProxy }: any) { 346 let clientId: string; 347 348 if (isInClient) { 349 if (useProxy) { 350 // Using the proxy in the client. 351 clientId = '22BNXR'; 352 } else { 353 // Client without proxy 354 clientId = '22BNXX'; 355 } 356 } else { 357 if (Platform.OS === 'web') { 358 // web apps with uri scheme `https://localhost:19006` 359 clientId = '22BNXQ'; 360 } else { 361 // Native bare apps with uri scheme `bareexpo` 362 clientId = '22BGYS'; 363 } 364 } 365 366 const [request, result, promptAsync] = useAuthRequest( 367 { 368 clientId, 369 redirectUri, 370 scopes: ['activity', 'sleep'], 371 prompt, 372 usePKCE, 373 }, 374 // discovery 375 { 376 authorizationEndpoint: 'https://www.fitbit.com/oauth2/authorize', 377 tokenEndpoint: 'https://api.fitbit.com/oauth2/token', 378 revocationEndpoint: 'https://api.fitbit.com/oauth2/revoke', 379 } 380 ); 381 382 return ( 383 <AuthSection 384 title="fitbit" 385 request={request} 386 result={result} 387 promptAsync={promptAsync} 388 useProxy={useProxy} 389 /> 390 ); 391} 392 393function Facebook({ usePKCE, prompt, useProxy }: any) { 394 const redirectUri = AuthSession.makeRedirectUri({ 395 path: 'redirect', 396 preferLocalhost: true, 397 useProxy, 398 native: `fb145668956753819://authorize`, 399 }); 400 401 const [request, result, promptAsync] = useAuthRequest( 402 { 403 clientId: '145668956753819', 404 redirectUri, 405 scopes: ['public_profile', 'user_likes'], 406 usePKCE, 407 prompt, 408 extraParams: { 409 display: 'popup', 410 // Rerequest decliened permissions, to test this, 411 // add "email" to the scopes and try again (be sure not to allow email permission). 412 auth_type: 'rerequest', 413 }, 414 }, 415 { 416 authorizationEndpoint: 'https://www.facebook.com/v6.0/dialog/oauth', 417 tokenEndpoint: 'https://graph.facebook.com/v6.0/oauth/access_token', 418 } 419 ); 420 421 return ( 422 <AuthSection 423 title="facebook" 424 disabled={isInClient && !useProxy} 425 request={request} 426 result={result} 427 promptAsync={() => promptAsync({ useProxy, windowFeatures: { width: 700, height: 600 } })} 428 useProxy={useProxy} 429 /> 430 ); 431} 432 433function Slack({ redirectUri, prompt, usePKCE, useProxy }: any) { 434 // https://api.slack.com/apps 435 // After you created an app, navigate to [Features > OAuth & Permissions] 436 // - Add a redirect URI Under [Redirect URLs] 437 // - Under [Scopes] add the scopes you want to request from the user 438 // Next go to [App Credentials] to get your client ID and client secret 439 // No refresh token or expiration is returned, assume the token lasts forever. 440 const [request, result, promptAsync] = useAuthRequest( 441 // config 442 { 443 clientId: '58692702102.1023025401076', 444 redirectUri, 445 scopes: ['emoji:read'], 446 prompt, 447 usePKCE, 448 }, 449 // discovery 450 { 451 authorizationEndpoint: 'https://slack.com/oauth/authorize', 452 tokenEndpoint: 'https://slack.com/api/oauth.access', 453 } 454 ); 455 456 return ( 457 <AuthSection 458 title="slack" 459 request={request} 460 result={result} 461 promptAsync={promptAsync} 462 useProxy={useProxy} 463 /> 464 ); 465} 466 467// Works on all platforms 468function Spotify({ redirectUri, prompt, usePKCE, useProxy }: any) { 469 const [request, result, promptAsync] = useAuthRequest( 470 { 471 clientId: 'a946eadd241244fd88d0a4f3d7dea22f', 472 redirectUri, 473 scopes: ['user-read-email', 'playlist-modify-public', 'user-read-private'], 474 usePKCE, 475 extraParams: { 476 show_dialog: 'false', 477 }, 478 prompt, 479 }, 480 // discovery 481 { 482 authorizationEndpoint: 'https://accounts.spotify.com/authorize', 483 tokenEndpoint: 'https://accounts.spotify.com/api/token', 484 } 485 ); 486 487 return ( 488 <AuthSection 489 title="spotify" 490 request={request} 491 result={result} 492 promptAsync={promptAsync} 493 useProxy={useProxy} 494 /> 495 ); 496} 497 498// Works on all platforms 499function Identity({ redirectUri, prompt, useProxy }: any) { 500 const discovery = AuthSession.useAutoDiscovery('https://demo.identityserver.io'); 501 502 const [request, result, promptAsync] = useAuthRequest( 503 { 504 clientId: 'native.code', 505 redirectUri, 506 prompt, 507 scopes: ['openid', 'profile', 'email', 'offline_access'], 508 }, 509 discovery 510 ); 511 512 return ( 513 <AuthSection 514 title="identity4" 515 request={request} 516 result={result} 517 promptAsync={promptAsync} 518 useProxy={useProxy} 519 /> 520 ); 521} 522 523// Doesn't work with proxy 524function Coinbase({ redirectUri, prompt, usePKCE, useProxy }: any) { 525 const [request, result, promptAsync] = useAuthRequest( 526 { 527 clientId: '13b2bc8d9114b1cb6d0132cf60c162bc9c2d5ec29c2599003556edf81cc5db4e', 528 redirectUri, 529 prompt, 530 usePKCE, 531 scopes: ['wallet:accounts:read'], 532 }, 533 // discovery 534 { 535 authorizationEndpoint: 'https://www.coinbase.com/oauth/authorize', 536 tokenEndpoint: 'https://api.coinbase.com/oauth/token', 537 revocationEndpoint: 'https://api.coinbase.com/oauth/revoke', 538 } 539 ); 540 541 return ( 542 <AuthSection 543 disabled={useProxy} 544 title="coinbase" 545 request={request} 546 result={result} 547 promptAsync={promptAsync} 548 useProxy={useProxy} 549 /> 550 ); 551} 552 553function Dropbox({ redirectUri, prompt, usePKCE, useProxy }: any) { 554 const [request, result, promptAsync] = useAuthRequest( 555 { 556 clientId: 'pjvyj0c5kxxrsfs', 557 redirectUri, 558 prompt, 559 usePKCE, 560 scopes: [], 561 responseType: AuthSession.ResponseType.Token, 562 }, 563 // discovery 564 { 565 authorizationEndpoint: 'https://www.dropbox.com/oauth2/authorize', 566 tokenEndpoint: 'https://www.dropbox.com/oauth2/token', 567 } 568 ); 569 570 return ( 571 <AuthSection 572 disabled={usePKCE} 573 title="dropbox" 574 request={request} 575 result={result} 576 promptAsync={promptAsync} 577 useProxy={useProxy} 578 /> 579 ); 580} 581 582function Twitch({ redirectUri, prompt, usePKCE, useProxy }: any) { 583 const [request, result, promptAsync] = useAuthRequest( 584 { 585 clientId: 'r7jomrc4hiz5wm1wgdzmwr1ccb454h', 586 redirectUri, 587 prompt, 588 scopes: ['openid', 'user_read', 'analytics:read:games'], 589 usePKCE, 590 }, 591 { 592 authorizationEndpoint: 'https://id.twitch.tv/oauth2/authorize', 593 tokenEndpoint: 'https://id.twitch.tv/oauth2/token', 594 revocationEndpoint: 'https://id.twitch.tv/oauth2/revoke', 595 } 596 ); 597 598 return ( 599 <AuthSection 600 disabled={useProxy} 601 title="twitch" 602 request={request} 603 result={result} 604 promptAsync={promptAsync} 605 useProxy={useProxy} 606 /> 607 ); 608} 609