Tutorial

NestJS Integration

Protect NestJS controllers and methods with Idenplane guards, custom decorators, and role-based access control.

Installation

Terminal
npm install idenplane-sdk

Idenplane Config Provider

Provide the server config that verifyToken needs — no client instance to construct, since token verification is a standalone function.

src/auth/idenplane.module.ts
import { Module } from '@nestjs/common';
import type { IdenplaneServerConfig } from 'idenplane-sdk/server';
import { ConfigService } from '@nestjs/config';

export const IDENPLANE_CONFIG = 'IDENPLANE_CONFIG';

@Module({
  providers: [
    {
      provide: IDENPLANE_CONFIG,
      useFactory: (cfg: ConfigService): IdenplaneServerConfig => ({
        issuerUrl: cfg.getOrThrow('IDENPLANE_URL'),
        realm: cfg.getOrThrow('IDENPLANE_REALM'),
      }),
      inject: [ConfigService],
    },
  ],
  exports: [IDENPLANE_CONFIG],
})
export class IdenplaneModule {}

Idenplane Guard

Implement a NestJS CanActivate guard around verifyToken, which performs full JWKS signature verification and attaches the decoded payload to the request.

src/auth/auth.guard.ts
import {
  CanActivate, ExecutionContext, Inject,
  Injectable, UnauthorizedException,
} from '@nestjs/common';
import { verifyToken } from 'idenplane-sdk/server';
import type { IdenplaneServerConfig } from 'idenplane-sdk/server';
import { IDENPLANE_CONFIG } from './idenplane.module';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(@Inject(IDENPLANE_CONFIG) private readonly config: IdenplaneServerConfig) {}

  async canActivate(ctx: ExecutionContext): Promise<boolean> {
    const req = ctx.switchToHttp().getRequest();
    const token = this.extractToken(req);

    if (!token) throw new UnauthorizedException();

    try {
      req.user = await verifyToken(token, this.config);
      return true;
    } catch {
      throw new UnauthorizedException('Invalid token');
    }
  }

  private extractToken(req: any): string | undefined {
    const [type, token] = req.headers.authorization?.split(' ') ?? [];
    return type === 'Bearer' ? token : undefined;
  }
}

CurrentUser Decorator

Create a parameter decorator to cleanly extract the authenticated user inside controller methods.

src/auth/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const CurrentUser = createParamDecorator(
  (_data: unknown, ctx: ExecutionContext) =>
    ctx.switchToHttp().getRequest().user,
);

Role-Based Access Control

Use a Roles decorator together with a RolesGuard to restrict controller methods to specific roles.

src/auth/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
src/auth/roles.guard.ts
import {
  CanActivate, ExecutionContext, Injectable,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(ctx: ExecutionContext): boolean {
    const required = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
      ctx.getHandler(),
      ctx.getClass(),
    });
    if (!required?.length) return true;

    const { user } = ctx.switchToHttp().getRequest();
    return required.some((r) => user?.roles?.includes(r));
  }
}
src/users/users.controller.ts
@Controller('users')
@UseGuards(AuthGuard, RolesGuard)
export class UsersController {
  @Get()
  @Roles('admin')
  findAll() { return this.usersService.findAll(); }

  @Get('profile')
  // No @Roles — any authenticated user can access
  getProfile(@CurrentUser() user: any) {
    return user;
  }
}