FriendChise Docs
Mobile Authentication
How the Expo app signs in against the same Auth.js backend as the web app
The mobile app does not run its own auth system. It authenticates against the FriendChise web backend and stores the resulting token on-device.
Flow
- The user signs in through the standard OAuth flow (or, in development, the seeded dev-user picker) against the web backend.
- The backend issues a signed JWT (the same token shape used for the web session) using
AUTH_SECRET. The mobile app never contains that secret. - The mobile app stores the token in Expo SecureStore.
- Subsequent API requests attach the token as a bearer
Authorizationheader via a sharedapiFetchhelper.
Demo sessions
The web app exposes GET /api/mobile-auth/demo, a development-only endpoint that provisions an isolated demo org and redirects back to the app with a token via callbackUrl. The issued JWT expires after 1 hour (DEMO_JWT_TTL_MS), the same lifetime as the web demo session, rather than the normal 30-day token. See Task System and the web Authentication page for how demo/dev credential flows are gated to non-production environments.
The mobile app detects demo sessions client-side by checking the token's email claim against the @demo.friendchise.app suffix (src/features/auth/demo.ts, mirroring the web's isDemoEmail) rather than a dedicated flag, since no new claim was added to the JWT. When a demo session is active, DemoSessionBanner (src/features/auth/demo-session-banner.tsx) renders a persistent status strip in the app shell with a live countdown to expiry and an "End demo" action, mirroring the web app's demo banner.
Session expiry
src/features/auth/jwt-utils.tsexposesgetJwtExpiryMs()/isJwtExpired()/getJwtEmail()to read the token'sexpandemailclaims client-side.src/features/auth/session-watcher.tsxrenders aSessionWatcherthat sets a timer for the token's expiry and signs the user out automatically when it lapses, and also derives demo-session state (isDemo,demoExpiresAt) from the token intoauth-store.ts\u2014 no separate mobile-specific expiry logic is needed since it all works off the standard JWT claims.src/features/auth/token-store.tswraps SecureStore reads/writes for the token.
Common failure
- The most common local-dev auth failure is a missing or incorrect
EXPO_PUBLIC_API_URLin the mobile app's environment — it must point at a reachable instance of the web backend (see Environment Variables). - The backend issuer and verifier must share the same
AUTH_SECRET; a mismatch causes token verification to fail silently (requests come back unauthorized).
TODO
- Document token refresh behavior (if/when refresh tokens are introduced) — today the app relies on session expiry + re-login rather than silent refresh.
