1import Constants, { ExecutionEnvironment } from 'expo-constants'; 2import * as Linking from 'expo-linking'; 3import { Platform } from 'expo-modules-core'; 4import { dismissAuthSession, openAuthSessionAsync } from 'expo-web-browser'; 5 6import { AuthRequest } from './AuthRequest'; 7import { 8 AuthRequestConfig, 9 AuthRequestPromptOptions, 10 CodeChallengeMethod, 11 Prompt, 12 ResponseType, 13} from './AuthRequest.types'; 14import { 15 AuthSessionOptions, 16 AuthSessionRedirectUriOptions, 17 AuthSessionResult, 18} from './AuthSession.types'; 19import { 20 DiscoveryDocument, 21 fetchDiscoveryAsync, 22 Issuer, 23 IssuerOrDiscovery, 24 ProviderMetadata, 25 resolveDiscoveryAsync, 26} from './Discovery'; 27import { generateHexStringAsync } from './PKCE'; 28import { getQueryParams } from './QueryParams'; 29import sessionUrlProvider from './SessionUrlProvider'; 30 31let _authLock = false; 32 33// @needsAudit 34/** 35 * Initiate an authentication session with the given options. Only one `AuthSession` can be active at any given time in your application. 36 * If you attempt to open a second session while one is still in progress, the second session will return a value to indicate that `AuthSession` is locked. 37 * 38 * @param options An object of type `AuthSessionOptions`. 39 * @return Returns a Promise that resolves to an `AuthSessionResult` object. 40 */ 41export async function startAsync(options: AuthSessionOptions): Promise<AuthSessionResult> { 42 const authUrl = options.authUrl; 43 // Prevent accidentally starting to an empty url 44 if (!authUrl) { 45 throw new Error( 46 'No authUrl provided to AuthSession.startAsync. An authUrl is required -- it points to the page where the user will be able to sign in.' 47 ); 48 } 49 // Prevent multiple sessions from running at the same time, WebBrowser doesn't 50 // support it this makes the behavior predictable. 51 if (_authLock) { 52 if (__DEV__) { 53 console.warn( 54 'Attempted to call AuthSession.startAsync multiple times while already active. Only one AuthSession can be active at any given time.' 55 ); 56 } 57 58 return { type: 'locked' }; 59 } 60 61 const returnUrl = options.returnUrl || sessionUrlProvider.getDefaultReturnUrl(); 62 const startUrl = sessionUrlProvider.getStartUrl(authUrl, returnUrl); 63 const showInRecents = options.showInRecents || false; 64 65 // About to start session, set lock 66 _authLock = true; 67 68 let result; 69 try { 70 result = await _openWebBrowserAsync(startUrl, returnUrl, showInRecents); 71 } finally { 72 // WebBrowser session complete, unset lock 73 _authLock = false; 74 } 75 76 // Handle failures 77 if (!result) { 78 throw new Error('Unexpected missing AuthSession result'); 79 } 80 if (!result.url) { 81 if (result.type) { 82 return result; 83 } else { 84 throw new Error('Unexpected AuthSession result with missing type'); 85 } 86 } 87 88 const { params, errorCode } = getQueryParams(result.url); 89 90 return { 91 type: errorCode ? 'error' : 'success', 92 params, 93 errorCode, 94 authentication: null, 95 url: result.url, 96 }; 97} 98 99// @needsAudit 100/** 101 * Cancels an active `AuthSession` if there is one. No return value, but if there is an active `AuthSession` 102 * then the Promise returned by the `AuthSession.startAsync()` that initiated it resolves to `{ type: 'dismiss' }`. 103 */ 104export function dismiss() { 105 dismissAuthSession(); 106} 107 108export const getDefaultReturnUrl = sessionUrlProvider.getDefaultReturnUrl; 109 110// @needsAudit @docsMissing 111/** 112 * Get the URL that your authentication provider needs to redirect to. For example: `https://auth.expo.io/@your-username/your-app-slug`. You can pass an additional path component to be appended to the default redirect URL. 113 * > **Note** This method will throw an exception if you're using the bare workflow on native. 114 * 115 * @param path 116 * @return 117 * 118 * @example 119 * ```ts 120 * const url = AuthSession.getRedirectUrl('redirect'); 121 * 122 * // Managed: https://auth.expo.io/@your-username/your-app-slug/redirect 123 * // Web: https://localhost:19006/redirect 124 * ``` 125 * 126 * @deprecated Use `makeRedirectUri({ path, useProxy })` instead. 127 */ 128export function getRedirectUrl(path?: string): string { 129 return sessionUrlProvider.getRedirectUrl(path); 130} 131 132// @needsAudit 133/** 134 * Create a redirect url for the current platform and environment. You need to manually define the redirect that will be used in 135 * a bare workflow React Native app, or an Expo standalone app, this is because it cannot be inferred automatically. 136 * - **Web:** Generates a path based on the current `window.location`. For production web apps, you should hard code the URL as well. 137 * - **Managed workflow:** Uses the `scheme` property of your `app.config.js` or `app.json`. 138 * - **Proxy:** Uses `auth.expo.io` as the base URL for the path. This only works in Expo Go and standalone environments. 139 * - **Bare workflow:** Will fallback to using the `native` option for bare workflow React Native apps. 140 * 141 * @param options Additional options for configuring the path. 142 * @return The `redirectUri` to use in an authentication request. 143 * 144 * @example 145 * ```ts 146 * const redirectUri = makeRedirectUri({ 147 * scheme: 'my-scheme', 148 * path: 'redirect' 149 * }); 150 * // Custom app: my-scheme://redirect 151 * // Expo Go: exp://127.0.0.1:19000/--/redirect 152 * // Web dev: https://localhost:19006/redirect 153 * // Web prod: https://yourwebsite.com/redirect 154 * 155 * const redirectUri2 = makeRedirectUri({ 156 * scheme: 'scheme2', 157 * preferLocalhost: true, 158 * isTripleSlashed: true, 159 * }); 160 * // Custom app: scheme2:/// 161 * // Expo Go: exp://localhost:19000 162 * // Web dev: https://localhost:19006 163 * // Web prod: https://yourwebsite.com 164 * 165 * const redirectUri3 = makeRedirectUri({ 166 * useProxy: true, 167 * }); 168 * // Custom app: https://auth.expo.io/@username/slug 169 * // Expo Go: https://auth.expo.io/@username/slug 170 * // Web dev: https://localhost:19006 171 * // Web prod: https://yourwebsite.com 172 * ``` 173 */ 174export function makeRedirectUri({ 175 native, 176 scheme, 177 isTripleSlashed, 178 queryParams, 179 path, 180 preferLocalhost, 181 useProxy, 182}: AuthSessionRedirectUriOptions = {}): string { 183 if ( 184 Platform.OS !== 'web' && 185 native && 186 [ExecutionEnvironment.Standalone, ExecutionEnvironment.Bare].includes( 187 Constants.executionEnvironment 188 ) 189 ) { 190 // Should use the user-defined native scheme in standalone builds 191 return native; 192 } 193 if (!useProxy || Platform.OS === 'web') { 194 const url = Linking.createURL(path || '', { 195 isTripleSlashed, 196 scheme, 197 queryParams, 198 }); 199 200 if (preferLocalhost) { 201 const ipAddress = url.match( 202 /\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/ 203 ); 204 // Only replace if an IP address exists 205 if (ipAddress?.length) { 206 const [protocol, path] = url.split(ipAddress[0]); 207 return `${protocol}localhost${path}`; 208 } 209 } 210 211 return url; 212 } 213 // Attempt to use the proxy 214 return sessionUrlProvider.getRedirectUrl(path); 215} 216 217// @needsAudit 218/** 219 * Build an `AuthRequest` and load it before returning. 220 * 221 * @param config A valid [`AuthRequestConfig`](#authrequestconfig) that specifies what provider to use. 222 * @param issuerOrDiscovery A loaded [`DiscoveryDocument`](#discoverydocument) or issuer URL. 223 * (Only `authorizationEndpoint` is required for requesting an authorization code). 224 * @return Returns an instance of `AuthRequest` that can be used to prompt the user for authorization. 225 */ 226export async function loadAsync( 227 config: AuthRequestConfig, 228 issuerOrDiscovery: IssuerOrDiscovery 229): Promise<AuthRequest> { 230 const request = new AuthRequest(config); 231 const discovery = await resolveDiscoveryAsync(issuerOrDiscovery); 232 await request.makeAuthUrlAsync(discovery); 233 return request; 234} 235 236async function _openWebBrowserAsync(startUrl: string, returnUrl: string, showInRecents: boolean) { 237 // $FlowIssue: Flow thinks the awaited result can be a promise 238 const result = await openAuthSessionAsync(startUrl, returnUrl, { showInRecents }); 239 if (result.type === 'cancel' || result.type === 'dismiss') { 240 return { type: result.type }; 241 } 242 243 return result; 244} 245 246export { useAutoDiscovery, useAuthRequest } from './AuthRequestHooks'; 247export { AuthError, TokenError } from './Errors'; 248 249export { 250 AuthSessionOptions, 251 AuthSessionRedirectUriOptions, 252 AuthSessionResult, 253 AuthRequest, 254 AuthRequestConfig, 255 AuthRequestPromptOptions, 256 CodeChallengeMethod, 257 DiscoveryDocument, 258 Issuer, 259 IssuerOrDiscovery, 260 Prompt, 261 ProviderMetadata, 262 ResponseType, 263 resolveDiscoveryAsync, 264 fetchDiscoveryAsync, 265 generateHexStringAsync, 266}; 267 268export { 269 // Token classes 270 TokenResponse, 271 AccessTokenRequest, 272 RefreshTokenRequest, 273 RevokeTokenRequest, 274 // Token methods 275 revokeAsync, 276 refreshAsync, 277 exchangeCodeAsync, 278 fetchUserInfoAsync, 279} from './TokenRequest'; 280 281// Token types 282export * from './TokenRequest.types'; 283 284// Provider specific types 285export { GoogleAuthRequestConfig } from './providers/Google'; 286export { FacebookAuthRequestConfig } from './providers/Facebook'; 287