expo-auth-session— builds the Ave authorize URL and runs PKCE;promptAsync()opens the in-app system browser (Chrome Custom Tabs on Android, SFSafariViewController on iOS) viaexpo-web-browser, not a rawWebViewand not Safari/Chrome leaving your app without a return path.expo-web-browser— session completion (maybeCompleteAuthSession), optionalwarmUpAsync/coolDownAsyncon Android@ave-id/sdk— exchange the code, refresh tokens, optional Ave Session
Linking.openURL(authorizeUrl) alone for login — you lose the secure auth-session handoff. Use useAuthRequest + promptAsync() (or WebBrowser.openAuthSessionAsync with the same redirect URL you registered).
If you’re using Convex too, see Convex custom auth. The short version: pass Convex the Ave id_token, not access_token_jwt.
Install dependencies
expo-crypto is a peer dependency of expo-auth-session.
Configure the Ave SDK for Expo
Expo native does not provide Web Crypto for SHA-256 (needed for PKCE code challenge). Useexpo-crypto, not browser crypto.subtle, for digest and getRandomValues.
One import (recommended)
generateCodeChallenge, startPkceLogin, or AuthSession’s PKCE).
System browser (expo-web-browser)
Ave MUST open in the Expo-managed browser session so the redirect returns to your app.
- Import at root (before your first auth prompt):
- Android: warm the custom tab while your login screen is mounted (same pattern as Expo’s own AuthSession examples):
AuthSession.useAuthRequest+promptAsync()— this is what actually opens the authorization URL inexpo-web-browser. You do not need to callopenBrowserAsyncyourself for the standard flow.
Register your redirect URI
First, give your app a custom scheme:In Expo development builds and standalone apps,
makeRedirectUri() uses your app scheme. On web it uses the current website origin. Keep the redirect URI you register in Ave aligned with the platform you are testing.Ave Session + SecureStore (single-flight refresh)
Foroffline_access, token refresh, and optional #app_key (E2EE) merged from the redirect URL string:
completeExpoOAuthCallback calls exchangeCode and session.setTokensFromResponse (no sessionStorage / window). Use onExpoAppForegroundRefresh from the same module for background refresh nudges.
Minimal implementation
This example uses Expo for the browser redirect and PKCE request, then uses@ave-id/sdk for Ave’s token exchange and refresh helpers.
What this flow does
useAuthRequest()creates the authorization request and PKCE verifier/challengepromptAsync()opens the system browser via Expo’s browser session support- Ave redirects back to your Expo deep link with an authorization
code - You exchange that code with
exchangeCode(...), passingrequest.codeVerifier - Ave returns the normal token set:
access_token,access_token_jwt,id_token, and optionallyrefresh_token
Token handling
Ave’s token response has a few fields Expo developers usually care about:| Field | Use |
|---|---|
access_token | Bearer token for Ave API endpoints like userinfo |
access_token_jwt | Signed JWT for Ave resource authorization |
id_token | OIDC identity token for your app and for Convex |
refresh_token | Used to rotate into a fresh token set when offline_access was granted |
verifyJwt() in the client. Use the tokens directly for app state, and move JWT signature verification to your backend if you need it.
Convex handoff
If your Expo app talks to Convex, give Convex the Aveid_token:
access_token_jwt to Convex. Its audience is https://aveid.net, while Convex expects the id_token audience to match your Ave clientId.
The full setup is documented in Convex custom auth.
Common pitfalls
The browser completes but the app never resumes
The browser completes but the app never resumes
Your app scheme or redirect URI is misconfigured. Make sure the scheme in Expo config matches the scheme used in
makeRedirectUri(), and register that exact redirect URI in the Ave developer portal.The code exchange fails with PKCE or invalid_grant errors
The code exchange fails with PKCE or invalid_grant errors
The authorization code must be exchanged with the exact
request.codeVerifier created by useAuthRequest(). Do not generate a second verifier yourself during the callback.The flow works on native but fails on web
The flow works on native but fails on web
Expo documents that
WebBrowser.maybeCompleteAuthSession() should run on web so the popup can finish cleanly. Also make sure the auth flow starts and finishes on the same web origin.Convex rejects the token
Convex rejects the token
Pass
id_token, not access_token_jwt. See Convex custom auth.Notes on Expo APIs
- Expo’s AuthSession docs say
usePKCEdefaults totrue, but it is still worth setting explicitly in auth code so the flow is obvious in your app. - Expo’s WebBrowser docs recommend
openAuthSessionAsyncfor auth-style browser flows and documentwarmUpAsync()/coolDownAsync()as Android performance helpers. - On web, Expo documents that
maybeCompleteAuthSession()is what closes the popup after the redirect page loads.
