Studybo Authentication — Architecture & Flow
Overview
Studybo uses Firebase Authentication on the client and Firebase Admin (server) to implement a secure
social login flow. The backend verifies third■party tokens, upserts a user document in Firestore, issues a
short■lived Firebase Custom Token, and relies on a NestJS guard to protect APIs by validating Firebase
ID tokens on each request.
Key Components
• Frontend ([Link]): Uses Firebase Web SDK to sign in with Google, obtains a Firebase ID token,
calls the backend. • AuthController (/auth/social-login): Public endpoint to verify a social token and return a
Firebase Custom Token. • AuthService: Verifies Google or Firebase tokens, creates/updates Firestore
user, issues a custom token with custom claims. • FirestoreDatabase: Thin wrapper exposing collections
like users/groups. • FirebaseAuthGuard: Validates Authorization: Bearer for protected routes, attaches
[Link].
End-to-End Social Login (Happy Path)
1) User Action (Browser)
• User clicks “Sign in with Google”. The Firebase Web SDK completes Google sign■in and returns a
Firebase ID token bound to the project.
2) Call Backend for Custom Token
• Frontend POSTs to /auth/social-login with { type: 'FIREBASE', token: }. • Controller delegates to
[Link]().
3) Server: Verify Token
• For FIREBASE type, [Link]() calls firebase-admin
[Link](idToken, true). Passing 'true' enforces revocation checks. • For GOOGLE type
(alternative path), verify via Google [Link]().
4) Server: Upsert User in Firestore
• getUserByEmail() searches users collection by email. • If not found → createUser(email, profile). • If
found → updateProfileDetails(uid, profile).
5) Server: Issue Custom Token
• createCustomToken(uid, { userFirstName }) produces a Firebase Custom Token signed by the service
account.
6) Client: Establish Session
• Frontend receives { firebaseToken, user } from server, then calls
signInWithCustomToken(firebaseToken). • Firebase returns a fresh ID token (1■hour expiry); SDK
auto-refreshes it.
7) Accessing Protected APIs
• Client includes Authorization: Bearer on API calls. • FirebaseAuthGuard verifies the ID token and injects
[Link] = { uid, email, name }.
Sequence (ASCII)
User Browser(Firebase) Backend (NestJS) Firebase Admin Firestore
| Click "Sign in with Google" | | |
|------------------------------>| | |
| Google sign-in popup | | |
| returns ID_TOKEN | | |
|<------------------------------| | |
| POST /auth/social-login | | |
| {type:FIREBASE, token:ID_TOK}|------------->| |
| | verifyIdToken(ID_TOKEN, true) --------->|
| | | decoded {email,name} |
| |<------------------------------------------
| | upsert user ---------------------------->| (users)
| |<------------------------------------------
| | createCustomToken(uid, claims) -------->|
| |<------------------------------------------
| {firebaseToken, user} |<-------------| |
| signInWithCustomToken |------------->| |
| obtain fresh ID_TOKEN |<-------------| |
| Authorization: Bearer ID_TOK |------------->| [Link] |
| (protected API) | |→ [Link] = {uid,email} |
Important Files & Responsibilities
• [Link]: /auth/social-login (public), /auth/test-user-login (public for debugging), /auth/login-ui,
/auth/delete-me. • [Link]: verifySocialToken(), verifyGoogleToken(), verifyFirebaseToken(),
authenticate(), createUser(), updateProfileDetails(), createCustomToken(), deleteUser(). •
guards/[Link]: FirebaseAuthGuard validates ID tokens and sets [Link]. •
firebase/[Link]: Collection helpers and simple operations.
User Deletion & Anonymization
DELETE /auth/delete-me (protected): • Loads the user doc by [Link]. • SHA■256 hashes PII (email,
firstName, lastName, avatar), sets isDeleted=true and anonymizedAt. • Note: Code currently comments
out disabling in Firebase Auth; consider disabling or deleting the auth user as a follow■up.
Security Considerations & Good Practices
• Always require HTTPS and set secure, strict headers (CSP, HSTS). • Store client IDs and secrets in
environment variables, not code. • Keep firebase-admin credentials scoped and rotated. • Use
verifyIdToken(..., true) (already used) to enforce revocation. • Consider role■based claims; validate them
inside guards. • Rate■limit public endpoints like /auth/social-login. • Log audit trails carefully (avoid logging
raw tokens in production).
Testing the Flow
1) Open /auth/login-ui and sign in with Google. 2) The page will call /auth/social-login and then
signInWithCustomToken(). 3) Copy the displayed ID token and call any protected API with Authorization:
Bearer . 4) Try calling the same API without the header to observe 401 from FirebaseAuthGuard.
Common Edge Cases
• Expired/Revoked Tokens: verifyIdToken(..., true) will reject; client should re-auth. • Partially missing
profile fields from providers: code defensively assigns empty strings. • Race on first login: ensure Firestore
upsert completes before issuing custom token (this flow already does).
API Quick Reference
POST /auth/social-login
{
"type": "FIREBASE" | "GOOGLE",
"token": "<id_token_from_provider_or_firebase>"
}
Response:
{
"firebaseToken": "<custom_token>",
"user": {
"uid": "...",
"email": "...",
"profile": {"firstName": "...", "lastName": "...", "avatar": "..."},
"createdAt": 1710000000000,
"updatedAt": 1710000000000
}
}
GET /auth/test-user-login
Returns a custom token for a fixed user (for debugging).
DELETE /auth/delete-me
Protected. Requires Authorization: Bearer . Soft■deletes & anonymizes Firestore user.
Guarded Routes
Any route without @PublicApi() is protected by FirebaseAuthGuard. The guard reads Authorization
header, verifies the ID token, and sets [Link] { uid, email, name }.
What Makes This Design Solid
• Strong Boundary: Only the backend can mint custom tokens; clients must present a valid
provider/Firebase token first. • Server■Side Source of Truth: Firestore upsert guarantees the user record
exists before issuing a session. • Simple Extensibility: Add Apple or other providers by implementing
verify*Token() and reusing the same flow. • Observability: Strategic logs around verification and upserts
(mindful of PII/tokens in production).
Next Steps & Enhancements
• Add roles/permissions to custom claims and enforce them in guards. • Add refresh logic and token
listeners on the client for more granular UX. • Implement hard delete/disable in Firebase Auth on account
deletion. • Move IDs/keys to environment variables, and add input validation on /auth/social-login.