Skip to main content

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.

Common Mistake

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

info

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

FlowDB writeSessionrequiresLogin
Profile (name/nickname)user.name, user.nicknameKeptfalse
Email (step 1: confirm old)Advances stage, sends new mailKeptfalse
Email (step 2: verify new)user.email, emailVerified = trueKeptfalse
Passwordaccount.password hashAll sessions deletedtrue

After a password confirmation, AuthService calls clearSession() and redirects to /auth/login.

Token Security

  • Tokens are 32 random bytes encoded as hex (64 hex 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:4200
  • http://127.0.0.1:4200
Common Mistake

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').