Skip to main content

Overview

Security decorators specify authentication and authorization requirements for controllers and endpoints. They integrate with your authentication middleware and generate accurate OpenAPI security documentation.

@Security

Requires authentication using specified security scheme(s).
function Security(
  name: string | { [name: string]: string[] },
  scopes?: string[]
): ClassDecorator & MethodDecorator
name
string | object
required
The security scheme name (from securityDefinitions in tsoa.json) or an object for AND security.
scopes
string[]
Required OAuth2/OpenID scopes for this endpoint.

Configuration

Define security schemes in your tsoa.json:
{
  "spec": {
    "securityDefinitions": {
      "api_key": {
        "type": "apiKey",
        "name": "X-API-Key",
        "in": "header"
      },
      "bearer_token": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      },
      "oauth2": {
        "type": "oauth2",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://example.com/oauth/authorize",
            "tokenUrl": "https://example.com/oauth/token",
            "scopes": {
              "read:users": "Read user information",
              "write:users": "Modify user information",
              "admin": "Administrative access"
            }
          }
        }
      }
    }
  }
}

Usage

API Key Authentication

import { Get, Route, Security } from 'tsoa';

@Route('api')
export class ApiController {
  @Security('api_key')
  @Get('data')
  public async getData(): Promise<Data[]> {
    // Only accessible with valid API key
    return await dataService.findAll();
  }
}

Bearer Token (JWT) Authentication

import { Get, Post, Route, Security, Request } from 'tsoa';

interface AuthenticatedRequest {
  user?: {
    id: string;
    email: string;
    role: string;
  };
}

@Route('users')
export class UserController {
  @Security('bearer_token')
  @Get('me')
  public async getCurrentUser(
    @Request() request: AuthenticatedRequest
  ): Promise<User> {
    // User is authenticated and available in request.user
    return await userService.findById(request.user.id);
  }
  
  @Security('bearer_token')
  @Post('me/profile')
  public async updateProfile(
    @Request() request: AuthenticatedRequest,
    @Body() profile: UpdateProfileRequest
  ): Promise<User> {
    return await userService.update(request.user.id, profile);
  }
}

OAuth2 with Scopes

import { Get, Post, Delete, Route, Security } from 'tsoa';

@Route('posts')
export class PostController {
  // Requires 'read:posts' scope
  @Security('oauth2', ['read:posts'])
  @Get()
  public async listPosts(): Promise<Post[]> {
    return await postService.findAll();
  }
  
  // Requires 'write:posts' scope
  @Security('oauth2', ['write:posts'])
  @Post()
  public async createPost(
    @Body() post: CreatePostRequest
  ): Promise<Post> {
    return await postService.create(post);
  }
  
  // Requires both 'write:posts' and 'delete:posts' scopes
  @Security('oauth2', ['write:posts', 'delete:posts'])
  @Delete('{postId}')
  public async deletePost(@Path() postId: string): Promise<void> {
    await postService.delete(postId);
  }
}

Controller-Level Security

Apply security to all methods in a controller:
import { Get, Post, Route, Security } from 'tsoa';

@Security('bearer_token')
@Route('account')
export class AccountController {
  // All methods require bearer token authentication
  
  @Get('profile')
  public async getProfile(): Promise<Profile> {
    return await profileService.get();
  }
  
  @Post('settings')
  public async updateSettings(
    @Body() settings: Settings
  ): Promise<Settings> {
    return await settingsService.update(settings);
  }
}

OR Security (Alternative Methods)

Multiple @Security decorators create an OR relationship:
import { Get, Route, Security } from 'tsoa';

@Route('api')
export class ApiController {
  // Accessible with EITHER api_key OR bearer_token
  @Security('api_key')
  @Security('bearer_token')
  @Get('data')
  public async getData(): Promise<Data[]> {
    return await dataService.findAll();
  }
  
  // Accessible with EITHER oauth2 OR api_key
  @Security('oauth2', ['read:data'])
  @Security('api_key')
  @Get('protected')
  public async getProtected(): Promise<any> {
    return await dataService.getProtected();
  }
}

AND Security (Multiple Requirements)

Pass an object to require multiple security schemes simultaneously:
import { Get, Route, Security } from 'tsoa';

@Route('admin')
export class AdminController {
  // Requires BOTH api_key AND oauth2
  @Security({
    api_key: [],
    oauth2: ['admin']
  })
  @Get('sensitive')
  public async getSensitive(): Promise<SensitiveData> {
    return await dataService.getSensitive();
  }
}

Complex Security Combinations

