Role-based access control (RBAC) grants permissions according to a role assigned to an authenticated subject, and a Nest guard is the component that reads that role — or other request context — to decide whether a request may reach a route handler.
In simpler words
RBAC says “admins can do this, members can’t”; a guard is the code that checks that rule before your controller runs.
Two guards run globally on every request in this API: JwtAuthGuard decides who you are, RolesGuard decides what your role allows.
Explain why RolesGuard needs JwtAuthGuard to run first
Choose 401 vs 403 for a failing check
Two guards, two questions
Definition
A Nest guard implements canActivate and returns whether the current execution context may proceed, commonly using Reflector to read metadata — such as required roles — attached by decorators on the handler or class.
In simpler words
JwtAuthGuard answers “is there a valid session at all?”; RolesGuard answers “does that session’s role satisfy this route’s @Roles list?”
Both guards are registered globally via APP_GUARD in AuthModule, so every route is protected by default — a route opts out with @Public(), and a route narrows further with @Roles(...).
Order matters: RolesGuard reads request.user, so it only works because JwtAuthGuard already ran and attached that user during the same guard phase.
A member sees 403 here; an admin sees 204. Every other ticket route has no @Roles, so both roles may call it once authenticated.
Picking the right guard failure
Definition
A guard should reject a request with the HTTP status that matches the failed check: unauthenticated requests receive 401 Unauthorized, and authenticated-but-disallowed requests receive 403 Forbidden.
In simpler words
401 means “prove who you are”; 403 means “I know who you are, and the answer is still no.”
JwtAuthGuard.handleRequest throws UnauthorizedException when Passport could not validate a user — no cookie, an expired token, or an unknown subject all land here.
RolesGuard throws ForbiddenException only after it already has request.user and finds the role missing from the required list — never for a request with no session at all.
RolesGuard check
if (!user) throw new UnauthorizedException();
if (!roles.includes(user.role)) {
throw new ForbiddenException('Insufficient permissions');
}
Guarding against a missing user first keeps the 401/403 distinction correct even if RolesGuard ran without JwtAuthGuard.
UI hide ≠ authorization; never trust body.role
Definition
Authorization is a server-side decision based on the authenticated subject and route metadata; client UI state and client-supplied role fields are not security controls.
In simpler words
Hiding a Delete button is polish. Nest guards are the boundary. A forged JSON body claiming role: "admin" must never grant power.
Roles come from the verified user record / trusted JWT claims after JwtAuthGuard — not from the request body.
Frontend checks improve UX (disable buttons early) but any attacker can call the API directly with curl/Postman.
When adding a dangerous route, ask: which @Roles decorate it, and what Postman proof shows a member gets 403?