nestjs/nest · error · ForbiddenException
Forbidden resource
Error message
Forbidden resource
What it means
After every authorization guard runs, the framework requires an explicit verdict: a guard either throws an HttpException or returns false; returning false makes the router throw ForbiddenException('Forbidden resource'), which surfaces as HTTP 403. This is the designed authorization-denied response, not a framework defect — the default message is generic because the guard chose not to explain itself.
Source
Thrown at packages/core/router/router-execution-context.ts:390
);
}
public createGuardsFn<TContext extends string = ContextType>(
guards: CanActivate[],
instance: Controller,
callback: (...args: any[]) => any,
contextType?: TContext,
): ((args: any[]) => Promise<void>) | null {
const canActivateFn = async (args: any[]) => {
const canActivate = await this.guardsConsumer.tryActivate<TContext>(
guards,
args,
instance,
callback,
contextType,
);
if (!canActivate) {
throw new ForbiddenException(FORBIDDEN_MESSAGE);
}
};
return guards.length ? canActivateFn : null;
}
public createPipesFn(
pipes: PipeTransform[],
paramsOptions: (ParamProperties & { metatype?: any })[],
) {
const pipesFn = async <TRequest, TResponse>(
args: any[],
req: TRequest,
res: TResponse,
next: Function,
) => {
const resolveParamValue = async (
param: ParamProperties & { metatype?: any },
) => {View on GitHub (pinned to dd75d7bd8c)
Solutions
- On the client: ensure credentials are sent (Authorization header / withCredentials cookie) and are valid for this route.
- In the guard, replace `return false` with a thrown, descriptive exception: `throw new ForbiddenException('You do not have the admin role')` or `UnauthorizedException()` for anonymous access.
- Whitelist public routes in the global guard (`Reflector` + `@Public()` decorator set) instead of returning false everywhere.
- Fix accidental falsy returns: `return user?.role === 'admin'` rather than returning the role itself.
Example fix
// before
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(ctx: ExecutionContext): boolean {
return false; // generic 'Forbidden resource'
}
}
// after
import { ForbiddenException } from '@nestjs/common';
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(ctx: ExecutionContext): boolean {
const { user } = ctx.switchToHttp().getRequest();
if (!user) throw new UnauthorizedException('Authentication required');
if (!user.roles.includes('admin'))
throw new ForbiddenException('Admin role required');
return true;
}
} Defensive patterns
Strategy: try-catch
Try / catch
// Give the generic 403 a real message at the boundary
import { Catch, ExceptionFilter, HttpException, ArgumentsHost } from '@nestjs/common';
@Catch(HttpException)
export class AuthResponseFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const res = host.switchToHttp().getResponse();
if (exception.getStatus?.() === 403 && exception.message === 'Forbidden resource') {
return res.status(403).json({ statusCode: 403, message: 'Insufficient permissions' });
}
const body = exception.getResponse();
res.status(exception.getStatus()).json(typeof body === 'string' ? { message: body } : body);
}
} Prevention
- Never `return false` from a guard without context — throw UnauthorizedException/ForbiddenException with a reason.
- Whitelist public routes via a @Public() metadata decorator checked inside the global guard.
- Return explicit booleans (`return user?.role === role`), never truthy/falsy passthrough values.
- On the client, treat 403 as expected and surface a permission message rather than retrying blindly.
When it happens
Trigger: A global or controller/method guard's `canActivate()` returns false (missing/invalid JWT, insufficient role, csrf failure, tenant mismatch); a guard returns a falsy value accidentally (`return user.role` where role is undefined); auth integration where the guard runs before the token is attached by the passport strategy.
Common situations: RolesGuard checking `user.roles.includes(requiredRole)` for unauthenticated requests where user is undefined; API clients hitting protected endpoints without the Authorization header; cookie-based auth in cross-origin setups where the cookie is not sent; new endpoints forgotten to be whitelisted in a global guard.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of nestjs/nest@dd75d7bd8c (2026-08-21).
Data as JSON: /api/errors/52910f3225e201db.
Report an issue: GitHub.