0

I wrote a custom JWT guard from which I am throwing an error if I find that an access token is missing from the a request cookie.

Problem is, I want to return an UnauthorizedException with a custom message to my React client, instead of ForbiddenException which is now being returned.

What is the correct way to throw and catch an exception from a guard in a filter? Or is there a better approach?

I am following this suggestion

JwtAuthGuard.ts

const cookieExtractor = (req: Request) => {
  if (req && req.cookies) {
    return req.cookies['jwt'];
  }
};

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  private logger = new Logger(JwtAuthGuard.name);

  constructor() {
    super();
  }

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();

    try {
      const accessToken = ExtractJwt.fromExtractors([cookieExtractor])(request);

      if (!accessToken)
        throw new UnauthorizedException('Access token is not set');

      return this.activate(context);
    } catch (err) {
      this.logger.error((err as Error).message);
      return false;
    }
  }

  async activate(context: ExecutionContext): Promise<boolean> {
    return super.canActivate(context) as Promise<boolean>;
  }
}

AuthExceptionFilter.ts

@Catch(HttpException)
export class AuthExceptionFilter extends BaseExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    console.log('exception', exception);

    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });

    super.catch(exception, host);
  }
}

Controller.ts

  @Get('/status')
  @UseGuards(JwtAuthGuard)
  @UseFilters(AuthExceptionFilter)
  async status(@Req() req: Request) {
    return true;
  }

exception from filter's console.log

[Nest] 65933  - 02/24/2023, 10:44:38 AM   ERROR [JwtAuthGuard] Access token is not set
exception ForbiddenException: Forbidden resource
    at canActivateFn (/test-project/node_modules/@nestjs/core/router/router-execution-context.js:136:23)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    at /test-project/node_modules/@nestjs/core/router/router-execution-context.js:42:31
    at /test-project/node_modules/@nestjs/core/router/router-proxy.js:9:17 {
  response: {
    statusCode: 403,
    message: 'Forbidden resource',
    error: 'Forbidden'
  },
  status: 403,
  options: {}
}
asus
  • 1,427
  • 3
  • 25
  • 59

1 Answers1

0

You can just throw the exception from the guard and let the filter handle it. If a guard returns a false Nest will throw the ForbiddenException for you, but you're welcome to throw whatever you want. TheThrottlerGuard from @nestjs/throttler throws a custom exception to return a 429 automatically, for example.

Jay McDoniel
  • 57,339
  • 7
  • 135
  • 147
  • When I run the code I end up in `if (!accessToken)` if statement, the guard throws the error but doesnt return anything. Where am I returning false in the guard that fires the `ForbiddenException`? – asus Feb 24 '23 at 23:38
  • Are you still `try/catch`ing when throwing the exception? And if so are you re-throwing the exception? – Jay McDoniel Feb 24 '23 at 23:41
  • I am throwing inside the try/catch in guard like it is above. Should I be try/catching in the filter? – asus Feb 24 '23 at 23:48
  • ohh! Removing the try/catch now throws the `UnauthorizedException`. Seems like I shouldn't try/catch inside the guard's `canActivate` - but why? – asus Feb 24 '23 at 23:51
  • You would need to re-throw the error in the catch if you do try/catch. Just how error handling works – Jay McDoniel Feb 24 '23 at 23:53