Appearance
Auth Architecture
TT Time Tracker uses cookie-based sessions rather than JWTs. This article explains the design and its implications.
How it works
Authentication is handled by better-auth. After a successful Google OAuth flow:
- better-auth creates a
Sessionrecord in the database - It sets a
better-auth.session_tokenHTTP-only cookie on the browser - Every subsequent request sends the cookie automatically
- The
AuthGuardvalidates the session token against the database on each request
There is no token expiry managed client-side. Sessions expire server-side, and the client simply gets a 401 when that happens.
Why not JWTs?
JWTs are stateless — the server cannot revoke them before expiry. For a time-tracking app where admins may need to disable user access immediately (e.g. when an employee leaves), stateless tokens create a window where the user retains access.
Cookie-based sessions can be invalidated instantly by deleting the Session record. The next request from that browser gets a 401.
The credentials: 'include' requirement
Because sessions are stored in cookies, every HTTP request from the frontend must include credentials. The generated SDK client (src/api/client.ts) is configured with:
typescript
createClient({
baseUrl: import.meta.env.VITE_API_URL,
// ...
})And the better-auth Vue client is also configured with fetchOptions: { credentials: 'include' }. This must not be removed or the auth system breaks silently — requests will succeed but the session cookie won't be sent.
CORS
Because the frontend and API are on different origins in production, the API must allow the frontend's origin with credentials: true:
typescript
// services/api/src/main.ts
app.enableCors({
origin: process.env.CORS_ORIGINS?.split(',') ?? 'http://localhost:5173',
credentials: true,
})The CORS_ORIGINS environment variable must list the exact frontend origin (no trailing slash, no wildcards when credentials: true).
Session invalidation
Sessions are invalidated when:
- The user calls
POST /api/auth/signout - An admin revokes a session (via the better-auth admin API)
- The session record is deleted from the database directly
The AuthGuard returns 401 for any request with an invalid or expired session token. The frontend's router guard detects 401 responses and redirects to /login.