This document explains how server-side authentication works in the Mobility Database Web app when calling the Mobility Feed API behind Google Cloud IAP with Identity Platform (GCIP). It also covers local development without Firebase access.
- Server components/actions call the Mobility Feed API using a server‑minted GCIP ID token.
- The client never forwards its Firebase ID token to the server; all API calls use server credentials only.
- A short‑lived HTTP‑only session cookie (
md_session) stores a server‑signed JWT with the current user's identity (including guest/anonymous flag). - Firebase Admin is initialized once via a centralized helper and used for both Remote Config and token minting.
- Mock mode (MSW) enables local development without hitting the real API or Firebase.
- Firebase Admin creates a custom token for a service UID (synthetic user identity).
- The custom token is exchanged with Identity Toolkit (
accounts:signInWithCustomToken) to obtain a GCIP ID token. - The GCIP ID token is added as
Authorization: Bearer <token>for calls to the IAP‑protected Mobility Feed API. - Tokens are cached server‑side and refreshed a few minutes before expiry.
Key code paths:
src/lib/firebase-admin.ts: centralized Admin initialization (getFirebaseAdminApp()), backed byensureAdminInitialized().src/app/utils/auth-server.ts: token functionsgetGcipIdToken()andgetSSRAccessToken()(the canonical token provider for SSR calls) and helpers for reading the session cookie.src/app/services/feeds/index.ts: OpenAPI client; injectsAuthorizationand user‑context headers using the token returned bygetSSRAccessToken().
To let server components know "who" the current user is (including guests) without ever trusting client tokens directly, the web app uses a short‑lived, server‑signed session JWT stored in the md_session HTTP‑only cookie.
Flow:
- The user signs in on the client with Firebase Auth (email/password, provider, or anonymous).
- After any successful login, a Redux saga calls
setUserCookieSession()from src/app/services/session-service.ts. setUserCookieSession()reads the current Firebase ID token from the client SDK and POSTs it to/api/session.- The
/api/sessionPOSThandler in src/app/api/session/route.ts:- Verifies the ID token with Firebase Admin.
- Derives an
isGuestflag from the sign‑in provider (anonymous→ guest). - Issues a short‑lived session JWT (1 hour) signed with
NEXT_SESSION_JWT_SECRETcontaining:uid, optionalemail,isGuest,iat, andexp.
- Sets the
md_sessioncookie (HTTP‑only,sameSite=lax,securein production).
On the server side:
- src/app/utils/session-jwt.ts defines the
SessionPayloadtype and helpers to sign/verify the JWT used inmd_session. - src/app/utils/auth-server.ts exposes:
getCurrentUserFromCookie()to decode the session cookie intoSessionPayloadfor SSR.getUserContextJwtFromCookie()to obtain the raw, verified session JWT for forwarding to the backend.
The GCIP ID token used for IAP remains a server‑minted token that does not depend on the client token; the session cookie is only used for identifying the current end‑user (including guests) and for per‑user attribution.
Centralized in getFirebaseAdminApp():
- Reuse existing Admin app if already initialized (matching
NEXT_PUBLIC_FIREBASE_PROJECT_ID). - Prefer inline service account JSON:
GOOGLE_SA_JSON(orFIREBASE_SERVICE_ACCOUNT_JSONif enabled in your environment). - Or load from file path:
GOOGLE_SA_JSON_PATH(orGOOGLE_APPLICATION_CREDENTIALS). - Fail fast if no credentials are provided to avoid unpredictable ADC/metadata lookups in serverless/dev environments.
Required service account fields: project_id, client_email, private_key.
Server‑side credentials and config (server‑only):
GOOGLE_SA_JSON: Inline service account JSON string.GOOGLE_SA_JSON_PATH: Absolute/relative path to service account JSON file.NEXT_PUBLIC_FIREBASE_PROJECT_ID: Project ID; used to match Admin apps and as fallback when JSON lacksproject_id.NEXT_SESSION_JWT_SECRET: Secret used to sign and verify themd_sessionsession JWT on the web side.
GCIP / Identity Toolkit:
GCIP_API_KEY(orFIREBASE_API_KEYorNEXT_PUBLIC_FIREBASE_API_KEY): API key foraccounts:signInWithCustomToken.GCIP_TENANT_ID(optional): Tenant ID when using multi‑tenant Identity Platform.GCIP_SERVICE_UID(optional): Synthetic UID for the server caller (default:iap-service-caller).
Mock/dev:
NEXT_PUBLIC_API_MOCKING=enabled: Enables MSW mock service worker in the browser.LOCAL_DEV_NO_ADMIN=1(optional): Bypass Admin initialization for Remote Config/token code paths during local experimentation.
Note: On the Mobility Feed API (Python) side, a matching secret (e.g.
S2S_JWT_SECRET) is used to validate the user‑context JWT forwarded from the web app.
- Server‑side code fetches Firebase Remote Config via Admin SDK.
- In mock mode, Remote Config returns defaults to avoid Admin calls.
- Entry points:
src/lib/remote-config.server.tsand theProviderswrapper insrc/app/providers.tsx.
Use MSW (Mock Service Worker) so mocks behave like a real API without changing app logic.
Setup:
- Initialize the worker (one‑time):
npx msw init public/
- Start dev in mock mode:
NEXT_PUBLIC_API_MOCKING=enabled yarn start:dev:mock
- Mock handlers live in
src/mocks/handlers.ts.- The browser worker starts from
src/mocks/browser.tsviasrc/app/providers.tsxwhen mock mode is enabled.
- The browser worker starts from
- SSR/API calls: Always obtain
accessToken = await getSSRAccessToken()in server components/actions, then pass toopenapi-fetchclient. - Do not forward client tokens to the server.
- Keep all credentials server‑only and never expose service account JSON to client code.
In addition to the GCIP ID token for IAP, SSR API calls also propagate a compact, server‑signed user‑context JWT so the backend can attribute requests to an end‑user without trusting any client tokens:
- The
md_sessioncookie's JWT is reused as this user‑context token. - On the server, src/app/utils/auth-server.ts reads the cookie via
getUserContextJwtFromCookie(). - src/app/services/api-auth-middleware.ts provides
generateAuthMiddlewareWithToken(accessToken, userContextJwt?), which:- Sets
Authorization: Bearer <accessToken>for IAP. - When
userContextJwtis present, also setsx-mdb-user-context: <userContextJwt>.
- Sets
- All server‑side feeds service functions in src/app/services/feeds/index.ts accept an optional
userContextJwtand pass it into this middleware.
On the Mobility Feed API side (see mobility-feed-api/api/src/middleware/request_context.py):
- The
RequestContextmiddleware readsx-mdb-user-context. - It verifies the HS256 signature using a shared secret and decodes the payload.
- It populates
user_id,user_email, and anis_guestflag for auditing and per‑user behavior.
- No client token pass‑through: Prevents elevation of privilege and token replay.
- Server‑only credentials: Service account material must never be sent to the client.
- End‑user attribution without client tokens: The backend receives only a server‑signed, minimal user‑context JWT via
x-mdb-user-context, never the raw client Firebase ID token. - Guest users: Anonymous sign‑ins are explicitly flagged via
isGuestin the session JWT so both the web app and backend can distinguish guest from authenticated accounts.
Common issues and fixes:
- "Firebase app already exists": Multiple Admin initializations. Use centralized
getFirebaseAdminApp()and reuse existing apps. - "Invalid GCIP ID token": IAP configured with Identity Platform requires GCIP tokens, not Google OIDC tokens.
- Metadata errors (ENOTFOUND): ADC fallback on local/dev without credentials. Provide explicit service account JSON/path and avoid metadata server.
- Missing fields: Ensure service account JSON contains
project_id,client_email,private_key. - MSW not intercepting: Confirm
NEXT_PUBLIC_API_MOCKING=enabled,public/mockServiceWorker.jsexists, and the worker starts inproviders.tsx.
- Provide service account (either inline or file path):
- Inline (recommended for Vercel server env):
- Set
GOOGLE_SA_JSONto a single‑line JSON string.
- Set
- File path:
- Set
GOOGLE_SA_JSON_PATHto the JSON file path.
- Set
- Inline (recommended for Vercel server env):
- Start dev:
yarn start:dev
- Optional mock mode:
npx msw init public/ NEXT_PUBLIC_API_MOCKING=enabled yarn start:dev:mock
- Set server‑only env vars in Vercel:
GOOGLE_SA_JSON,GCIP_API_KEY, and optionallyGCIP_TENANT_ID,GCIP_SERVICE_UID.
- Ensure
NEXT_PUBLIC_FIREBASE_PROJECT_IDmatches your Firebase project. - Use Node runtime for server components/actions (default in Next.js 16 App Router).
For questions or improvements, see src/lib/firebase-admin.ts, src/app/utils/auth-server.ts, and src/mocks/handlers.ts for practical references.