Merge pull request #654 from Oloodi/codex/feat-v2-auth-docs

docs(auth): clarify v2 auth flows and refresh
This commit is contained in:
Achintha Isuru
2026-03-17 08:51:02 -04:00
committed by GitHub
3 changed files with 346 additions and 0 deletions

View File

@@ -141,6 +141,7 @@ Those routes still exist for backend/internal compatibility, but mobile/frontend
## 7) Docs ## 7) Docs
- [Authentication](./authentication.md)
- [Unified API](./unified-api.md) - [Unified API](./unified-api.md)
- [Core API](./core-api.md) - [Core API](./core-api.md)
- [Command API](./command-api.md) - [Command API](./command-api.md)

View File

@@ -0,0 +1,343 @@
# V2 Authentication Guide
This document is the source of truth for V2 authentication.
Base URL:
```env
API_V2_BASE_URL=https://krow-api-v2-933560802882.us-central1.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 <firebase-id-token>
```
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.
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 <firebase-id-token>
```
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 <firebase-id-token>
```
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.

View File

@@ -8,6 +8,8 @@ The gateway keeps backend services separate internally, but frontend should trea
## 1) Auth routes ## 1) Auth routes
Full auth behavior, including staff phone flow and refresh rules, is documented in [Authentication](./authentication.md).
### Client auth ### Client auth
- `POST /auth/client/sign-in` - `POST /auth/client/sign-in`