Mobile Expo Cookie Authentication
Applies to TeeTime Mobile and any Expo/React Native client that authenticates against the DigiWedge IDP with first-party cookies.
Cookie-backed sessions let our mobile clients share the same auth story as the web: the IDP issues HTTP-only cookies, we store them in the native cookie jar, and every subsequent request automatically replays them. This guide captures the full setup, the runtime switches you must expose, how the HTTPS dev proxy keeps cookies same-origin, and what to do when things go sideways.
Setup
Dependencies
- Expo SDK 54 (React Native 0.81)
@react-native-cookies/cookies@^6.2.1- Access to an IDP instance exposing:
GET /api/auth/csrfPOST /api/auth/loginPOST /api/auth/refresh
Install the native cookie bridge inside the mobile workspace:
pnpm --filter teetime-mobile add @react-native-cookies/cookies
Expo Dev Client and production builds ship the native module automatically. Expo Go does not expose the cookie APIs—use a dev client even during local work.
Client wiring
- Call
ensureCsrf()on app bootstrap. It fetches/api/auth/csrfwithcredentials: 'include', which seeds theXSRF-TOKENcookie via@react-native-cookies/cookies. - During login, read cookies via
CookieManager.get(idpBaseUrl)and sendX-XSRF-Tokenon the/api/auth/loginPOST. - Biometric unlock redeems
/api/auth/refreshwithcredentials: 'include'. - On logout, clear the cookie jar (
CookieManager.clearAll()) and wipe any biometric grants from SecureStore so stale sessions cannot be replayed.
The legacy hooks exported from @digiwedge/mobile-auth (useIDP,
useBiometricAuth) already implement the pattern above—import and use them
instead of rolling a custom flow.
Local dev checklist
-
corepack enable && pnpm install -w(ensures the cookie bridge is installed). -
Start the HTTPS proxy for stable FQDNs:
pnpm nx run dev-proxy:up -
Make sure your simulator or device resolves the proxy hosts to your laptop.
- macOS/iOS Simulator: add entries for
*.dev.digiwedge.com→127.0.0.1in/etc/hosts. - Android Emulator: use
10.0.2.2when mapping hosts, or rely on the proxy's TLS certificate by importing it via Android settings.
- macOS/iOS Simulator: add entries for
-
Launch the mobile app with the Expo dev client:
pnpm nx serve teetime-mobile
Environment variables
Set the runtime variables in
apps/teetime/teetime-mobile/.env (or the config provider you use inside Expo).
| Variable | Description |
|---|---|
EXPO_PUBLIC_IDP_BASE_URL | IDP origin (scheme + host only, e.g. https://idp.dev.digiwedge.com). The app appends /api internally. |
EXPO_PUBLIC_IDP_AUDIENCE | IDP audience string (teetime-mobile by default). |
EXPO_PUBLIC_MCA_BASE_URL | Legacy MCA API used during login hand-off. |
EXPO_PUBLIC_MCA_BEARER_TOKEN | Static bearer token for MCA bootstrap calls. |
TEE_TIME_API_BASE | TeeTime API base URL (shares the cookie session when hosted on the same FQDN). |
EXPO_PUBLIC_DEV_PROXY_ORIGIN | Optional override when tunnelling; point to your LAN IP if the device is off-network. |
After changing .env, restart Expo (R in the dev client) so Metro picks up
the updates.
Development proxy usage
Cookie auth only succeeds when the IDP and APIs live on the same origin the cookie was minted for. The Caddy-based proxy provides TLS FQDNs that match our production domains:
- Start the proxy once per session:
pnpm nx run dev-proxy:up. - The proxy exposes hosts like
idp.dev.digiwedge.com,teetime-api.dev.digiwedge.com, andteetime-frontend.dev.digiwedge.com. Review the full matrix indocs/internal/dev/backend-ports.md. - When testing on hardware, point the device DNS (or override the host mapping) at your workstation IP so HTTPS requests still reach the proxy.
- For local tunnels (e.g.
ngrokor Cloudflare), setEXPO_PUBLIC_DEV_PROXY_ORIGIN=https://<tunnel-host>and exportTEETIME_FRONTEND_AUTH_UPSTREAM/TEETIME_API_UPSTREAMbeforedev-proxy:upto fan traffic out to the tunnel target.
With the proxy running, the Expo bridge simply talks to the origin defined in
EXPO_PUBLIC_IDP_BASE_URL and relies on
fetch(..., { credentials: 'include' })—no custom proxy logic is
required inside the app itself. The TeeTime mobile client calls
joinIdpApiPath() to append /api/... so the cookie scope always matches the
origin that minted it.
Auth diagnostics helper
When developing with the Expo dev client, open the
AuthDiagnostics screen (from the login page's dev-only action or by deep
linking via teetime-mobile://auth-diagnostics; the Expo Dev Client URL is
exp://127.0.0.1:8081/--/auth-diagnostics). The screen provides buttons to:
- Clear the cookie jar via
CookieManager.clearAll(). - Run
ensureCsrf()and display the returned XSRF header alongside theXSRF-TOKENvalue stored for the current IDP origin. - Inspect the active IDP origin and client audience values sourced from environment variables.
Use this view to confirm the CSRF handshake before attempting a login on the standard flows.
Request flow refresher
- CSRF handshake –
/api/auth/csrfseedsXSRF-TOKEN. - Login – Send credentials to
/api/auth/loginwith the CSRF token header. - Biometric unlock – Redeem
/api/auth/refreshto rotate the session and biometric grant. - Logout – Drop secure storage and clear the cookie jar.
Failure recovery
| Symptom | Recovery steps |
|---|---|
403 Invalid CSRF token | Confirm the proxy is running, EXPO_PUBLIC_IDP_BASE_URL matches the cookie scope, and CookieManager.get() returns XSRF-TOKEN before posting credentials. |
| Session disappears after backgrounding | Ensure you are using a dev client build—Expo Go discards cookies. Re-run pnpm nx run dev-proxy:up if the proxy timed out and log back in. |
TypeError: Network request failed on device | Device cannot reach the proxy host. Map the *.dev.digiwedge.com hosts to your workstation IP or export EXPO_PUBLIC_DEV_PROXY_ORIGIN with a tunnel URL. |
Invalid key provided to SecureStore | Upgrade to the latest @digiwedge/mobile-auth (migrates keys) and run the biometric reset action inside the app to wipe stale entries. |
HttpRequestError 502 during /api/auth/refresh | Restart the upstream service (pnpm nx run idp:serve / teetime-api:serve) and bounce the proxy so Caddy picks up the healthy backend. |