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()` 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 * // Development Build: my-scheme://redirect 155 * // Expo Go: exp://127.0.0.1:8081/--/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 * // Development Build: scheme2:/// 165 * // Expo Go: exp://localhost:8081 166 * // Web dev: https://localhost:19006 167 * // Web prod: https://yourwebsite.com 168 * ``` 169 */ 170export function makeRedirectUri({ 171 native, 172 scheme, 173 isTripleSlashed, 174 queryParams, 175 path, 176 preferLocalhost, 177 useProxy, 178 projectNameForProxy, 179}: AuthSessionRedirectUriOptions = {}): string { 180 if ( 181 Platform.OS !== 'web' && 182 native && 183 [ExecutionEnvironment.Standalone, ExecutionEnvironment.Bare].includes( 184 Constants.executionEnvironment 185 ) 186 ) { 187 // Should use the user-defined native scheme in standalone builds 188 return native; 189 } 190 if (!useProxy || Platform.OS === 'web') { 191 const url = Linking.createURL(path || '', { 192 isTripleSlashed, 193 scheme, 194 queryParams, 195 }); 196 197 if (preferLocalhost) { 198 const ipAddress = url.match( 199 /\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/ 200 ); 201 // Only replace if an IP address exists 202 if (ipAddress?.length) { 203 const [protocol, path] = url.split(ipAddress[0]); 204 return `${protocol}localhost${path}`; 205 } 206 } 207 208 return url; 209 } 210 // Attempt to use the proxy 211 return sessionUrlProvider.getRedirectUrl({ urlPath: path, projectNameForProxy }); 212} 213 214// @needsAudit 215/** 216 * Build an `AuthRequest` and load it before returning. 217 * 218 * @param config A valid [`AuthRequestConfig`](#authrequestconfig) that specifies what provider to use. 219 * @param issuerOrDiscovery A loaded [`DiscoveryDocument`](#discoverydocument) or issuer URL. 220 * (Only `authorizationEndpoint` is required for requesting an authorization code). 221 * @return Returns an instance of `AuthRequest` that can be used to prompt the user for authorization. 222 */ 223export async function loadAsync( 224 config: AuthRequestConfig, 225 issuerOrDiscovery: IssuerOrDiscovery 226): Promise<AuthRequest> { 227 const request = new AuthRequest(config); 228 const discovery = await resolveDiscoveryAsync(issuerOrDiscovery); 229 await request.makeAuthUrlAsync(discovery); 230 return request; 231} 232 233async function _openWebBrowserAsync(startUrl: string, returnUrl: string, showInRecents: boolean) { 234 const result = await openAuthSessionAsync(startUrl, returnUrl, { showInRecents }); 235 if (result.type === 'cancel' || result.type === 'dismiss') { 236 return { type: result.type }; 237 } 238 239 return result; 240} 241 242export { useAutoDiscovery, useAuthRequest } from './AuthRequestHooks'; 243export { AuthError, TokenError } from './Errors'; 244 245export { 246 AuthSessionOptions, 247 AuthSessionRedirectUriOptions, 248 AuthSessionResult, 249 AuthRequest, 250 AuthRequestConfig, 251 AuthRequestPromptOptions, 252 CodeChallengeMethod, 253 DiscoveryDocument, 254 Issuer, 255 IssuerOrDiscovery, 256 Prompt, 257 ProviderMetadata, 258 ResponseType, 259 resolveDiscoveryAsync, 260 fetchDiscoveryAsync, 261 generateHexStringAsync, 262}; 263 264export { 265 // Token classes 266 TokenResponse, 267 AccessTokenRequest, 268 RefreshTokenRequest, 269 RevokeTokenRequest, 270 // Token methods 271 revokeAsync, 272 refreshAsync, 273 exchangeCodeAsync, 274 fetchUserInfoAsync, 275} from './TokenRequest'; 276 277// Token types 278export * from './TokenRequest.types'; 279 280// Provider specific types 281export { GoogleAuthRequestConfig } from './providers/Google'; 282export { FacebookAuthRequestConfig } from './providers/Facebook'; 283