Overview
Authenticate machine-to-machine communication with API keys.
Features
- Timing-safe comparison
- Multiple extraction methods
- Multiple API keys support
- Public route bypass
Installation
npx hanma add api-key-guard --framework nest
Code
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { IS_PUBLIC_KEY } from "../decorators/public.decorator";
import { timingSafeEqual } from "crypto";
export const API_KEY_HEADER = "x-api-key";
@Injectable()
export class ApiKeyGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// Check for public routes
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
const request = context.switchToHttp().getRequest();
const apiKey = this.extractApiKey(request);
if (!apiKey) {
throw new UnauthorizedException("API key is required");
}
const validApiKeys = this.getValidApiKeys();
const isValid = validApiKeys.some((validKey) =>
this.secureCompare(apiKey, validKey)
);
if (!isValid) {
throw new UnauthorizedException("Invalid API key");
}
// Optionally attach API key metadata to request
request.apiKeyId = this.getApiKeyId(apiKey, validApiKeys);
return true;
}
private extractApiKey(request: any): string | null {
// Check X-API-Key header
const headerKey = request.headers[API_KEY_HEADER];
if (headerKey) return headerKey;
// Check Authorization header with ApiKey scheme
const authHeader = request.headers.authorization;
if (authHeader?.startsWith("ApiKey ")) {
return authHeader.slice(7);
}
// Check query parameter (not recommended for production)
if (process.env.NODE_ENV === "development" && request.query?.api_key) {
return request.query.api_key;
}
return null;
}
private getValidApiKeys(): string[] {
// Load from environment or configuration
const keys = process.env.API_KEYS?.split(",") || [];
if (keys.length === 0) {
throw new Error("No API keys configured");
}
return keys;
}
private getApiKeyId(apiKey: string, validKeys: string[]): string {
const index = validKeys.findIndex((k) => this.secureCompare(apiKey, k));
return `key_${index}`;
}
// Timing-safe comparison to prevent timing attacks
private secureCompare(a: string, b: string): boolean {
if (a.length !== b.length) {
return false;
}
const bufA = Buffer.from(a);
const bufB = Buffer.from(b);
return timingSafeEqual(bufA, bufB);
}
}
Usage
@UseGuards(ApiKeyGuard)
@Controller('api')
export class ApiController {}
Dependencies
No external dependencies required.
Frequently Asked Questions
How do I install API Key Guard?
Run the following command:
npx hanma add api-key-guard --framework nestHow do I use this snippet?
Copy the code and paste it into your project. The snippet will be saved to
src/guards/api-key.guard.ts.Can I customize this snippet?
Yes! All snippets are copy-paste code that you own. Feel free to modify them to fit your needs.
Does this work with other frameworks?
This snippet is designed for the specific framework. Check the other framework pages for equivalent implementations.