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