Authentication Flow — End to End
This page explains how the authentication system works across both the Angular game client and the NestJS API. It covers session management, the sign-up lifecycle, and the confirmed account change flows.
For API payloads, see API Endpoints. For Angular service methods, see Game Reference.
Local Dev: No CORS Needed
In local development, the Angular dev server (localhost:4200) proxies all /api requests to the NestJS server (localhost:3000) via proxy.conf.json. The browser sees a single origin. No CORS headers are needed in dev.
In production, both frontend and backend run on td.traitor.app. The NestJS CORS config allows that origin with credentials.
Session Lifecycle
Better Auth uses cookie-based sessions. The session cookie is set by the API and read on every authenticated request.
AuthService exposes two signals that reflect the session state:
isLoggedIn: Signal<boolean>user: Signal<User | null>
checkSession() calls Better Auth's getSession endpoint. The client caches the session in a cookie by default. Pass forceFresh = true to bypass the cache and read from the database.
INCORRECT: Reading authService.user() after a profile change without refreshing.
CORRECT: Call await authService.checkSession(true) first to get the updated user from the database.
Sign-Up Flow
Login Flow
Password Reset Flow
AuthService.forgotPassword() tries requestPasswordReset first, then falls back to forgetPassword. Both map to the same Better Auth reset flow. The exact endpoint name depends on the Better Auth client version installed.
Profile Change Flow (Client → API → DB → Client)
All three confirmed change flows follow the same pattern: the client calls a profile endpoint, the API validates, persists a pending_account_change row, sends a mail, and the client later calls confirm-change with the token from the email.
After Confirmation — What Each Flow Does
| Flow | DB write | Session | requiresLogin |
|---|---|---|---|
| Profile (name/nickname) | user.name, user.nickname | Kept | false |
| Email (step 1: confirm old) | Advances stage, sends new mail | Kept | false |
| Email (step 2: verify new) | user.email, emailVerified = true | Kept | false |
| Password | account.password hash | All sessions deleted | true |
After a password confirmation, AuthService calls clearSession() and redirects to /auth/login.
Token Security
- Tokens are
32random bytes encoded as hex (64hex chars) - The API stores only the SHA-256 hash of the token in
pending_account_change.tokenHash - Tokens expire after 24 hours
- The API deletes expired rows before each validation check
Trusted Origins
Better Auth and the ProfileService validate that callbackURL origins are trusted. Only these origins are allowed in local development:
http://localhost:4200http://127.0.0.1:4200
INCORRECT: Passing callbackURL: window.location.href when window.location.href includes untrusted query params or a path that resolves to a different origin.
CORRECT: Use new URL('/auth/confirm-account-change', window.location.origin).toString().
AuthService already does this by default via buildUrl('/auth/confirm-account-change').