# V2 Authentication Guide This document is the source of truth for V2 authentication. Base URL: ```env API_V2_BASE_URL=https://krow-api-v2-e3g6witsvq-uc.a.run.app ``` ## 1) What is implemented ### Client app Client authentication is implemented through backend endpoints: - `POST /auth/client/sign-in` - `POST /auth/client/sign-up` - `POST /auth/client/sign-out` - `GET /auth/session` The backend signs the user in with Firebase Identity Toolkit, validates the user against the V2 database, and returns the full auth envelope. ### Staff app Staff authentication is implemented, but it is a hybrid flow. Routes: - `POST /auth/staff/phone/start` - `POST /auth/staff/phone/verify` - `POST /auth/staff/sign-out` - `GET /auth/session` Important: - the default mobile path is **not** a fully backend-managed OTP flow - the usual mobile path uses the Firebase Auth SDK on-device for phone verification - after the device gets a Firebase `idToken`, frontend sends that token to `POST /auth/staff/phone/verify` So if someone expects `POST /auth/staff/phone/start` to always send the SMS and always return `sessionInfo`, that expectation is wrong for the current implementation ## 2) Auth refresh There is currently **no** backend `/auth/refresh` endpoint. That is intentional for now. Current refresh model: - frontend keeps Firebase Auth local session state - frontend lets the Firebase SDK refresh the ID token - frontend sends the latest Firebase ID token in: ```http Authorization: Bearer ``` Use: - `authStateChanges()` / `idTokenChanges()` listeners - `currentUser.getIdToken()` - `currentUser.getIdToken(true)` only when a forced refresh is actually needed `GET /auth/session` is **not** a refresh endpoint. It is a context endpoint used to: - hydrate role/tenant/business/staff context - validate that the signed-in Firebase user is allowed in this app ## 3) Client auth flow ### Client sign-in Request: ```http POST /auth/client/sign-in Content-Type: application/json ``` ```json { "email": "legendary.owner+v2@krowd.com", "password": "Demo2026!" } ``` Response: ```json { "sessionToken": "firebase-id-token", "refreshToken": "firebase-refresh-token", "expiresInSeconds": 3600, "user": { "id": "user-uuid", "email": "legendary.owner+v2@krowd.com", "displayName": "Legendary Owner", "phone": null }, "tenant": { "tenantId": "tenant-uuid", "tenantName": "Legendary Event Staffing and Entertainment" }, "business": { "businessId": "business-uuid", "businessName": "Google Mountain View Cafes" }, "requestId": "uuid" } ``` Frontend behavior: 1. Call `POST /auth/client/sign-in` 2. If success, sign in locally with Firebase Auth SDK using the same email/password 3. Use Firebase SDK token refresh for later API calls 4. Use `GET /auth/session` when role/session hydration is needed on app boot ### Client sign-up Request: ```http POST /auth/client/sign-up Content-Type: application/json ``` ```json { "companyName": "Legendary Event Staffing and Entertainment", "email": "legendary.owner+v2@krowd.com", "password": "Demo2026!" } ``` What it does: - creates Firebase Auth account - creates V2 user - creates tenant - creates business - creates tenant membership - creates business membership Frontend behavior after success: 1. call `POST /auth/client/sign-up` 2. sign in locally with Firebase Auth SDK using the same email/password 3. use Firebase SDK for later token refresh ## 4) Staff auth flow ## Step 1: start phone auth Request: ```http POST /auth/staff/phone/start Content-Type: application/json ``` ```json { "phoneNumber": "+15551234567" } ``` Possible response A: ```json { "mode": "CLIENT_FIREBASE_SDK", "provider": "firebase-phone-auth", "phoneNumber": "+15551234567", "nextStep": "Complete phone verification in the mobile client, then call /auth/staff/phone/verify with the Firebase idToken.", "requestId": "uuid" } ``` This is the normal mobile path when frontend does **not** send recaptcha or integrity tokens. Current dev demo worker: - phone number: `+15557654321` - email: `ana.barista+v2@krowd.com` Those two now resolve to the same Firebase user and the same seeded staff profile in v2. Possible response B: ```json { "mode": "IDENTITY_TOOLKIT_SMS", "phoneNumber": "+15551234567", "sessionInfo": "firebase-session-info", "requestId": "uuid" } ``` This is the server-managed SMS path. ## Step 2A: normal mobile path (`CLIENT_FIREBASE_SDK`) Frontend must do this on-device: 1. call `FirebaseAuth.verifyPhoneNumber(...)` 2. collect the `verificationId` 3. collect the OTP code from the user 4. create a Firebase phone credential 5. call `signInWithCredential(...)` 6. get Firebase `idToken` 7. call `POST /auth/staff/phone/verify` with that `idToken` Request: ```http POST /auth/staff/phone/verify Content-Type: application/json ``` ```json { "mode": "sign-in", "idToken": "firebase-id-token-from-device" } ``` Response: ```json { "sessionToken": "firebase-id-token-from-device", "refreshToken": null, "expiresInSeconds": 3600, "user": { "id": "user-uuid", "phone": "+15551234567" }, "staff": { "staffId": "staff-uuid" }, "requiresProfileSetup": false, "requestId": "uuid" } ``` Important: - `refreshToken` is expected to be `null` in this path - refresh remains owned by Firebase Auth SDK on the device ## Step 2B: server SMS path (`IDENTITY_TOOLKIT_SMS`) If `start` returned `sessionInfo`, frontend can call: ```json { "mode": "sign-in", "sessionInfo": "firebase-session-info", "code": "123456" } ``` The backend exchanges `sessionInfo + code` with Identity Toolkit and returns the hydrated auth envelope. ## 5) Sign-out Routes: - `POST /auth/sign-out` - `POST /auth/client/sign-out` - `POST /auth/staff/sign-out` All sign-out routes require: ```http Authorization: Bearer ``` What sign-out does: - revokes backend-side Firebase sessions for that user - frontend should still clear local Firebase Auth state with `FirebaseAuth.instance.signOut()` ## 6) Session endpoint Route: - `GET /auth/session` Headers: ```http Authorization: Bearer ``` Use it for: - app startup hydration - role validation - deciding whether this app should allow the current signed-in user Do not use it as: - a refresh endpoint - a login endpoint ## 7) Error contract All auth routes use the standard V2 error envelope: ```json { "code": "STRING_CODE", "message": "Human readable message", "details": {}, "requestId": "uuid" } ``` Common auth failures: - `UNAUTHENTICATED` - `FORBIDDEN` - `VALIDATION_ERROR` - `AUTH_PROVIDER_ERROR` ## 8) Troubleshooting ### Staff sign-in does not work, but endpoints are reachable The most likely causes are: 1. frontend expected `POST /auth/staff/phone/start` to always return `sessionInfo` 2. frontend did not complete Firebase phone verification on-device 3. frontend called `POST /auth/staff/phone/verify` without a valid Firebase `idToken` 4. frontend phone-auth setup in Firebase mobile config is incomplete ### `POST /auth/staff/phone/start` returns `CLIENT_FIREBASE_SDK` That is expected for the normal mobile flow when no recaptcha or integrity tokens are sent. ### There is no `/auth/refresh` That is also expected right now. Refresh is handled by Firebase Auth SDK on the client.