🔐 Comprehensive NestJS module for API key-based authentication with user integration and Passport.js support
Enterprise-grade API key authentication module providing robust user-based validation, Bearer token support, route protection through guards, and seamless integration with the HSuite ecosystem for secure application access control.
📚 Table of Contents
✨ Quick Start
Installation
npm install @hsuite/api-key
Basic Setup
import { ApiKeyModule } from '@hsuite/api-key';
@Module({
imports: [
ApiKeyModule.forRoot() // Global registration with automatic guard protection
]
})
export class AppModule {}
Basic Usage
import { ApiKeyAuthGuard, ApiKeyService } from '@hsuite/api-key';
import { UseGuards, Controller, Get } from '@nestjs/common';
@Controller('api')
export class ApiController {
constructor(private apiKeyService: ApiKeyService) {}
@UseGuards(ApiKeyAuthGuard)
@Get('protected')
async getProtectedResource() {
return {
message: 'This route is protected by API key authentication',
timestamp: new Date().toISOString()
};
}
}
🏗️ Architecture
Core Authentication Framework
🔐 API Key Validation System
User-Based Authentication - API keys stored in user tags for ownership tracking
Bearer Token Support - Standard Authorization header with Bearer scheme
Tag-Based Storage - Flexible key storage using user document tags
Database Integration - MongoDB queries through @hsuite/users module
🛡️ Route Protection System
Passport.js Integration - Custom strategy using passport-headerapikey
NestJS Guards - Type-safe route protection with automatic validation
Global Protection - Optional global authentication guard registration
Flexible Configuration - Per-route or controller-level protection
🌐 HSuite Ecosystem Integration
User Module Integration - Seamless @hsuite/users module compatibility
Logging Integration - Comprehensive logging with @hsuite/helpers
TypeScript First - Full type safety and IDE support
Modular Design - Clean separation of concerns and dependency injection
📊 Request Processing
Header Extraction - Automatic API key extraction from Authorization header
User Attachment - Authenticated user object attached to request
Process: Extracts key, validates, attaches user to request
Integration: Extends Passport AuthGuard with 'api-key' strategy
ApiKeyStrategy
Passport Strategy
Strategy Configuration
Header: Authorization
Prefix: Bearer
Session Support: Enabled for stateful authentication
Validation: Custom user-based API key validation
validate(apiKey: string): Promise<User>
Purpose: Core validation method called by Passport
Parameters: apiKey: string - Extracted API key
Returns: Authenticated user object
Process: Database lookup, validation, user return
Error Handling: Throws UnauthorizedException on validation failure
User Tag Structure
Database Schema
API keys are stored in the user document's tags array:
interface UserTag {
key: 'api-key';
value: string; // The actual API key
}
interface User {
_id: string;
tags: UserTag[];
// ... other user properties
}
Error Types
Error Type
Status Code
Description
Resolution
UnauthorizedException
401
Invalid or missing API key
Provide valid API key in Authorization header
ForbiddenException
403
Valid key but insufficient permissions
Check user permissions and access level
InternalServerErrorException
500
Database or validation error
Check database connection and service health
BadRequestException
400
Malformed Authorization header
Use proper "Bearer {api-key}" format
📖 Guides
API Key Generation Guide
Complete guide to generating and managing API keys for users. This module provides comprehensive API key generation functionality with secure random key creation, user tag integration, and metadata management for enterprise applications.
Route Protection Guide
Advanced route protection patterns and guard configuration. Learn how to implement route-level authentication using the ApiKeyAuthGuard, configure global protection, and customize authentication behavior for different endpoints.
User Integration Guide
Integrating with @hsuite/users module and tag management. The API key system seamlessly integrates with the HSuite user management system by storing API keys in user tags, enabling flexible key-to-user relationships and permission management.
Security Best Practices Guide
Security recommendations and authentication patterns. This covers secure API key generation, proper header formatting, rate limiting implementation, key rotation strategies, and protection against common authentication vulnerabilities.
🎯 Examples
Enterprise API Key Management Service
import { ApiKeyService, ApiKeyAuthGuard } from '@hsuite/api-key';
import { Injectable, Logger, UnauthorizedException } from '@nestjs/common';
import { UsersService } from '@hsuite/users';
@Injectable()
export class EnterpriseApiKeyService {
private readonly logger = new Logger(EnterpriseApiKeyService.name);
constructor(
private readonly apiKeyService: ApiKeyService,
private readonly usersService: UsersService
) {}
async generateApiKeyForUser(userId: string, keyName?: string) {
try {
// Generate secure API key
const apiKey = await this.generateSecureApiKey();
// Get user document
const user = await this.usersService.findById(userId);
if (!user) {
throw new Error('User not found');
}
// Add API key to user tags
const tagData = {
key: 'api-key',
value: apiKey,
metadata: {
name: keyName || 'Default API Key',
createdAt: new Date().toISOString(),
lastUsed: null,
permissions: ['read', 'write'],
rateLimit: {
requests: 1000,
period: 'hour'
}
}
};
await this.usersService.addTag(userId, tagData);
this.logger.log(`API key generated for user: ${userId}`);
return {
success: true,
apiKey,
keyInfo: {
userId,
keyName: tagData.metadata.name,
permissions: tagData.metadata.permissions,
rateLimit: tagData.metadata.rateLimit,
createdAt: tagData.metadata.createdAt
},
usage: {
headerFormat: `Authorization: Bearer ${apiKey}`,
curlExample: `curl -H "Authorization: Bearer ${apiKey}" https://api.example.com/protected`
}
};
} catch (error) {
this.logger.error('API key generation error:', error);
throw new Error(`Failed to generate API key: ${error.message}`);
}
}
async validateEnterpriseApiKey(apiKey: string, requiredPermissions: string[] = []) {
try {
// Find user by API key
const user = await this.getUserByApiKey(apiKey);
if (!user) {
throw new UnauthorizedException('Invalid API key');
}
// Get API key metadata
const apiKeyTag = user.tags.find(tag =>
tag.key === 'api-key' && tag.value === apiKey
);
if (!apiKeyTag || !apiKeyTag.metadata) {
throw new UnauthorizedException('API key metadata not found');
}
// Check permissions
const hasRequiredPermissions = requiredPermissions.every(permission =>
apiKeyTag.metadata.permissions?.includes(permission)
);
if (!hasRequiredPermissions) {
throw new UnauthorizedException('Insufficient API key permissions');
}
// Check rate limiting
const rateLimitCheck = await this.checkRateLimit(user._id, apiKey);
if (!rateLimitCheck.allowed) {
throw new UnauthorizedException(`Rate limit exceeded: ${rateLimitCheck.message}`);
}
// Update last used timestamp
await this.updateLastUsed(user._id, apiKey);
return {
user,
apiKeyInfo: apiKeyTag.metadata,
rateLimitStatus: rateLimitCheck
};
} catch (error) {
this.logger.error('API key validation error:', error);
throw error;
}
}
async revokeApiKey(userId: string, apiKey: string) {
try {
const user = await this.usersService.findById(userId);
if (!user) {
throw new Error('User not found');
}
// Remove API key tag
await this.usersService.removeTag(userId, 'api-key', apiKey);
this.logger.log(`API key revoked for user: ${userId}`);
return {
success: true,
message: 'API key successfully revoked',
revokedAt: new Date().toISOString()
};
} catch (error) {
this.logger.error('API key revocation error:', error);
throw error;
}
}
async listUserApiKeys(userId: string) {
try {
const user = await this.usersService.findById(userId);
if (!user) {
throw new Error('User not found');
}
const apiKeys = user.tags
.filter(tag => tag.key === 'api-key')
.map(tag => ({
id: this.hashApiKey(tag.value), // Don't expose full key
name: tag.metadata?.name || 'Unnamed Key',
permissions: tag.metadata?.permissions || [],
createdAt: tag.metadata?.createdAt,
lastUsed: tag.metadata?.lastUsed,
rateLimit: tag.metadata?.rateLimit,
status: 'active'
}));
return {
success: true,
userId,
apiKeys,
totalKeys: apiKeys.length
};
} catch (error) {
this.logger.error('API key listing error:', error);
throw error;
}
}
private async generateSecureApiKey(): Promise<string> {
const crypto = require('crypto');
const prefix = 'hsk'; // HSuite Key prefix
const randomBytes = crypto.randomBytes(32).toString('hex');
return `${prefix}_${randomBytes}`;
}
private async getUserByApiKey(apiKey: string) {
return await this.usersService.findOne({
'tags.key': 'api-key',
'tags.value': apiKey
});
}
private async checkRateLimit(userId: string, apiKey: string): Promise<any> {
// Implementation would check Redis for rate limiting
return {
allowed: true,
remaining: 999,
resetTime: new Date(Date.now() + 3600000).toISOString()
};
}
private async updateLastUsed(userId: string, apiKey: string) {
// Implementation would update the tag metadata
await this.usersService.updateTagMetadata(userId, 'api-key', apiKey, {
lastUsed: new Date().toISOString()
});
}
private hashApiKey(apiKey: string): string {
const crypto = require('crypto');
return crypto.createHash('sha256').update(apiKey).digest('hex').substring(0, 8);
}
}
Advanced Route Protection Implementation
import { ApiKeyAuthGuard } from '@hsuite/api-key';
import { Controller, Get, Post, UseGuards, SetMetadata, createParamDecorator, ExecutionContext } from '@nestjs/common';
// Custom decorator for API key permissions
export const RequirePermissions = (...permissions: string[]) =>
SetMetadata('apikey-permissions', permissions);
// Parameter decorator to get API key info
export const ApiKeyInfo = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.apiKeyInfo; // Set by enhanced guard
},
);
@Controller('enterprise')
export class EnterpriseController {
@UseGuards(ApiKeyAuthGuard)
@Get('public-data')
async getPublicData() {
return {
data: 'Public enterprise data',
accessLevel: 'basic',
timestamp: new Date().toISOString()
};
}
@UseGuards(ApiKeyAuthGuard)
@RequirePermissions('read', 'analytics')
@Get('analytics')
async getAnalyticsData(@ApiKeyInfo() keyInfo: any) {
return {
data: 'Advanced analytics data',
accessLevel: 'analytics',
keyName: keyInfo?.name,
permissions: keyInfo?.permissions,
timestamp: new Date().toISOString()
};
}
@UseGuards(ApiKeyAuthGuard)
@RequirePermissions('write', 'admin')
@Post('admin-operation')
async performAdminOperation(@ApiKeyInfo() keyInfo: any) {
return {
operation: 'Admin operation completed',
executedBy: keyInfo?.name,
timestamp: new Date().toISOString(),
permissions: keyInfo?.permissions
};
}
}
// Enhanced guard with permission checking
@Injectable()
export class EnhancedApiKeyGuard extends ApiKeyAuthGuard {
constructor(
private reflector: Reflector,
private enterpriseApiKeyService: EnterpriseApiKeyService
) {
super();
}
async canActivate(context: ExecutionContext): Promise<boolean> {
// First check basic API key authentication
const basicAuth = await super.canActivate(context);
if (!basicAuth) return false;
// Get required permissions from metadata
const requiredPermissions = this.reflector.getAllAndOverride<string[]>(
'apikey-permissions',
[context.getHandler(), context.getClass()]
);
if (!requiredPermissions) return true; // No specific permissions required
// Extract API key and validate permissions
const request = context.switchToHttp().getRequest();
const apiKey = this.extractApiKey(request);
try {
const validation = await this.enterpriseApiKeyService.validateEnterpriseApiKey(
apiKey,
requiredPermissions
);
// Attach API key info to request for later use
request.apiKeyInfo = validation.apiKeyInfo;
return true;
} catch (error) {
return false;
}
}
private extractApiKey(request: any): string {
const authHeader = request.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
return authHeader.substring(7);
}
return null;
}
}
Multi-Application API Key Integration
import { ApiKeyService } from '@hsuite/api-key';
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class MultiAppApiKeyService {
private readonly logger = new Logger(MultiAppApiKeyService.name);
constructor(private readonly apiKeyService: ApiKeyService) {}
async configureApplicationApiKeys() {
const applications = [
{
name: 'Smart Node Dashboard',
permissions: ['read', 'write', 'admin'],
rateLimit: { requests: 10000, period: 'hour' }
},
{
name: 'Analytics Service',
permissions: ['read', 'analytics'],
rateLimit: { requests: 5000, period: 'hour' }
},
{
name: 'Mobile App',
permissions: ['read'],
rateLimit: { requests: 1000, period: 'hour' }
}
];
const results = [];
for (const app of applications) {
try {
const apiKeyResult = await this.generateApplicationApiKey(app);
results.push(apiKeyResult);
this.logger.log(`API key configured for application: ${app.name}`);
} catch (error) {
this.logger.error(`Failed to configure API key for ${app.name}:`, error);
results.push({
application: app.name,
success: false,
error: error.message
});
}
}
return {
success: true,
configuredApplications: results.length,
results
};
}
async validateCrossApplicationAccess(apiKey: string, targetApplication: string) {
try {
const validation = await this.apiKeyService.validateApiKey(apiKey);
if (!validation) {
throw new Error('Invalid API key');
}
// Check application-specific access
const hasApplicationAccess = await this.checkApplicationAccess(
validation.user,
targetApplication
);
if (!hasApplicationAccess) {
throw new Error(`Access denied for application: ${targetApplication}`);
}
return {
valid: true,
user: validation.user,
application: targetApplication,
accessLevel: this.getAccessLevel(validation.user, targetApplication)
};
} catch (error) {
this.logger.error('Cross-application access validation error:', error);
throw error;
}
}
private async generateApplicationApiKey(appConfig: any) {
// Implementation would create application-specific API keys
const apiKey = `app_${appConfig.name.toLowerCase().replace(/\s+/g, '_')}_${Date.now()}`;
return {
application: appConfig.name,
apiKey,
permissions: appConfig.permissions,
rateLimit: appConfig.rateLimit,
success: true
};
}
private async checkApplicationAccess(user: any, application: string): Promise<boolean> {
// Implementation would check user's application access rights
return true; // Simplified for example
}
private getAccessLevel(user: any, application: string): string {
// Implementation would determine access level based on user and application
return 'standard'; // Simplified for example
}
}
// 1. Client makes request with API key
const response = await fetch('/api/protected', {
headers: {
'Authorization': 'Bearer hsk_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'
}
});
// 2. ApiKeyAuthGuard intercepts request
// 3. ApiKeyStrategy extracts and validates key
// 4. ApiKeyService queries user database
// 5. User object attached to request
// 6. Route handler receives authenticated request
Database Queries
// User lookup by API key
db.users.find({
"tags.key": "api-key",
"tags.value": "hsk_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
});
// Recommended index for performance
db.users.createIndex({ "tags.key": 1, "tags.value": 1 });
🔐 Secure Authentication: Enterprise-grade API key validation with user-based ownership and tag storage.
🛡️ Route Protection: Comprehensive guard system with Passport.js integration and flexible protection patterns.
🌐 HSuite Integration: Seamless compatibility with @hsuite/users module and ecosystem-wide authentication standards.