{"record":{"id":"52910f3225e201db","repo":"nestjs/nest","slug":"forbidden-resource-52910f","errorCode":null,"errorMessage":"Forbidden resource","messagePattern":"Forbidden resource","errorType":"http","errorClass":"ForbiddenException","httpStatus":403,"severity":"error","filePath":"packages/core/router/router-execution-context.ts","lineNumber":390,"sourceCode":"    );\n  }\n\n  public createGuardsFn<TContext extends string = ContextType>(\n    guards: CanActivate[],\n    instance: Controller,\n    callback: (...args: any[]) => any,\n    contextType?: TContext,\n  ): ((args: any[]) => Promise<void>) | null {\n    const canActivateFn = async (args: any[]) => {\n      const canActivate = await this.guardsConsumer.tryActivate<TContext>(\n        guards,\n        args,\n        instance,\n        callback,\n        contextType,\n      );\n      if (!canActivate) {\n        throw new ForbiddenException(FORBIDDEN_MESSAGE);\n      }\n    };\n    return guards.length ? canActivateFn : null;\n  }\n\n  public createPipesFn(\n    pipes: PipeTransform[],\n    paramsOptions: (ParamProperties & { metatype?: any })[],\n  ) {\n    const pipesFn = async <TRequest, TResponse>(\n      args: any[],\n      req: TRequest,\n      res: TResponse,\n      next: Function,\n    ) => {\n      const resolveParamValue = async (\n        param: ParamProperties & { metatype?: any },\n      ) => {","sourceCodeStart":372,"sourceCodeEnd":408,"githubUrl":"https://github.com/nestjs/nest/blob/dd75d7bd8c5e88048587e6768d36eb695f3e7a25/packages/core/router/router-execution-context.ts#L372-L408","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n@Injectable()\nexport class RolesGuard implements CanActivate {\n  canActivate(ctx: ExecutionContext): boolean {\n    return false; // generic 'Forbidden resource'\n  }\n}\n\n// after\nimport { ForbiddenException } from '@nestjs/common';\n\n@Injectable()\nexport class RolesGuard implements CanActivate {\n  canActivate(ctx: ExecutionContext): boolean {\n    const { user } = ctx.switchToHttp().getRequest();\n    if (!user) throw new UnauthorizedException('Authentication required');\n    if (!user.roles.includes('admin'))\n      throw new ForbiddenException('Admin role required');\n    return true;\n  }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Give the generic 403 a real message at the boundary\nimport { Catch, ExceptionFilter, HttpException, ArgumentsHost } from '@nestjs/common';\n\n@Catch(HttpException)\nexport class AuthResponseFilter implements ExceptionFilter {\n  catch(exception: HttpException, host: ArgumentsHost) {\n    const res = host.switchToHttp().getResponse();\n    if (exception.getStatus?.() === 403 && exception.message === 'Forbidden resource') {\n      return res.status(403).json({ statusCode: 403, message: 'Insufficient permissions' });\n    }\n    const body = exception.getResponse();\n    res.status(exception.getStatus()).json(typeof body === 'string' ? { message: body } : body);\n  }\n}","preventionTips":["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."],"tags":["guards","authorization","http-403","security"],"backgroundTag":"http-403-forbidden","analyzedSha":"dd75d7bd8c5e88048587e6768d36eb695f3e7a25","analyzedAt":"2026-08-21T19:39:39.867Z","contentChangedAt":"2026-08-21T19:39:39.867Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}