import { Get, Route, Security } from 'tsoa';

@Route('resources')
export class ResourceController {
  // (api_key AND bearer_token) OR oauth2 with admin scope
  @Security({
    api_key: [],
    bearer_token: []
  })
  @Security('oauth2', ['admin'])
  @Get('protected')
  public async getProtected(): Promise<Resource[]> {
    return await resourceService.findProtected();
  }
}

@NoSecurity

Explicitly marks an endpoint as not requiring security, even if the controller has security applied.
function NoSecurity(): ClassDecorator & MethodDecorator

Override Controller Security

import { Get, Route, Security, NoSecurity } from 'tsoa';

@Security('bearer_token')
@Route('api')
export class ApiController {
  // Requires authentication
  @Get('private')
  public async getPrivate(): Promise<Data> {
    return await dataService.getPrivate();
  }
  
  // Public endpoint - no authentication required
  @NoSecurity()
  @Get('public')
  public async getPublic(): Promise<Data> {
    return await dataService.getPublic();
  }
  
  // Public health check
  @NoSecurity()
  @Get('health')
  public async health(): Promise<{ status: string }> {
    return { status: 'ok' };
  }
}

Override Root Security

If you’ve configured rootSecurity in tsoa.json, use @NoSecurity to make specific endpoints public:
{
  "spec": {
    "rootSecurity": [
      { "bearer_token": [] }
    ]
  }
}
import { Get, Route, NoSecurity } from 'tsoa';

@Route('api')
export class ApiController {
  // Protected by rootSecurity
  @Get('data')
  public async getData(): Promise<Data[]> {
    return await dataService.findAll();
  }
  
  // Public - overrides rootSecurity
  @NoSecurity()
  @Get('status')
  public async getStatus(): Promise<{ status: string }> {
    return { status: 'operational' };
  }
}

Authentication Middleware

Implement authentication logic in your authentication module:

Express Example

// authentication.ts
import { Request } from 'express';
import * as jwt from 'jsonwebtoken';

export async function expressAuthentication(
  request: Request,
  securityName: string,
  scopes?: string[]
): Promise<any> {
  if (securityName === 'api_key') {
    const apiKey = request.header('X-API-Key');
    if (!apiKey) {
      throw new Error('No API key provided');
    }
    
    const isValid = await validateApiKey(apiKey);
    if (!isValid) {
      throw new Error('Invalid API key');
    }
    
    return { apiKey };
  }
  
  if (securityName === 'bearer_token') {
    const token = request.header('Authorization')?.replace('Bearer ', '');
    if (!token) {
      throw new Error('No token provided');
    }
    
    try {
      const decoded = jwt.verify(token, process.env.JWT_SECRET!);
      return decoded;
    } catch (err) {
      throw new Error('Invalid token');
    }
  }
  
  if (securityName === 'oauth2') {
    const token = request.header('Authorization')?.replace('Bearer ', '');
    if (!token) {
      throw new Error('No token provided');
    }
    
    const decoded = await verifyOAuthToken(token);
    
    // Check scopes
    if (scopes) {
      const userScopes = decoded.scopes || [];
      const hasRequiredScopes = scopes.every(scope => 
        userScopes.includes(scope)
      );
      
      if (!hasRequiredScopes) {
        throw new Error('Insufficient permissions');
      }
    }
    
    return decoded;
  }
  
  throw new Error('Unknown security scheme');
}

Configure in tsoa.json

{
  "routes": {
    "authenticationModule": "./src/authentication.ts"
  }
}

Common Security Schemes

API Key

{
  "api_key": {
    "type": "apiKey",
    "name": "X-API-Key",
    "in": "header"
  }
}

HTTP Bearer (JWT)

{
  "bearer_token": {
    "type": "http",
    "scheme": "bearer",
    "bearerFormat": "JWT"
  }
}

HTTP Basic

{
  "basic_auth": {
    "type": "http",
    "scheme": "basic"
  }
}

OAuth2 Authorization Code

{
  "oauth2": {
    "type": "oauth2",
    "flows": {
      "authorizationCode": {
        "authorizationUrl": "https://example.com/oauth/authorize",
        "tokenUrl": "https://example.com/oauth/token",
        "scopes": {
          "read": "Read access",
          "write": "Write access",
          "admin": "Admin access"
        }
      }
    }
  }
}

OpenID Connect

{
  "openid": {
    "type": "openIdConnect",
    "openIdConnectUrl": "https://example.com/.well-known/openid-configuration"
  }
}

See Also

Build docs developers (and LLMs) love