nestjs-auth-api-key
v0.0.1
Published
The API Key Guard provides a flexible and secure mechanism for protecting NestJS endpoints using API key authentication. It supports multiple authentication methods (header and query parameter), environment-based configuration, and seamless integration wi
Maintainers
Readme
Nestjs Auth API Key
Overview
The API Key Guard provides a flexible and secure mechanism for protecting NestJS endpoints using API key authentication. It supports multiple authentication methods (header and query parameter), environment-based configuration, and seamless integration with Swagger documentation.
Features
- ✅ Multiple Authentication Methods: Header-based and query parameter authentication
- ✅ Environment Variable Support: Automatic key resolution from environment variables
- ✅ Case Insensitive Comparison: Configurable case sensitivity for key validation
- ✅ Multiple Keys Support: Accept multiple valid API keys from different sources
- ✅ Swagger Integration: Automatic security scheme documentation
- ✅ Flexible Configuration: Per-endpoint or class-level configuration
Installation
The guard is built using NestJS core packages. Ensure you have the following dependencies:
npm install @nestjs/common @nestjs/core @nestjs/swaggerBasic Usage
1. Setup Swagger Security Schemes
First, configure the Swagger security schemes in your application bootstrap:
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { INestApplication } from '@nestjs/common';
const SWAGGER_SECURITY_SCHEME = {
API_KEY_HEADER: 'api-key-header',
API_KEY_QUERY: 'api-key-query',
} as const;
export const setupSwagger = (app: INestApplication) => {
const options = new DocumentBuilder()
.setTitle('Your API')
.setDescription('API Documentation')
.setVersion('1.0')
.addApiKey(
{
type: 'apiKey',
name: 'X-API-KEY',
in: 'header',
description: 'Enter your API key',
},
SWAGGER_SECURITY_SCHEME.API_KEY_HEADER,
)
.addApiKey(
{
type: 'apiKey',
name: 'api_key',
in: 'query',
description: 'Enter your API key',
},
SWAGGER_SECURITY_SCHEME.API_KEY_QUERY,
)
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('api-docs', app, document);
};2. Protect Individual Endpoints
Use the @ApiKeyProtected() decorator to protect specific endpoints:
import { Controller, Get } from '@nestjs/common';
import { ApiKeyProtected } from './guards';
@Controller('protected')
export class ProtectedController {
@Get()
@ApiKeyProtected({
keys: ['my-secret-key'],
})
findAll() {
return { message: 'Protected resource' };
}
}3. Protect Entire Controllers
Apply the decorator at the class level to protect all endpoints:
import { Controller, Get } from '@nestjs/common';
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
import { ApiKeyProtected } from './guards';
@Controller('admin')
@ApiKeyProtected({
allowQueryParam: true,
envKeysVariableNames: ['ADMIN_API_KEY'],
})
@ApiSecurity('api-key-header')
@ApiSecurity('api-key-query')
@ApiTags('Admin')
export class AdminController {
@Get('dashboard')
getDashboard() {
return { message: 'Admin dashboard' };
}
@Get('users')
getUsers() {
return { message: 'User list' };
}
}Configuration Options
The @ApiKeyProtected() decorator accepts a configuration object with the following options:
| Option | Type | Default | Description |
| ---------------------- | ---------- | ------------------------- | -------------------------------------------- |
| headerName | string | 'x-api-key' | Name of the header containing the API key |
| keys | string[] | [] | Explicit list of valid API keys |
| allowQueryParam | boolean | false | Enable authentication via query parameter |
| envKeysVariableNames | string[] | ['API_KEY', 'API_KEYS'] | Environment variable names to read keys from |
| caseInsensitive | boolean | true | Perform case-insensitive key comparison |
Configuration Examples
Using Explicit Keys
@ApiKeyProtected({
keys: ['key1', 'key2', 'key3'],
})Using Environment Variables
@ApiKeyProtected({
envKeysVariableNames: ['MY_SERVICE_API_KEY'],
})Environment file (.env):
MY_SERVICE_API_KEY=super-secret-keyMultiple Keys from Environment
You can provide multiple keys in a single environment variable, separated by comma, semicolon, or space:
API_KEYS=key1,key2,key3
# or
API_KEYS=key1;key2;key3
# or
API_KEYS=key1 key2 key3Query Parameter Authentication
@ApiKeyProtected({
allowQueryParam: true,
envKeysVariableNames: ['PUBLIC_API_KEY'],
})Access the endpoint with:
GET /api/data?api_key=your-key-hereCustom Header Name
@ApiKeyProtected({
headerName: 'X-Custom-Auth-Key',
keys: ['custom-key'],
})Access the endpoint with:
curl -H "X-Custom-Auth-Key: custom-key" http://localhost:3000/api/dataCase Sensitive Keys
@ApiKeyProtected({
keys: ['MySecretKey123'],
caseInsensitive: false,
})Authentication Methods
Header-based (Recommended)
Send the API key in the request header:
curl -H "X-API-KEY: your-api-key" http://localhost:3000/api/protectedQuery Parameter (Optional)
Enable with allowQueryParam: true and send the key as a query parameter:
curl "http://localhost:3000/api/protected?api_key=your-api-key"Supported query parameter names:
api_keyapiKeykey
Error Responses
Missing API Key (401 Unauthorized)
Request:
curl http://localhost:3000/api/protectedResponse:
{
"statusCode": 401,
"message": "API key header missing",
"error": "Unauthorized"
}Invalid API Key (403 Forbidden)
Request:
curl -H "X-API-KEY: wrong-key" http://localhost:3000/api/protectedResponse:
{
"statusCode": 403,
"message": "Invalid API key",
"error": "Forbidden"
}Advanced Usage
Base Controller Pattern
Create a base controller class with common authentication settings:
import { Controller } from '@nestjs/common';
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
import { ApiKeyProtected } from './guards';
@Controller('jobs')
@ApiKeyProtected({
allowQueryParam: true,
envKeysVariableNames: ['JOBS_API_KEY'],
})
@ApiSecurity('api-key-header')
@ApiSecurity('api-key-query')
@ApiTags('Jobs')
export abstract class JobsBaseController {}Extend this base controller in your feature controllers:
import { Get, Post, Body } from '@nestjs/common';
import { JobsBaseController } from './jobs.base.controller';
@Controller('jobs/sync')
export class SyncJobsController extends JobsBaseController {
@Post('start')
startSync(@Body() data: any) {
return { status: 'started' };
}
@Get('status')
getStatus() {
return { status: 'running' };
}
}All endpoints in SyncJobsController will automatically inherit the API key protection.
Combining with Other Guards
The API Key Guard can be combined with other guards:
import { Controller, Get, UseGuards } from '@nestjs/common';
import { ApiKeyProtected } from './guards';
import { RolesGuard } from './roles.guard';
import { Roles } from './roles.decorator';
@Controller('admin')
@ApiKeyProtected({ envKeysVariableNames: ['ADMIN_API_KEY'] })
@UseGuards(RolesGuard)
export class AdminController {
@Get('users')
@Roles('admin', 'superadmin')
getUsers() {
return { users: [] };
}
}Environment-specific Configuration
Use different API keys per environment:
@ApiKeyProtected({
envKeysVariableNames: [
process.env.NODE_ENV === 'production'
? 'PROD_API_KEY'
: 'DEV_API_KEY'
],
})Development (.env.development):
DEV_API_KEY=dev-key-123Production (.env.production):
PROD_API_KEY=prod-secure-key-xyzSecurity Best Practices
- Never hardcode API keys in your source code
- Use environment variables for key storage
- Rotate keys regularly in production environments
- Use HTTPS to prevent key interception
- Prefer header authentication over query parameters
- Enable query parameter authentication only when necessary (e.g., webhooks, file downloads)
- Use different keys for different environments
- Implement rate limiting alongside API key authentication
- Log authentication attempts for security monitoring
- Set short expiration times and implement key rotation policies
Testing
Unit Testing the Guard
import { ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import { ApiKeyGuard } from './api-key.guard';
describe('ApiKeyGuard', () => {
let guard: ApiKeyGuard;
let reflector: Reflector;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [ApiKeyGuard, Reflector],
}).compile();
guard = module.get<ApiKeyGuard>(ApiKeyGuard);
reflector = module.get<Reflector>(Reflector);
});
it('should allow valid API key', () => {
const mockContext = {
switchToHttp: () => ({
getRequest: () => ({
headers: { 'x-api-key': 'valid-key' },
}),
}),
getHandler: () => ({}),
getClass: () => ({}),
} as unknown as ExecutionContext;
jest.spyOn(reflector, 'get').mockReturnValue({
keys: ['valid-key'],
});
expect(guard.canActivate(mockContext)).toBe(true);
});
it('should reject invalid API key', () => {
const mockContext = {
switchToHttp: () => ({
getRequest: () => ({
headers: { 'x-api-key': 'invalid-key' },
}),
}),
getHandler: () => ({}),
getClass: () => ({}),
} as unknown as ExecutionContext;
jest.spyOn(reflector, 'get').mockReturnValue({
keys: ['valid-key'],
});
expect(() => guard.canActivate(mockContext)).toThrow();
});
});Integration Testing
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './app.module';
describe('API Key Authentication (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('should reject request without API key', () => {
return request(app.getHttpServer())
.get('/protected')
.expect(401);
});
it('should accept request with valid API key', () => {
return request(app.getHttpServer())
.get('/protected')
.set('X-API-KEY', 'valid-key')
.expect(200);
});
afterAll(async () => {
await app.close();
});
});Troubleshooting
Guard Not Working
Problem: Endpoints are accessible without API key.
Solutions:
- Ensure the guard is properly imported and applied
- Check that you're using
@ApiKeyProtected()(with parentheses) - Verify that either
keysorenvKeysVariableNamesare configured - Check environment variables are properly loaded
Keys Not Found from Environment
Problem: Authentication always fails despite correct key.
Solutions:
- Verify environment variables are loaded (use
console.log(process.env.YOUR_VAR)) - Check variable names match exactly (case-sensitive)
- Ensure
.envfile is in the correct location - Confirm environment variables are available at runtime
Case Sensitivity Issues
Problem: Same key works sometimes but not others.
Solution: By default, comparison is case-insensitive. If you need case-sensitive comparison:
@ApiKeyProtected({
keys: ['CaseSensitiveKey'],
caseInsensitive: false,
})Swagger Not Showing Security Options
Problem: Swagger UI doesn't show API key authentication.
Solutions:
- Ensure
@ApiSecurity()decorators are applied - Verify security schemes are registered in Swagger setup
- Check that scheme names match between setup and decorators
Migration Guide
From Basic Auth to API Key
Before:
@UseGuards(AuthGuard('basic'))After:
@ApiKeyProtected({
envKeysVariableNames: ['API_KEY'],
})
@ApiSecurity('api-key-header')From Custom Implementation
Before:
@UseGuards(CustomApiKeyGuard)
@SetMetadata('apiKeys', ['key1', 'key2'])After:
@ApiKeyProtected({
keys: ['key1', 'key2'],
})References
License
This implementation follows standard NestJS patterns and can be freely used in any NestJS project.
