Skip to main content

Guard

Guards decide whether an RPC continues. They run after pre-handler middleware and before interceptors and the RPC method.

interface Guard<TRequest = any> {
activate(request: TRequest): Promise<boolean> | boolean;
}

activate receives the @grpc/grpc-js call. Return true to continue. Return false to finish the call with PERMISSION_DENIED. Throw a GrpcError to finish the call with that status.

Add @injectable() and bind the guard. @UseGuard() on the class applies to every RPC. @UseGuard() on a method applies to that RPC.

import {
type Guard,
RPC,
Service,
UnauthenticatedGrpcError,
UseGuard,
} from '@inversifyjs/grpc-core';
import { injectable } from 'inversify';

import {
type HeroRequest,
type HeroResponse,
heroServiceDefinition,
} from './loadHeroServiceDefinition.js';

interface HeroCall {
metadata: {
get(key: string): Array<string | Uint8Array>;
};
request: HeroRequest;
}

function isHeroCall(call: object): call is HeroCall {
if (!('metadata' in call) || !('request' in call)) {
return false;
}

const request: unknown = call.request;

return (
typeof request === 'object' &&
request !== null &&
'id' in request &&
typeof request.id === 'string'
);
}

@injectable()
export class HeroIdGuard implements Guard {
public activate(call: object): boolean {
if (!isHeroCall(call)) {
return false;
}

return call.request.id !== 'forbidden';
}
}

@injectable()
export class AuthenticationGuard implements Guard<HeroCall> {
public activate(call: HeroCall): boolean {
if (call.metadata.get('authorization').length === 0) {
throw new UnauthenticatedGrpcError();
}

return true;
}
}

@Service(heroServiceDefinition)
@UseGuard(HeroIdGuard)
export class GuardedHeroService {
@RPC('GetHero')
public getHero(call: { request: HeroRequest }): HeroResponse {
return {
name: call.request.id,
};
}
}

@Service(heroServiceDefinition)
@UseGuard(AuthenticationGuard)
export class AuthenticatedHeroService {
@RPC('GetHero')
public getHero(call: { request: HeroRequest }): HeroResponse {
return {
name: call.request.id,
};
}
}

HeroIdGuard allows every id except forbidden. AuthenticationGuard requires authorization metadata and throws UnauthenticatedGrpcError when it is missing.

Global guards use applyGlobalGuards() before build(). They run before class and method guards. Type activate's argument as object and narrow it when you register the guard globally. The adapter's global guard list is typed with the transport call, which is a smaller type than ServerUnaryCall.