Skip to main content

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/csrf
    • POST /api/auth/login
    • POST /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

  1. Call ensureCsrf() on app bootstrap. It fetches /api/auth/csrf with credentials: 'include', which seeds the XSRF-TOKEN cookie via @react-native-cookies/cookies.
  2. During login, read cookies via CookieManager.get(idpBaseUrl) and send X-XSRF-Token on the /api/auth/login POST.
  3. Biometric unlock redeems /api/auth/refresh with credentials: 'include'.
  4. 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

  1. corepack enable && pnpm install -w (ensures the cookie bridge is installed).

  2. Start the HTTPS proxy for stable FQDNs:

    pnpm nx run dev-proxy:up
  3. Make sure your simulator or device resolves the proxy hosts to your laptop.

    • macOS/iOS Simulator: add entries for *.dev.digiwedge.com127.0.0.1 in /etc/hosts.
    • Android Emulator: use 10.0.2.2 when mapping hosts, or rely on the proxy's TLS certificate by importing it via Android settings.
  4. 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).

VariableDescription
EXPO_PUBLIC_IDP_BASE_URLIDP origin (scheme + host only, e.g. https://idp.dev.digiwedge.com). The app appends /api internally.
EXPO_PUBLIC_IDP_AUDIENCEIDP audience string (teetime-mobile by default).
EXPO_PUBLIC_MCA_BASE_URLLegacy MCA API used during login hand-off.
EXPO_PUBLIC_MCA_BEARER_TOKENStatic bearer token for MCA bootstrap calls.
TEE_TIME_API_BASETeeTime API base URL (shares the cookie session when hosted on the same FQDN).
EXPO_PUBLIC_DEV_PROXY_ORIGINOptional 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:

  1. Start the proxy once per session: pnpm nx run dev-proxy:up.
  2. The proxy exposes hosts like idp.dev.digiwedge.com, teetime-api.dev.digiwedge.com, and teetime-frontend.dev.digiwedge.com. Review the full matrix in docs/internal/dev/backend-ports.md.
  3. When testing on hardware, point the device DNS (or override the host mapping) at your workstation IP so HTTPS requests still reach the proxy.
  4. For local tunnels (e.g. ngrok or Cloudflare), set EXPO_PUBLIC_DEV_PROXY_ORIGIN=https://<tunnel-host> and export TEETIME_FRONTEND_AUTH_UPSTREAM / TEETIME_API_UPSTREAM before dev-proxy:up to 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 the XSRF-TOKEN value 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

  1. CSRF handshake/api/auth/csrf seeds XSRF-TOKEN.
  2. Login – Send credentials to /api/auth/login with the CSRF token header.
  3. Biometric unlock – Redeem /api/auth/refresh to rotate the session and biometric grant.
  4. Logout – Drop secure storage and clear the cookie jar.

Failure recovery

SymptomRecovery steps
403 Invalid CSRF tokenConfirm 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 backgroundingEnsure 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 deviceDevice 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 SecureStoreUpgrade 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/refreshRestart the upstream service (pnpm nx run idp:serve / teetime-api:serve) and bounce the proxy so Caddy picks up the healthy backend.