nestjs/nest · error · RpcException

Forbidden resource

Error message

Forbidden resource

What it means

Thrown as an RpcException wrapping FORBIDDEN_MESSAGE ('Forbidden resource') from createGuardsFn() inside rpc-context-creator.ts. When an RPC handler is protected by one or more guards (@UseGuards), NestJS invokes each guard's canActivate(); if any returns false (or resolves falsy) the framework throws this RpcException, which the RPC exception filter then serializes to the client as an error status with the permission/forbidden semantics.

Source

Thrown at packages/microservices/context/rpc-context-creator.ts:162

    return Reflect.getMetadata(PARAMTYPES_METADATA, instance, callback.name);
  }

  public createGuardsFn<TContext extends string = ContextType>(
    guards: any[],
    instance: Controller,
    callback: (...args: unknown[]) => unknown,
    contextType?: TContext,
  ): Function | null {
    const canActivateFn = async (args: any[]) => {
      const canActivate = await this.guardsConsumer.tryActivate<TContext>(
        guards,
        args,
        instance,
        callback,
        contextType,
      );
      if (!canActivate) {
        throw new RpcException(FORBIDDEN_MESSAGE);
      }
    };
    return guards.length ? canActivateFn : null;
  }

  public getMetadata<TMetadata, TContext extends ContextType = ContextType>(
    instance: Controller,
    methodName: string,
    defaultCallMetadata: Record<string, any>,
    contextType: TContext,
  ): RpcHandlerMetadata {
    const cacheMetadata = this.handlerMetadataStorage.get(instance, methodName);
    if (cacheMetadata) {
      return cacheMetadata;
    }
    const metadata =
      this.contextUtils.reflectCallbackMetadata<TMetadata>(
        instance,

View on GitHub (pinned to 6ec0e2783d)

Solutions

  1. Verify the guard's canActivate() logic returns true for authorized callers — inspect token/role extraction.
  2. Ensure the client sends the credentials/metadata the guard expects (e.g. gRPC metadata, MQTT/RMQ headers) so canActivate() can succeed.
  3. Make the guard return an explicit boolean (true), not undefined/null/0, for the allow path.
  4. If the denial is expected, handle RpcException with status permission-denied on the client side.

Example fix

// before
@Injectable()
class RoleGuard implements CanActivate {
  canActivate(ctx: ExecutionContext): boolean {
    const req = ctx.switchToRpc().getContext();
    return req.user?.role === 'admin'; // undefined -> false -> 'Forbidden resource'
  }
}

// after
@Injectable()
class RoleGuard implements CanActivate {
  canActivate(ctx: ExecutionContext): boolean {
    const req = ctx.switchToRpc().getContext();
    return Boolean(req.user && req.user.role === 'admin');
  }
}
// and on the client: send the auth metadata so user is populated
Defensive patterns

Strategy: try-catch

Validate before calling

// No pre-call validation possible from the caller for a guard's decision;
// instead ensure the caller sends the credentials the guard inspects.
function withAuthMetadata<T>(payload: T, token: string) {
  return { data: payload, metadata: { authorization: `Bearer ${token}` } };
}

Type guard

import { RpcException } from '@nestjs/microservices';
const isForbiddenRpc = (e: unknown): boolean =>
  e instanceof RpcException && /Forbidden resource/.test(e?.message ?? '');

Try / catch

try {
  await firstValueFrom(client.send('getUser', payload));
} catch (e) {
  if (isForbiddenRpc(e)) {
    // redirect to login / refresh token / surface 403
  } else throw e;
}

Prevention

When it happens

Trigger: An @MessagePattern/@GrpcMethod handler guarded by @UseGuards where a guard's canActivate() returns false (e.g. role check failed, JWT missing or invalid, tenant mismatch). A guard that throws is different — throwing inside a guard surfaces a different error; this specific 'Forbidden resource' is the clean false-return path.

Common situations: Authorization guard rejects the call (missing/invalid token, insufficient role). Tenant-scoped guard where the request metadata does not match the resource's tenant. Misconfigured guard that always returns false during testing or because of a bug. Custom guards that return undefined/null instead of a boolean get coerced to falsy and trigger this.

Related errors


AI-assisted analysis of nestjs/nest@6ec0e2783d (2026-08-03). Data as JSON: /data/errors/72e934b70b12743c.json. Report an issue: GitHub.