351 lines
7.2 KiB
Markdown
351 lines
7.2 KiB
Markdown
# 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.
|
|
|
|
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 <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.
|