-
Notifications
You must be signed in to change notification settings - Fork 8
Description
Summary
When using tspec with NestJS mode (nestjs: true), the securityDefinitions config correctly generates components.securitySchemes in the OpenAPI spec, but there's no way to apply security requirements to individual API operations.
Current Behavior
1. securityDefinitions works correctly ✅
tspec.config.json:
{
"specPathGlobs": ["src/**/*.controller.ts"],
"nestjs": true,
"openapi": {
"securityDefinitions": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
}
}Generated OpenAPI spec:
{
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
}
}This works as expected! 🎉
2. No way to apply security to operations ❌
Problem with @ApiBearerAuth
In NestJS, we typically use @ApiBearerAuth() decorator to mark endpoints that require authentication:
import { ApiBearerAuth } from "@nestjs/swagger";
@Controller("users")
export class UserController {
@Get("me")
@ApiBearerAuth("bearerAuth") // This is ignored by tspec
async getCurrentUser(): Promise<User> {
// ...
}
}However, tspec's NestJS integration doesn't recognize @ApiBearerAuth decorator.
Problem with Custom Composite Decorators
We use a custom @Auth() decorator that combines guard and swagger decorator:
import { applyDecorators, UseGuards } from "@nestjs/common";
import { ApiBearerAuth } from "@nestjs/swagger";
import { SessionAuthGuard } from "../guards/session-auth.guard";
/**
* 인증이 필요한 엔드포인트에 적용하는 컴포지트 데코레이터.
* SessionAuthGuard와 ApiBearerAuth를 동시에 적용합니다.
*/
export function Auth(): MethodDecorator & ClassDecorator {
return applyDecorators(
UseGuards(SessionAuthGuard),
ApiBearerAuth("access-token"),
);
}Usage:
@Controller("users")
export class UserController {
@Get("me")
@Auth() // Custom decorator - not recognized by tspec
async getCurrentUser(): Promise<User> {
// ...
}
}Neither the direct @ApiBearerAuth nor the custom @Auth() decorator is recognized by tspec.
Expected:
{
"paths": {
"/users/me": {
"get": {
"security": [{ "bearerAuth": [] }]
}
}
}
}Actual:
{
"paths": {
"/users/me": {
"get": {
// No security field - lock icon doesn't appear in Swagger UI
}
}
}
}Documentation Reference
The NestJS Integration Guide mentions in the Limitations section:
"Interceptors/Guards: These are not reflected in the generated spec"
However, @ApiBearerAuth is a Swagger decorator (not a Guard), and it's listed under "Swagger Decorators" section but not actually supported.
Feature Request
Please consider adding support for one or more of the following:
Option 1: Support @ApiBearerAuth decorator
@Get("me")
@ApiBearerAuth("bearerAuth")
async getCurrentUser(): Promise<User> { }Option 2: Config option for custom auth decorator names
Allow users to specify custom decorator names that should be treated as security decorators:
{
"openapi": {
"securityDefinitions": {
"bearerAuth": { "type": "http", "scheme": "bearer" }
},
"authDecorators": {
"Auth": "bearerAuth",
"AdminAuth": "bearerAuth"
}
}
}Option 3: Custom decorator handler function
Allow users to provide a function that determines security for each operation:
const spec = await generateTspec({
nestjs: true,
resolveOperationSecurity: (controllerClass, methodName, decorators) => {
if (decorators.includes("Auth") || decorators.includes("ApiBearerAuth")) {
return [{ bearerAuth: [] }];
}
return undefined;
}
});Option 4: Global security with exclusions
Apply security to all operations by default, with ability to exclude specific ones:
{
"openapi": {
"securityDefinitions": {
"bearerAuth": { "type": "http", "scheme": "bearer" }
},
"security": [{ "bearerAuth": [] }],
"publicTags": ["Health", "Auth"] // Exclude these tags from security
}
}Current Workaround
We're manually adding security to operations at runtime:
const tspecDocument = await generateTspec();
// Add security to non-public endpoints
for (const path of Object.values(tspecDocument.paths)) {
for (const method of Object.values(path)) {
const operation = method as { tags?: string[]; security?: object[] };
const isPublic = operation.tags?.some(tag => ["Health", "Auth"].includes(tag));
if (!isPublic) {
operation.security = [{ "bearerAuth": [] }];
}
}
}This works but is not ideal.
Environment
- tspec version: 0.2.10
- Node.js version: 22.x
- NestJS version: 10.x
Thank you for the great library! 🙏