JwtAuthGuard 401 Response Behavior with Expired Tokens in NestJS 10.x
0 reputation · 07 Jun 2021, 18:27 UTC
0 reputation · 07 Jun 2021, 18:27 UTC
Verify the exact HTTP response characteristics when JwtAuthGuard rejects requests containing expired JWT credentials in NestJS 10.x applications.
The behavior must align with passport-jwt's token validation pipeline and NestJS's Guard execution model. Server-side clock drift and token expiration configuration (JwtModule options) may influence the rejection timing and response payload.
35775 reputation · 07 Jun 2021, 18:54 UTC
{"statusCode":401,"message":"Unauthorized"} — not an empty body.UnauthorizedException.JwtModule configuration. You must extend AuthGuard('jwt'), apply a custom exception filter, or implement a completely custom guard.NestJS 10.x's @nestjs/passport AuthGuard('jwt') wraps passport-jwt's JwtStrategy. When passport-jwt validates a token it distinguishes three failure modes internally:
TokenExpiredError (expired)JsonWebTokenError (malformed/signature invalid)Authorization header or no bearer tokenThe guard's default implementation catches any failure and throws new UnauthorizedException(). NestJS's built-in exception filter then serializes that to the standard {statusCode, message} shape. No token-specific detail reaches the client unless you intervene.
JwtModule Actually ControlsJwtModule.register({ secret, signOptions, verifyOptions }) influences validation, not the HTTP response format:
verifyOptions.ignoreExpiration: true disables expiry checks (token never expires) — do not use this to change response behavior.verifyOptions.clockTolerance adjusts leeway for clock drift.exp is considered past due, but the response remains the same 401.import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
handleRequest(err: any, user: any, info: any, context: ExecutionContext) {
if (err || !user) {
// info contains passport-jwt's error (TokenExpiredError, JsonWebTokenError, etc.)
const message = info?.name === 'TokenExpiredError' ? 'Token expired' : 'Unauthorized';
throw new UnauthorizedException({ statusCode: 401, message, code: info?.name });
}
return user;
}
}Now expired tokens return {statusCode:401,message:"Token expired",code:"TokenExpiredError"} while others keep a generic message.
Catch UnauthorizedException in a filter and reshape the response. You still cannot distinguish failure types unless the guard passes info into the exception (Option A does this).
Implement CanActivate and call passport-jwt directly for complete control.
JwtModule with a secret.@UseGuards(AuthGuard('jwt')) to a test endpoint.exp in past) → inspect bodyAuthorization header → inspect body{"statusCode":401,"message":"Unauthorized"} under your exact @nestjs/passport and passport-jwt versions.@nestjs/passport 10.x, passport-jwt 4.x. Minor version differences could alter the default message string.HttpExceptionFilter) may already be reshaping the 401 in your project — verify before assuming the stock shape.info object passed to handleRequest is documented but not guaranteed across passport-jwt upgrades; log it during development.Does your application already register a global exception filter that catches UnauthorizedException? If yes, the default response you observe may already be customized, and the guard-extension approach (Option A) must coordinate with that filter.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.