ProvenPin Install

NestJS JWT guard testing: working versions (tested 2026-10-18)

Issue and verify JWTs with @nestjs/jwt, protect a route with a Bearer-token AuthGuard, and assert 401 without a token and 200 with a valid one using supertest offline.

passed2 passed · 0 failed2026-10-18 03:58 UTC
Status
passed
Last passed
2026-10-18 03:58 UTC
Tests
2 / 2
Language
TypeScript
Runtime
node v22.23.3
pinned versions11
@nestjs/common@12.1.3@nestjs/core@12.1.3@nestjs/jwt@12.0.3@nestjs/platform-express@12.1.3@nestjs/testing@12.1.3reflect-metadata@0.2.2rxjs@7.8.2supertest@7.3.1@types/supertest@7.2.1@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 39095442401

The code

Exactly the files that passed. Your agent can pull them with get_recipe("nestjs-jwt-guard-testing") (install), or see them on GitHub.

package.json
{
  "name": "nestjs-jwt-guard-testing",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "@nestjs/common": "12.1.3",
    "@nestjs/core": "12.1.3",
    "@nestjs/jwt": "12.0.3",
    "@nestjs/platform-express": "12.1.3",
    "reflect-metadata": "0.2.2",
    "rxjs": "7.8.2"
  },
  "devDependencies": {
    "@nestjs/testing": "12.1.3",
    "@types/node": "26.6.5",
    "@types/supertest": "7.2.1",
    "supertest": "7.3.1",
    "tsx": "4.23.15"
  }
}
tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": false,
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true,
    "allowImportingTsExtensions": true,
    "noEmit": true,
    "types": ["node"]
  },
  "include": ["src", "test"]
}
src/auth.service.ts
import { Inject, Injectable } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";

export type SessionUser = { sub: string; username: string };

@Injectable()
export class AuthService {
  constructor(@Inject(JwtService) private readonly jwt: JwtService) {}

  login(username: string): { access_token: string } {
    const payload: SessionUser = { sub: username, username };
    return { access_token: this.jwt.sign(payload) };
  }

  verify(token: string): SessionUser {
    return this.jwt.verify<SessionUser>(token);
  }
}
src/auth.guard.ts
import {
  CanActivate,
  ExecutionContext,
  Inject,
  Injectable,
  UnauthorizedException,
} from "@nestjs/common";
import { AuthService, type SessionUser } from "./auth.service.ts";

type BearerRequest = {
  headers: { authorization?: string };
  user?: SessionUser;
};

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(@Inject(AuthService) private readonly auth: AuthService) {}

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest<BearerRequest>();
    const header = request.headers.authorization;
    if (typeof header !== "string" || !header.startsWith("Bearer ")) {
      throw new UnauthorizedException("Missing bearer token");
    }
    const token = header.slice("Bearer ".length).trim();
    try {
      request.user = this.auth.verify(token);
      return true;
    } catch {
      throw new UnauthorizedException("Invalid bearer token");
    }
  }
}
src/auth.controller.ts
import { Body, Controller, Get, Inject, Post, Request, UseGuards } from "@nestjs/common";
import { AuthService, type SessionUser } from "./auth.service.ts";
import { AuthGuard } from "./auth.guard.ts";

@Controller()
export class AuthController {
  constructor(@Inject(AuthService) private readonly auth: AuthService) {}

  @Post("login")
  login(@Body("username") username: string): { access_token: string } {
    return this.auth.login(username);
  }

  @UseGuards(AuthGuard)
  @Get("profile")
  profile(@Request() request: { user?: SessionUser }): { user: SessionUser | undefined } {
    return { user: request.user };
  }
}
src/app.module.ts
import { Module } from "@nestjs/common";
import { JwtModule } from "@nestjs/jwt";
import { AuthService } from "./auth.service.ts";
import { AuthController } from "./auth.controller.ts";
import { AuthGuard } from "./auth.guard.ts";

@Module({
  imports: [
    JwtModule.register({
      secret: "provenpin-test-secret",
      signOptions: { expiresIn: "1h" },
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService, AuthGuard],
})
export class AppModule {}
test/auth.e2e.test.ts
import "reflect-metadata";
import { test } from "node:test";
import assert from "node:assert/strict";
import request from "supertest";
import { Test } from "@nestjs/testing";
import type { INestApplication } from "@nestjs/common";
import { AppModule } from "../src/app.module.ts";

async function createApp(): Promise<INestApplication> {
  const moduleRef = await Test.createTestingModule({ imports: [AppModule] }).compile();
  const app = moduleRef.createNestApplication();
  await app.init();
  return app;
}

test("protected route returns 401 without a token", async (t) => {
  const app = await createApp();
  t.after(async () => {
    await app.close();
  });

  await request(app.getHttpServer()).get("/profile").expect(401);
});

test("login issues a JWT and the guard accepts it", async (t) => {
  const app = await createApp();
  t.after(async () => {
    await app.close();
  });

  const login = await request(app.getHttpServer())
    .post("/login")
    .send({ username: "ada" })
    .expect(201);

  assert.equal(typeof login.body.access_token, "string");
  assert.ok(login.body.access_token.length > 0);

  const profile = await request(app.getHttpServer())
    .get("/profile")
    .set("Authorization", `Bearer ${login.body.access_token}`)
    .expect(200);

  assert.equal(profile.body.user.username, "ada");
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.