Backend: Validating the Token
AuthService.validateSession
Two Knex queries: look the row up by session_token, then load its user by user_id. A missing token, a missing row, an expires in the past, or a missing user each throw UnauthorizedException. No JWT secret is involved, which is what lets the backend validate a session NextAuth issued.
Guards
SessionGuard and InternalApiTokenGuard live in backend/src/auth/; AdminGuard lives in backend/src/admin/admin.guard.ts.
The guards are REST CanActivate classes.
| Guard | Behavior |
|---|---|
SessionGuard |
Reads Authorization: Bearer <token>, calls AuthService.validateSession, attaches user to req. Throws 401 on any failure. |
AdminGuard |
Must be stacked after SessionGuard. Reads req.user.is_admin and throws 403 if missing. |
InternalApiTokenGuard |
Reads X-Internal-Token, hashes both sides and compares with timingSafeEqual against process.env.INTERNAL_API_TOKEN. Applied to the entire InternalAuthAdapterController. |
Stack like this:
@UseGuards(SessionGuard, AdminGuard)
@Get()
async list() { /* req.user is the admin */ }
@CurrentUser() is a small param decorator that pulls req.user out of the execution context, usable on any route guarded by SessionGuard.
Internal API Surface
The controller is @ApiExcludeController(), so these routes are absent from openapi.json and Swagger UI entirely; the frontend types them by hand in lib/internal-api-types.ts. Only internalApi() ever calls them, the token is never sent to the browser, and the routes are typically deployed on a private network.
Rotating INTERNAL_API_TOKEN: see Environment Variables.