nest-api-key-auth
v0.2.1
Published
A NestJS module for API key-based authentication with built-in security, scopes, and multiple database adapters
Maintainers
Readme
nest-api-key-auth
A comprehensive NestJS module for API key-based authentication with built-in security, scopes, and multiple database adapters.
Development Status: This library is actively being developed and is NOT ready for production use. While many features are implemented, extensive testing, security audits, and performance optimization are still required. The API may change in future versions.
Table of Contents
- Overview
- Features
- Installation
- Quick Start
- CLI Tool
- Configuration
- API Reference
- Database Support
- Advanced Features
- Rate Limiting
- Usage Quotas
- IP Whitelisting
- Audit Logging
- Caching
- Redis Support
- Usage Analytics
- Webhook Notifications
- Bulk Operations
- Expiration Monitoring
- Key Metadata
- Automated Rotation Policies
- Advanced Filtering and Querying
- Export/Import Functionality
- Request Signing (HMAC)
- Key Templates
- IP Blacklisting
- Revocation Reason Tracking
- Security Threat Detection
- Key History/Audit Trail
- Endpoint-Specific Rate Limiting
- GraphQL Support
- Express Middleware
- Key Groups/Teams
- Multi-Tenancy Support
- Key Versioning
- Usage Reports
- Circuit Breaker Pattern
- Key Cloning
- Key Transfer/Sharing
- Key Testing Endpoint
- Key Security Scoring
- Key Aliases
- Compliance Features
- Security
- Testing
- Logging
- Validation
- Documentation
- Contributing
- License
Overview
nest-api-key-auth is a comprehensive NestJS module that simplifies API key-based authentication. It provides a complete solution for managing API keys, protecting routes, and enforcing fine-grained permissions through scopes.
Key Benefits:
- Zero boilerplate - Get started in minutes
- Database-agnostic - Works with any database through ORM adapters
- Type-safe - Full TypeScript support
- Secure by default - Built-in hashing, validation, and security features
- Highly configurable - Customize every aspect of the authentication flow
- Comprehensive feature set - Rate limiting, quotas, analytics, webhooks, and more
Features
Core Features:
- API key creation with secure hashing (bcrypt or argon2)
- Route protection with
@ApiKeyAuth()decorator - Scope-based permissions with
@Scopes()decorator - Multiple key sources (headers, query params, cookies)
- Key expiration dates and last used tracking
- Key management (create, find, list, revoke, rotate)
- Key rotation with grace period support
Advanced Features:
- Rate limiting - Per-key rate limiting with configurable limits
- Endpoint-specific rate limiting - Per-route rate limits with decorator support
- Usage quotas - Per-key usage limits (daily, monthly, yearly) with automatic reset
- Redis support - Distributed rate limiting and caching with Redis (with in-memory fallback)
- IP whitelisting - Restrict keys to specific IP addresses or CIDR ranges
- IP blacklisting - Block specific IP addresses or CIDR ranges (takes precedence over whitelisting)
- Audit logging - Comprehensive request logging for security and compliance (with database storage)
- Key history/audit trail - Track all key changes, modification history, and rollback capability
- Security threat detection - Anomaly detection, brute force protection, and suspicious activity alerts
- Caching layer - In-memory or Redis-based caching for improved performance
- Usage analytics - Track API key usage, performance metrics, and request statistics
- Webhook notifications - Real-time notifications for key events (create, revoke, rotate, expire)
- Bulk operations - Create or revoke multiple API keys in a single operation
- Expiration monitoring - Automatic monitoring and notifications for expiring keys
- CLI tool - Command-line interface for managing API keys directly from terminal
- Database audit logging - Store audit logs in database with query and analytics capabilities
- Key metadata - Store custom metadata, tags, owner, environment, and descriptions for better organization
- Automated rotation policies - Schedule and automate API key rotations based on policies
- Advanced filtering - Query API keys by tags, owner, environment, scopes, and more
- Export/Import - Export key configurations and import metadata for backup and migration
- Request signing (HMAC) - Support for HMAC signature verification for enhanced security
- Key templates - Define reusable API key configurations and presets
- Revocation reason tracking - Store reasons for API key revocation for audit purposes
- GraphQL support - Guards for GraphQL resolvers with context integration
- Express middleware - Standalone middleware for non-NestJS applications
- Key groups/teams - Group management, group-level permissions, and bulk operations
- Multi-tenancy support - Tenant isolation, tenant-scoped queries, and tenant-level limits
- Key versioning - Track versions, version history, and rollback capability
- Usage reports - Generate PDF/CSV reports for compliance and analytics
- Circuit breaker pattern - Resilience for database failures with automatic recovery
- Key cloning - Clone keys with same permissions/metadata
- Key sharing/transfer - Transfer ownership between users/teams
- Key testing endpoint - Test key validity without real requests
- Key security scoring - Risk score based on usage patterns
- Key aliases - Multiple names for the same key
- Compliance features - GDPR export, SOC2 audit trails, and data retention
Developer Experience:
- Comprehensive input validation (token format, scope format, configuration)
- Robust error handling with detailed logging
- Multiple ORM support (Prisma, TypeORM, Mongoose, Custom)
- PrismaClient injection support
- Built-in logging mechanism
- Health check functionality
- Comprehensive test suite
- Ready-to-use migration files
- Example implementations
Installation
npm install nest-api-key-auth @prisma/client
npm install -D prismaPeer Dependencies:
@nestjs/common^10.0.0@nestjs/core^10.0.0reflect-metadata^0.1.13rxjs^7.8.0
Optional Dependencies:
@prisma/client^7.0.0 (for Prisma adapter)typeorm(for TypeORM adapter)mongoose(for Mongoose adapter)ioredis(for Redis support)
Quick Start
Basic Setup
- Initialize Prisma (if using Prisma adapter):
npx prisma init- Update your
schema.prismato include the API key schema, or run:
npx prisma migrate dev --name add_api_keys- Generate Prisma Client:
npx prisma generate- Import the Module:
import { Module } from '@nestjs/common';
import { ApiKeyModule } from 'nest-api-key-auth';
@Module({
imports: [
ApiKeyModule.register({
adapter: 'prisma', // Optional: 'prisma' is the default
secretLength: 32,
headerName: 'x-api-key',
}),
],
})
export class AppModule {}Protect Routes
import { Controller, Get, Post } from '@nestjs/common';
import { ApiKeyAuth, Scopes } from 'nest-api-key-auth';
@Controller('projects')
export class ProjectController {
@Get()
@ApiKeyAuth()
findAll() {
return [];
}
@Get(':id')
@ApiKeyAuth()
@Scopes('read:projects')
findOne() {
return {};
}
@Post()
@ApiKeyAuth()
@Scopes('write:projects')
create() {
return {};
}
}Create API Keys
import { Injectable } from '@nestjs/common';
import { ApiKeyService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly apiKeyService: ApiKeyService) {}
async createKey() {
const result = await this.apiKeyService.create({
name: 'My App',
scopes: ['read:projects'], // Optional
expiresAt: new Date('2025-12-31'), // Optional: key expiration date
});
console.log('API Key created:', result.token);
// Store this token securely - it's only shown once!
return result;
}
async listKeys() {
const activeKeys = await this.apiKeyService.findAllActive();
return activeKeys;
}
async revokeKey(id: string) {
return await this.apiKeyService.revoke(id);
}
}Use API Keys
API keys can be provided via headers, query parameters, or cookies:
# Header (recommended)
curl -H "x-api-key: your-api-key-here" http://localhost:3000/projects
# Query parameter
curl "http://localhost:3000/projects?api_key=your-api-key-here"
# Cookie
curl -H "Cookie: api_key=your-api-key-here" http://localhost:3000/projectsCLI Tool
The library includes a command-line tool for managing API keys directly from your terminal:
# Create a new API key
npx nest-api-key create --name "My App" --scopes "read:projects,write:projects"
# List all API keys
npx nest-api-key list
# List including revoked keys
npx nest-api-key list --all
# Revoke an API key
npx nest-api-key revoke <key-id>
# Rotate an API key (create new, optionally revoke old)
npx nest-api-key rotate <key-id> --revoke-old
# Set database URL via environment variable
export DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
npx nest-api-key create --name "My App"
# Or pass database URL directly
npx nest-api-key create --name "My App" --db-url "postgresql://..."CLI Features:
- Direct database access (no NestJS app required)
- Create, list, revoke, and rotate API keys
- Support for all key features (scopes, expiration, IP whitelisting, rate limits)
- Works with Prisma adapter (TypeORM and Mongoose support coming soon)
Configuration
Module Options
ApiKeyModule.register({
// Basic configuration
secretLength: 32, // Length of generated API keys
headerName: 'x-api-key', // Header name for API keys
queryParamName: 'api_key', // Query parameter name
cookieName: 'api_key', // Cookie name
// Adapter configuration
adapter: 'prisma', // 'prisma' | 'typeorm' | 'mongoose' | 'custom'
prismaClient: prisma, // Optional: use existing PrismaClient instance
typeOrmRepository: repository, // Required for TypeORM adapter
mongooseModel: model, // Required for Mongoose adapter
customAdapter: adapter, // Required for custom adapter
// Hashing configuration
hashAlgorithm: 'bcrypt', // 'bcrypt' | 'argon2'
bcryptRounds: 10, // Only used when hashAlgorithm is 'bcrypt'
// Feature flags
enableRateLimiting: true,
enableAuditLogging: true,
enableCaching: true,
enableAnalytics: true,
enableWebhooks: true,
// Cache configuration
cacheTtlMs: 300000, // Cache TTL in milliseconds (default: 5 minutes)
// Redis configuration
redisClient: redis, // Optional: Redis client for distributed rate limiting/caching
// Audit logging configuration
auditLogOptions: {
logToConsole: true,
logToDatabase: false,
retentionDays: 90,
onLog: async (entry) => {
// Custom logging logic
},
},
// Webhook configuration
webhooks: [
{
url: 'https://your-app.com/webhooks/api-keys',
secret: 'your-webhook-secret',
events: ['key.created', 'key.revoked', 'key.rotated', 'key.expired'],
retryAttempts: 3,
timeout: 5000,
},
],
// Expiration notification configuration
expirationNotificationOptions: {
enabled: true,
checkIntervalMs: 3600000, // 1 hour
warningThresholdsDays: [30, 7, 1],
},
});Adapter Selection
Prisma (Default):
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
@Module({
imports: [
ApiKeyModule.register({
adapter: 'prisma',
prismaClient: prisma, // Optional: use existing instance
}),
],
})
export class AppModule {}TypeORM:
import { getRepositoryToken } from '@nestjs/typeorm';
import { ApiKeyEntity } from './entities/api-key.entity';
@Module({
imports: [
TypeOrmModule.forFeature([ApiKeyEntity]),
ApiKeyModule.register({
adapter: 'typeorm',
typeOrmRepository: getRepositoryToken(ApiKeyEntity),
}),
],
})
export class AppModule {}Mongoose:
import { getModelToken } from '@nestjs/mongoose';
@Module({
imports: [
MongooseModule.forFeature([{ name: 'ApiKey', schema: ApiKeySchema }]),
ApiKeyModule.register({
adapter: 'mongoose',
mongooseModel: getModelToken('ApiKey'),
}),
],
})
export class AppModule {}Custom Adapter:
import { IApiKeyAdapter } from 'nest-api-key-auth';
class MyCustomAdapter implements IApiKeyAdapter {
// Implement all required methods
}
@Module({
imports: [
ApiKeyModule.register({
adapter: 'custom',
customAdapter: new MyCustomAdapter(),
}),
],
})
export class AppModule {}Custom Key Sources
Configure which sources to check for API keys:
ApiKeyModule.register({
headerName: 'x-api-key', // Custom header name
queryParamName: 'api_key', // Custom query param name
cookieName: 'api_key', // Custom cookie name
});API Reference
ApiKeyService
create(dto: CreateApiKeyDto): Promise<CreateApiKeyResponse>
Creates a new API key. Returns the plaintext token only once - store it securely!
const key = await apiKeyService.create({
name: 'My App',
scopes: ['read:projects'], // Optional
expiresAt: new Date('2025-12-31'), // Optional: expiration date
ipWhitelist: ['192.168.1.0/24'], // Optional: IP restrictions
ipBlacklist: ['192.168.1.100'], // Optional: IP blacklist (takes precedence over whitelist)
rateLimitMax: 1000, // Optional: max requests per window
rateLimitWindowMs: 60000, // Optional: time window in milliseconds
quotaMax: 10000, // Optional: maximum requests per quota period
quotaPeriod: 'daily', // Optional: 'daily', 'monthly', or 'yearly'
metadata: { appVersion: '1.0.0', region: 'us-east-1' }, // Optional: custom metadata
tags: ['production', 'api-v2'], // Optional: tags for organization
owner: 'team-backend', // Optional: owner identifier
environment: 'production', // Optional: 'production', 'staging', or 'development'
description: 'API key for production mobile app', // Optional: description
});
console.log(key.token); // Store this securely!Throws:
BadRequestExceptionif name is empty, scopes are invalid, or IP format is invalid
findById(id: string): Promise<ApiKey>
Finds an API key by its ID.
const key = await apiKeyService.findById('key-id-123');
console.log(key.name, key.scopes);Throws:
BadRequestExceptionif id is invalidApiKeyNotFoundExceptionif key doesn't exist
findAll(): Promise<ApiKey[]>
Retrieves all API keys, including revoked ones.
const allKeys = await apiKeyService.findAll();
console.log(`Total keys: ${allKeys.length}`);findAllActive(): Promise<ApiKey[]>
Retrieves only active (non-revoked) API keys.
const activeKeys = await apiKeyService.findAllActive();
console.log(`Active keys: ${activeKeys.length}`);revoke(id: string, options?: { reason?: string }): Promise<ApiKey>
Revokes an API key by ID. Once revoked, the key can no longer be used.
const revokedKey = await apiKeyService.revoke('key-id-123', {
reason: 'Security breach detected',
});
console.log(`Revoked at: ${revokedKey.revokedAt}`);
console.log(`Reason: ${revokedKey.revocationReason}`);Throws:
BadRequestExceptionif id is invalidApiKeyNotFoundExceptionif key doesn't existApiKeyAlreadyRevokedExceptionif key is already revoked
rotate(oldKeyId: string, options?: RotateOptions): Promise<CreateApiKeyResponse>
Rotates an API key by creating a new key and optionally revoking the old one.
const newKey = await apiKeyService.rotate('old-key-id', {
revokeOldKey: true,
gracePeriodHours: 24, // Optional: grace period before revoking old key
});Decorators
@ApiKeyAuth()
Protects a route with API key authentication. The validated API key data is attached to request.apiKey.
@ApiKeyAuth()
@Get()
findAll(@Req() req: Request) {
console.log(req.apiKey.name); // Access the API key data
return [];
}@Scopes(...scopes: string[])
Specifies required scopes for a route. Can be used with @ApiKeyAuth() to enforce fine-grained permissions. Multiple scopes are treated as AND (all required).
@ApiKeyAuth()
@Scopes('read:projects')
@Get('projects')
getProjects() {
return [];
}
@ApiKeyAuth()
@Scopes('read:projects', 'write:projects')
@Post('projects')
createProject() {
return {};
}Throws:
ForbiddenExceptionif the API key lacks required scopes
Exceptions
The library provides custom exceptions for better error handling:
ApiKeyNotFoundException- Thrown when an API key is not foundApiKeyAlreadyRevokedException- Thrown when trying to revoke an already revoked key
Database Support
The library is database-agnostic and works with any database through ORM adapters. You configure your database in your ORM, and the library works with it.
Supported Databases
Via Prisma Adapter:
- PostgreSQL
- MySQL
- SQLite
- MongoDB
- SQL Server
- CockroachDB
- And any database Prisma supports
Via TypeORM Adapter:
- PostgreSQL
- MySQL
- MariaDB
- SQLite
- SQL Server
- Oracle
- MongoDB (with limitations)
- And any database TypeORM supports
Via Mongoose Adapter:
- MongoDB
Database Configuration
The library doesn't care which database you use - you configure it in your ORM:
Prisma Example (PostgreSQL):
datasource db {
provider = "postgresql"
// DATABASE_URL in .env
}Prisma Example (MySQL):
datasource db {
provider = "mysql"
// DATABASE_URL in .env
}Prisma Example (MongoDB):
datasource db {
provider = "mongodb"
// DATABASE_URL in .env
}See Database Configuration Examples for complete examples for each database.
Database Schema
The ApiKey table/collection contains:
id: Unique identifiername: Human-readable namekeyPrefix: First 8 characters for fast lookuphashedKey: Hashed key (bcrypt or argon2, depending onhashAlgorithmconfiguration)scopes: Array of permission scopesexpiresAt: Expiration timestamp (null if no expiration)revokedAt: Revocation timestamp (null if active)lastUsedAt: Last usage timestamp (null if never used)ipWhitelist: Array of allowed IP addressesipBlacklist: Array of blocked IP addresses (takes precedence over whitelist)rateLimitMax: Maximum requests per windowrateLimitWindowMs: Rate limit window in millisecondsrevocationReason: Reason for revocation (null if active)quotaMax: Maximum requests allowed within a quota period (null if no quota)quotaPeriod: Quota period - 'daily', 'monthly', or 'yearly' (null if no quota)quotaUsed: Number of requests used in current quota periodquotaResetAt: Timestamp when quota resetsmetadata: Custom metadata as JSON object (null if none)tags: Array of tags for organizationowner: Owner identifier (null if none)environment: Environment - 'production', 'staging', or 'development' (null if none)description: Description of the API key (null if none)createdAt: Creation timestampupdatedAt: Last update timestamp
Important: After updating the schema, run:
# For Prisma
npx prisma migrate dev --name add_api_keys
npx prisma generate
# For TypeORM
npm run typeorm migration:run
# For Mongoose
# Schema is automatically created on first useORM Setup Guides
TypeORM Setup:
- Create your entity:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity('api_keys')
export class ApiKeyEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@Column()
keyPrefix: string;
@Column()
hashedKey: string;
@Column('simple-array')
scopes: string[];
@Column({ nullable: true })
expiresAt: Date;
@Column({ nullable: true })
revokedAt: Date;
@Column({ nullable: true })
lastUsedAt: Date;
@Column()
createdAt: Date;
@Column()
updatedAt: Date;
}- Register the module:
@Module({
imports: [
TypeOrmModule.forFeature([ApiKeyEntity]),
ApiKeyModule.register({
adapter: 'typeorm',
typeOrmRepository: getRepositoryToken(ApiKeyEntity),
}),
],
})
export class AppModule {}Mongoose Setup:
- Create your schema:
import { Schema } from 'mongoose';
export const ApiKeySchema = new Schema({
name: String,
keyPrefix: String,
hashedKey: String,
scopes: [String],
expiresAt: Date,
revokedAt: Date,
lastUsedAt: Date,
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now },
});- Register the module:
@Module({
imports: [
MongooseModule.forFeature([{ name: 'ApiKey', schema: ApiKeySchema }]),
ApiKeyModule.register({
adapter: 'mongoose',
mongooseModel: getModelToken('ApiKey'),
}),
],
})
export class AppModule {}Advanced Features
Rate Limiting
Per-key rate limiting with configurable limits:
const key = await apiKeyService.create({
name: 'My App',
rateLimitMax: 1000, // Max requests
rateLimitWindowMs: 60000, // Per minute
});IP Whitelisting
Restrict keys to specific IP addresses or CIDR ranges:
const key = await apiKeyService.create({
name: 'My App',
ipWhitelist: ['192.168.1.0/24', '10.0.0.1'],
});Audit Logging
Comprehensive request logging for security and compliance:
ApiKeyModule.register({
enableAuditLogging: true,
auditLogOptions: {
logToConsole: true,
logToDatabase: true, // Enable database storage
retentionDays: 90,
},
});Query Audit Logs:
import { AuditLogService, AUDIT_LOG_SERVICE_TOKEN } from 'nest-api-key-auth';
@Injectable()
export class AuditService {
constructor(@Inject(AUDIT_LOG_SERVICE_TOKEN) private readonly auditLogService: AuditLogService) {}
async getKeyLogs(keyId: string) {
return await this.auditLogService.query({ keyId, limit: 100 });
}
async getStats() {
return await this.auditLogService.getStats();
}
}Caching
In-memory or Redis-based caching for improved performance:
ApiKeyModule.register({
enableCaching: true,
cacheTtlMs: 300000, // 5 minutes
});Redis Support
For distributed systems with multiple instances, use Redis for rate limiting and caching:
Installation:
npm install ioredis
npm install -D @types/ioredisConfiguration:
import Redis from 'ioredis';
import { ApiKeyModule } from 'nest-api-key-auth';
const redis = new Redis({
host: 'localhost',
port: 6379,
});
@Module({
imports: [
ApiKeyModule.register({
redisClient: redis,
enableRateLimiting: true,
enableCaching: true,
}),
],
})
export class AppModule {}When redisClient is provided, the library automatically uses Redis for:
- Rate limiting: Distributed rate limit tracking across all instances
- Caching: Shared cache across all instances
If Redis is unavailable, the library automatically falls back to in-memory implementations.
Usage Quotas
Enforce usage quotas per API key with automatic reset:
import { QuotaService, QUOTA_SERVICE_TOKEN } from 'nest-api-key-auth';
import { Inject } from '@nestjs/common';
@Injectable()
export class AppService {
constructor(@Inject(QUOTA_SERVICE_TOKEN) private readonly quotaService: QuotaService) {}
async checkQuota(keyId: string) {
const status = await this.quotaService.getQuotaStatus(keyId);
if (status && !status.allowed) {
throw new Error(`Quota exceeded: ${status.used}/${status.limit}`);
}
}
}Create API key with quota:
const key = await apiKeyService.create({
name: 'Limited API Key',
quotaMax: 10000, // Maximum requests
quotaPeriod: 'daily', // Reset daily ('daily', 'monthly', or 'yearly')
});Enable quota limiting in module:
ApiKeyModule.register({
enableQuotaLimiting: true,
// ... other options
});The quota service automatically:
- Tracks usage per API key
- Resets quotas at the end of each period (daily, monthly, yearly)
- Supports Redis for distributed quota tracking
- Falls back to in-memory tracking if Redis is unavailable
Usage Analytics
Track API key usage and performance metrics:
ApiKeyModule.register({
enableAnalytics: true,
});Using Analytics Service:
import { AnalyticsService, ANALYTICS_SERVICE_TOKEN } from 'nest-api-key-auth';
@Controller('analytics')
export class AnalyticsController {
constructor(
@Inject(ANALYTICS_SERVICE_TOKEN) private readonly analyticsService: AnalyticsService,
) {}
@Get('key/:keyId')
async getKeyMetrics(@Param('keyId') keyId: string) {
return await this.analyticsService.getKeyMetrics(keyId);
}
@Get('overview')
async getAnalytics() {
return await this.analyticsService.getAnalytics();
}
}Metrics tracked:
- Request counts (total, success, failure)
- Response times
- Last used timestamps
- Error rates
- Top performing keys
Webhook Notifications
Receive real-time notifications for API key events:
Configuration:
ApiKeyModule.register({
enableWebhooks: true,
webhooks: [
{
url: 'https://your-app.com/webhooks/api-keys',
secret: 'your-webhook-secret',
events: ['key.created', 'key.revoked', 'key.rotated', 'key.expired', 'key.expiring'],
retryAttempts: 3,
timeout: 5000,
},
],
});Supported Events:
key.created- When a new API key is createdkey.revoked- When an API key is revokedkey.rotated- When an API key is rotatedkey.expired- When an API key expireskey.expiring- When an API key is about to expire (configurable thresholds)
Webhook Payload Format:
{
"event": "key.created",
"timestamp": "2024-01-01T00:00:00.000Z",
"data": {
"keyId": "key-123",
"keyName": "My API Key",
"scopes": ["read:projects"],
"expiresAt": "2025-01-01T00:00:00.000Z"
}
}Bulk Operations
Create or revoke multiple API keys efficiently:
import { BulkOperationsService } from 'nest-api-key-auth';
@Injectable()
export class KeyManagementService {
constructor(private readonly bulkOps: BulkOperationsService) {}
async createMultipleKeys(names: string[]) {
const result = await this.bulkOps.bulkCreate(
names.map((name) => ({ name, scopes: ['read:projects'] })),
);
return result;
}
async revokeMultipleKeys(keyIds: string[]) {
const result = await this.bulkOps.bulkRevoke(keyIds);
return result;
}
}Expiration Monitoring
Automatically monitor and notify about expiring API keys:
import { ExpirationNotificationService } from 'nest-api-key-auth';
@Injectable()
export class AppService implements OnModuleInit {
constructor(private readonly expirationService: ExpirationNotificationService) {}
onModuleInit() {
this.expirationService.startMonitoring();
}
}The service automatically:
- Checks for expiring keys at configurable intervals (default: 24 hours)
- Sends notifications when keys are about to expire (default: 30, 7, 1 days before)
- Sends notifications when keys have expired
- Integrates with webhook service if enabled
Usage Quotas
Enforce usage quotas per API key with automatic reset:
import { QuotaService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly quotaService: QuotaService) {}
async checkQuota(keyId: string) {
const status = await this.quotaService.getQuotaStatus(keyId);
if (status && !status.allowed) {
throw new Error(`Quota exceeded: ${status.used}/${status.limit}`);
}
}
}Create API key with quota:
const key = await apiKeyService.create({
name: 'Limited API Key',
quotaMax: 10000, // Maximum requests
quotaPeriod: 'daily', // Reset daily
});Key Metadata
Store custom metadata and organizational information with API keys:
const key = await apiKeyService.create({
name: 'Production API Key',
metadata: {
appVersion: '2.0.0',
region: 'us-east-1',
deploymentId: 'deploy-123',
},
tags: ['production', 'api-v2', 'mobile'],
owner: 'team-backend',
environment: 'production',
description: 'API key for production mobile application',
});Query keys by metadata (using adapter directly):
import { IApiKeyAdapter, API_KEY_ADAPTER } from 'nest-api-key-auth';
import { Inject } from '@nestjs/common';
@Injectable()
export class AppService {
constructor(@Inject(API_KEY_ADAPTER) private readonly adapter: IApiKeyAdapter) {}
async queryKeys() {
const keys = await this.adapter.query({
tags: ['production'],
owner: 'team-backend',
environment: 'production',
});
return keys;
}
}Automated Rotation Policies
Schedule and automate API key rotations:
import { RotationPolicyService } from 'nest-api-key-auth';
@Injectable()
export class AppService implements OnModuleInit {
constructor(private readonly rotationService: RotationPolicyService) {}
async onModuleInit() {
// Register a rotation policy
this.rotationService.registerPolicy({
id: 'monthly-rotation',
name: 'Monthly Key Rotation',
rotationIntervalDays: 30,
revokeOldKey: true,
gracePeriodHours: 24,
enabled: true,
nextRunAt: new Date(),
});
}
}Policy-based rotation:
// Rotate keys matching specific criteria
this.rotationService.registerPolicy({
id: 'production-rotation',
name: 'Production Keys Rotation',
tags: ['production'],
owner: 'team-backend',
rotationIntervalDays: 90,
revokeOldKey: true,
enabled: true,
nextRunAt: new Date(),
});Advanced Filtering and Querying
Query API keys with advanced filters using the adapter:
import { IApiKeyAdapter, API_KEY_ADAPTER } from 'nest-api-key-auth';
import { Inject } from '@nestjs/common';
@Injectable()
export class AppService {
constructor(@Inject(API_KEY_ADAPTER) private readonly adapter: IApiKeyAdapter) {}
async queryKeys() {
// Query by multiple criteria
const keys = await this.adapter.query({
tags: ['production', 'api-v2'],
owner: 'team-backend',
environment: 'production',
scopes: ['read:projects'],
active: true,
createdAfter: new Date('2024-01-01'),
limit: 50,
offset: 0,
});
return keys;
}
}Export/Import Functionality
Export and import API key configurations:
import { ExportImportService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly exportService: ExportImportService) {}
async exportKeys() {
const json = await this.exportService.exportKeys({
includeRevoked: false,
format: 'json',
filters: {
tags: ['production'],
environment: 'production',
},
});
// Save to file or send to backup service
return json;
}
async importKeys(jsonData: string) {
const result = await this.exportService.importKeys(jsonData);
console.log(`Imported: ${result.success}, Failed: ${result.failed}`);
}
}Request Signing (HMAC)
Verify HMAC signatures for enhanced security:
import { RequestSigningService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly signingService: RequestSigningService) {}
verifyRequest(secret: string, payload: string, signature: string, timestamp: number) {
return this.signingService.verifyRequest(secret, payload, signature, timestamp, {
algorithm: 'sha256',
timestampToleranceMs: 300000, // 5 minutes
});
}
}Sign a request:
const signed = this.signingService.signRequest(apiKeyToken, `${method}${path}${body}`, Date.now());
// Include signature and timestamp in headers
headers['x-signature'] = signed.signature;
headers['x-timestamp'] = signed.timestamp.toString();Key Templates
Define reusable API key configurations:
import { KeyTemplateService } from 'nest-api-key-auth';
@Injectable()
export class AppService implements OnModuleInit {
constructor(
private readonly templateService: KeyTemplateService,
private readonly apiKeyService: ApiKeyService,
) {}
async onModuleInit() {
// Register a template
this.templateService.registerTemplate({
id: 'production-template',
name: 'Production API Key Template',
description: 'Standard production key configuration',
config: {
scopes: ['read:projects', 'write:projects'],
quotaMax: 100000,
quotaPeriod: 'monthly',
environment: 'production',
tags: ['production'],
rateLimitMax: 1000,
rateLimitWindowMs: 60000,
},
});
}
async createFromTemplate(name: string) {
const dto = this.templateService.createFromTemplate('production-template', name, {
owner: 'team-backend', // Override template value
});
return await this.apiKeyService.create(dto);
}
}IP Blacklisting
Block specific IP addresses or CIDR ranges. Blacklist takes precedence over whitelist:
const key = await apiKeyService.create({
name: 'My App',
ipWhitelist: ['192.168.1.0/24'], // Allow this range
ipBlacklist: ['192.168.1.100'], // But block this specific IP
});Note: If an IP is in both whitelist and blacklist, it will be blocked (blacklist takes precedence).
Key Suspension
Temporarily suspend API keys without revoking them:
import { KeySuspensionService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly suspensionService: KeySuspensionService) {}
async suspendKey(keyId: string, reason?: string) {
const suspended = await this.suspensionService.suspend(keyId, reason);
return suspended; // Key state is now 'suspended'
}
async unsuspendKey(keyId: string) {
const active = await this.suspensionService.unsuspend(keyId);
return active; // Key state is now 'active'
}
async getSuspendedKeys() {
return await this.suspensionService.getSuspendedKeys();
}
}Features:
- Temporarily disable keys without permanent revocation
- Track suspension reason
- Easy unsuspension when needed
- Suspended keys are automatically rejected by guards
Key Restore/Unrevoke
Restore previously revoked API keys:
import { KeyRestoreService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly restoreService: KeyRestoreService) {}
async restoreKey(keyId: string) {
const restored = await this.restoreService.restore(keyId);
return restored; // Key is now active again
}
async getRevokedKeys() {
return await this.restoreService.getRevokedKeys();
}
}Note: Only revoked keys can be restored. Suspended or expired keys must be unsuspended or recreated.
Key Approval Workflow
Require approval before API keys become active:
import { KeyApprovalService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(
private readonly apiKeyService: ApiKeyService,
private readonly approvalService: KeyApprovalService,
) {}
async createPendingKey(name: string) {
// Create key in 'pending' state
const key = await this.apiKeyService.create({
name,
requiresApproval: true, // Key will be created in 'pending' state
});
return key; // State is 'pending'
}
async approveKey(keyId: string) {
const approved = await this.approvalService.approve(keyId);
return approved; // State is now 'active'
}
async rejectKey(keyId: string, reason?: string) {
const rejected = await this.approvalService.reject(keyId, reason);
return rejected; // Key is revoked
}
async getPendingKeys() {
return await this.approvalService.getPendingKeys();
}
}Configuration:
ApiKeyModule.register({
requireApproval: true, // All new keys require approval by default
});Expiration Grace Period
Allow keys to work for a grace period after expiration:
const key = await apiKeyService.create({
name: 'My Key',
expiresAt: new Date('2024-12-31'),
expirationGracePeriodMs: 7 * 24 * 60 * 60 * 1000, // 7 days grace period
});Features:
- Keys continue working during grace period
- State automatically transitions to 'expired' after expiration
- Grace period is configurable per key or globally
- Useful for gradual key rotation
Revocation Reason Tracking
Track the reason for API key revocation:
const revokedKey = await apiKeyService.revoke('key-id-123', {
reason: 'Security breach detected',
});
console.log(revokedKey.revocationReason); // 'Security breach detected'Security Threat Detection
Detect and respond to security threats:
import { ThreatDetectionService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly threatDetection: ThreatDetectionService) {}
async checkThreats(ipAddress: string) {
const stats = await this.threatDetection.getThreatStats(ipAddress);
if (stats.isBlocked) {
throw new Error('IP address is blocked due to suspicious activity');
}
}
}Features:
- Brute force detection (configurable threshold)
- Anomaly detection based on usage patterns
- Automatic blocking of suspicious IPs
- Integration with audit logging and webhooks
Key History/Audit Trail
Track all changes to API keys:
import { KeyHistoryService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly keyHistory: KeyHistoryService) {}
async getKeyHistory(keyId: string) {
const history = await this.keyHistory.getKeyHistory(keyId);
return history; // Array of all changes
}
async compareVersions(keyId: string, version1: number, version2: number) {
const diff = await this.keyHistory.compareKeys(keyId, version1, version2);
return diff; // Shows what changed between versions
}
}Endpoint-Specific Rate Limiting
Set different rate limits for different endpoints:
import { EndpointRateLimit } from 'nest-api-key-auth';
@Controller('projects')
export class ProjectsController {
@Get()
@ApiKeyAuth()
@EndpointRateLimit({ max: 100, windowMs: 60000 }) // 100 requests per minute
findAll() {
return [];
}
@Post()
@ApiKeyAuth()
@EndpointRateLimit({ max: 10, windowMs: 60000 }) // 10 requests per minute
create() {
return {};
}
}Using the service directly:
import { EndpointRateLimitService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly endpointRateLimit: EndpointRateLimitService) {}
async checkLimit(endpoint: string, keyId: string) {
const allowed = await this.endpointRateLimit.checkEndpointLimit(endpoint, keyId, {
max: 100,
windowMs: 60000,
});
if (!allowed) {
throw new Error('Rate limit exceeded');
}
}
}GraphQL Support
Use API key authentication with GraphQL resolvers:
import { GraphQLApiKeyGuard } from 'nest-api-key-auth';
@Resolver()
export class ProjectsResolver {
@Query(() => [Project])
@UseGuards(GraphQLApiKeyGuard)
async projects(@Context() context: any) {
const apiKey = context.req.apiKey; // API key data is attached to context
return [];
}
}Note: Requires @nestjs/graphql to be installed. The guard automatically extracts API keys from GraphQL context and validates them.
Express Middleware
Use API key authentication in non-NestJS Express applications:
import express from 'express';
import { createApiKeyMiddleware } from 'nest-api-key-auth';
const app = express();
// Configure the middleware
const apiKeyMiddleware = createApiKeyMiddleware({
adapter: 'prisma',
prismaClient: prisma,
headerName: 'x-api-key',
});
// Use the middleware
app.use('/api', apiKeyMiddleware);
app.get('/api/projects', (req, res) => {
const apiKey = req.apiKey; // API key data is attached to request
res.json([]);
});Key Groups/Teams
Organize API keys into groups with group-level permissions:
import { KeyGroupService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly keyGroup: KeyGroupService) {}
async createGroup() {
const group = await this.keyGroup.createGroup({
name: 'Backend Team',
description: 'API keys for backend services',
defaultScopes: ['read:projects', 'write:projects'],
});
return group;
}
async addKeyToGroup(keyId: string, groupId: string) {
await this.keyGroup.addKeyToGroup(keyId, groupId);
}
async getGroupKeys(groupId: string) {
return await this.keyGroup.getGroupKeys(groupId);
}
}Multi-Tenancy Support
Isolate API keys by tenant:
import { MultiTenancyService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly multiTenancy: MultiTenancyService) {}
async createTenant(tenantId: string) {
await this.multiTenancy.createTenant(tenantId, {
name: 'Acme Corp',
limits: { maxKeys: 100, maxRequestsPerDay: 1000000 },
});
}
async assignKeyToTenant(keyId: string, tenantId: string) {
await this.multiTenancy.assignKeyToTenant(keyId, tenantId);
}
async getTenantKeys(tenantId: string) {
return await this.multiTenancy.getTenantKeys(tenantId);
}
}Key Versioning
Track versions of API keys and rollback if needed:
import { KeyVersioningService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly keyVersioning: KeyVersioningService) {}
async getKeyVersions(keyId: string) {
return await this.keyVersioning.getKeyVersions(keyId);
}
async rollbackKey(keyId: string, version: number) {
return await this.keyVersioning.rollbackKey(keyId, version);
}
}Usage Reports
Generate compliance and analytics reports:
import { UsageReportService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly reportService: UsageReportService) {}
async generateReport(keyId: string, format: 'pdf' | 'csv' = 'pdf') {
const report = await this.reportService.generateReport(keyId, {
startDate: new Date('2024-01-01'),
endDate: new Date('2024-12-31'),
format,
});
return report;
}
}Circuit Breaker Pattern
Add resilience for database failures:
import { CircuitBreakerService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly circuitBreaker: CircuitBreakerService) {}
async executeWithCircuitBreaker<T>(operation: () => Promise<T>): Promise<T> {
return await this.circuitBreaker.execute('database-operation', operation);
}
}Configuration:
- Automatic failure detection
- Circuit opens after threshold failures
- Automatic recovery attempts
- Configurable timeouts and thresholds
Key Cloning
Clone existing API keys with same permissions:
import { KeyCloningService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly keyCloning: KeyCloningService) {}
async cloneKey(sourceKeyId: string, newName: string) {
const cloned = await this.keyCloning.cloneKey(sourceKeyId, {
name: newName,
// Optionally override properties
scopes: ['read:projects'], // Override scopes
});
return cloned;
}
}Key Transfer/Sharing
Transfer API key ownership between users or teams:
import { KeyTransferService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly keyTransfer: KeyTransferService) {}
async transferKey(keyId: string, newOwner: string) {
await this.keyTransfer.transferKeyOwnership(keyId, newOwner, {
notifyOldOwner: true,
notifyNewOwner: true,
});
}
}Key Testing Endpoint
Test API key validity without making real requests:
import { KeyTestingService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly keyTesting: KeyTestingService) {}
async testKey(apiKey: string) {
const result = await this.keyTesting.testKey(apiKey);
return {
valid: result.valid,
key: result.key,
error: result.error,
};
}
async testKeys(apiKeys: string[]) {
const results = await this.keyTesting.testKeys(apiKeys);
return results; // Array of test results
}
}Key Security Scoring
Calculate risk scores based on usage patterns:
import { SecurityScoringService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly securityScoring: SecurityScoringService) {}
async getSecurityScore(keyId: string) {
const score = await this.securityScoring.calculateSecurityScore(keyId);
return {
score: score.score, // 0-100, higher is more secure
factors: score.factors, // Array of factors affecting the score
recommendations: score.recommendations, // Array of improvement suggestions
};
}
}Key Aliases
Assign multiple names to the same API key:
import { KeyAliasService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly keyAlias: KeyAliasService) {}
async createAlias(keyId: string, alias: string) {
await this.keyAlias.createAlias(keyId, alias);
}
async resolveAlias(alias: string) {
const keyId = await this.keyAlias.resolveAlias(alias);
return keyId;
}
}Compliance Features
GDPR export, SOC2 audit trails, and data retention:
import { ComplianceService } from 'nest-api-key-auth';
@Injectable()
export class AppService {
constructor(private readonly compliance: ComplianceService) {}
async exportGdprData(userId: string) {
const data = await this.compliance.exportGdprData(userId);
return data; // All user-related API key data
}
async getAuditTrail(keyId: string, startDate: Date, endDate: Date) {
const trail = await this.compliance.getAuditTrail(keyId, startDate, endDate);
return trail; // SOC2-compliant audit trail
}
async enforceDataRetention() {
await this.compliance.enforceDataRetention({
retentionDays: 90,
deleteExpiredKeys: true,
});
}
}Security
Hashing:
- API keys are hashed using bcrypt (default) or argon2 before storage
- Choose your preferred hashing algorithm:
hashAlgorithm: 'bcrypt'orhashAlgorithm: 'argon2' - Argon2 is recommended for new projects (winner of Password Hashing Competition)
- Only the hashed version is stored in the database
- Plaintext token is returned only once during creation
Key Management:
- Keys can be revoked at any time
- Keys can have expiration dates
- Last used timestamp tracking for security monitoring
- Automatic expiration checking on validation
Validation:
- Token format validation (hexadecimal only)
- Scope format validation (resource:action pattern)
- Configuration validation at module initialization
Security Features:
- Rate limiting - Prevent abuse with per-key rate limits
- Endpoint-specific rate limiting - Per-route rate limits
- IP whitelisting - Restrict access to specific IP addresses
- IP blacklisting - Block specific IP addresses (takes precedence over whitelisting)
- Audit logging - Track all API key usage for security monitoring
- Security threat detection - Anomaly detection, brute force protection, suspicious activity alerts
- Key history/audit trail - Track all key changes and modifications
- Revocation reason tracking - Store reasons for key revocation
- Circuit breaker pattern - Resilience for database failures
- Security scoring - Risk assessment based on usage patterns
Testing
The library includes a comprehensive test suite. Run tests with:
npm test
npm run test:watch
npm run test:cov
npm run test:integration # Integration testsHealthService
checkHealth(): Promise<HealthStatus>
Performs a health check on the adapter and database connection.
const health = await healthService.checkHealth();
console.log(health.status); // 'healthy' or 'unhealthy'
console.log(health.adapter.status); // 'connected' or 'disconnected'isHealthy(): Promise<boolean>
Quick health check that returns a boolean.
if (await healthService.isHealthy()) {
console.log('System is healthy');
}Logging
The library includes built-in logging for debugging and monitoring. Logs are automatically generated for:
- API key creation, revocation, and validation
- Database connection issues
- Invalid token/scope attempts
- Configuration errors
Logs use NestJS's Logger by default, but you can provide a custom logger:
import { Logger } from '@nestjs/common';
import { ApiKeyLogger } from 'nest-api-key-auth';
const logger = new Logger('MyApp');
ApiKeyLogger.setLogger(logger);Validation
Token Format
Tokens must be hexadecimal strings (0-9, a-f). Invalid formats are rejected early.
Scope Format
Scopes must follow the resource:action pattern:
- Valid:
read:projects,write:users,admin:* - Invalid:
read,read-projects,read/projects
Configuration
Module options are validated at startup:
secretLength: Must be between 8 and 128headerName,queryParamName,cookieName: Must be non-empty strings
Documentation
Additional documentation and examples:
- Migration Guide - Migrate existing databases to support new features
- Database Configuration Examples - Examples for PostgreSQL, MySQL, SQLite, MongoDB, etc.
- Code Examples - Code examples for all features
- Advanced Features Examples - Rate limiting, IP whitelisting, audit logging, caching
- Database Setup Examples - PostgreSQL, MySQL, SQLite, MongoDB setup examples
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT
Author: Mohammad Shariq
