@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private jwt: JwtService, private reflector: Reflector) {}
async canActivate(ctx: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [ctx.getHandler(), ctx.getClass()]);
if (isPublic) return true;
const req = ctx.switchToHttp().getRequest();
const token = req.headers.authorization?.split(" ")[1];
if (!token) throw new UnauthorizedException();
try { req.user = await this.jwt.verifyAsync(token); }
catch { throw new UnauthorizedException(); }
return true;
}
}
Use a JWT guard registered globally, plus a @Public() decorator for the few open routes.
Login. Register JwtModule from @nestjs/jwt with a secret and an expiry time. A login endpoint checks the user's credentials. Then it returns a token with await jwtService.signAsync({ sub: user.id, roles }).
The guard:
- It takes the bearer token from the
Authorizationheader. - It checks the token with
verifyAsync. - It attaches the token's payload to
req.user. - If the token is missing or invalid, it throws
UnauthorizedException. The client gets 401 instead of the default 403.
Protected by default. Register the guard globally with { provide: APP_GUARD, useClass: JwtAuthGuard }. This way also gives the guard DI (dependency injection: Nest creates and passes in the services it needs).
Public routes. Make a @Public() decorator with SetMetadata(IS_PUBLIC_KEY, true). Put it on routes like login. The guard checks this flag with Reflector first and lets those requests through.
@nestjs/passport with a JWT strategy is another way to do the same job.
- On login, use
JwtModuleandJwtService.signAsyncto create the token. - The guard verifies the bearer token and sets
req.user. - Throw
UnauthorizedExceptionto return 401. - Register the guard globally with
APP_GUARD. - A
@Public()metadata flag lets a route skip the guard.