API Authentication: OAuth 2.0, JWT, and API Keys Compared
Choosing authentication for APIs balances security, complexity, and user experience. This comprehensive guide compares OAuth 2.0/OIDC, JWT, API keys, and mTLS with implementation patterns, security considerations, and real-world examples.
Executive Summary
API authentication is critical for securing modern applications. This guide covers the most common authentication methods, their trade-offs, and when to use each approach. We'll explore OAuth 2.0/OIDC for user authentication, JWT for stateless tokens, API keys for service-to-service communication, and mTLS for high-security scenarios.
Key recommendations:
- Public APIs: OAuth 2.0 + short-lived JWT tokens; avoid long-lived API keys
- Service-to-service: mTLS or workload identity + JWT assertions
- Internal APIs: Consider API keys with proper rotation and scoping
- High-security: Implement mTLS with certificate-based authentication
Authentication Methods Comparison
Overview Table
| Method | Use Case | Security Level | Complexity | Revocation | Stateless |
|---|---|---|---|---|---|
| OAuth 2.0 | User authentication | High | High | Yes | No |
| JWT | Stateless tokens | Medium-High | Medium | Limited | Yes |
| API Keys | Service-to-service | Medium | Low | Yes | Yes |
| mTLS | High-security | Very High | High | Yes | Yes |
Decision Matrix
class AuthenticationSelector {
selectMethod(requirements: AuthRequirements): AuthMethod {
const { userType, securityLevel, stateless, revocation } = requirements;
if (userType === 'end-user' && securityLevel === 'high') {
return { method: 'oauth2', reasoning: 'Best for user authentication with high security' };
}
if (stateless && securityLevel === 'very-high') {
return { method: 'mtls', reasoning: 'Highest security with certificate-based auth' };
}
if (stateless && revocation === 'important') {
return { method: 'api-key', reasoning: 'Easy revocation with stateless operation' };
}
if (stateless && securityLevel === 'medium') {
return { method: 'jwt', reasoning: 'Stateless tokens with reasonable security' };
}
return { method: 'oauth2', reasoning: 'Default choice for most scenarios' };
}
}
OAuth 2.0 and OpenID Connect
OAuth 2.0 Flow Implementation
class OAuth2Provider {
private clientRegistry: Map<string, OAuth2Client> = new Map();
private tokenStore: TokenStore;
private userStore: UserStore;
constructor() {
this.tokenStore = new TokenStore();
this.userStore = new UserStore();
}
async authorizeRequest(request: AuthorizationRequest): Promise<AuthorizationResponse> {
// Validate client
const client = this.clientRegistry.get(request.clientId);
if (!client) {
throw new Error('Invalid client ID');
}
// Validate redirect URI
if (!client.redirectUris.includes(request.redirectUri)) {
throw new Error('Invalid redirect URI');
}
// Validate scopes
const validScopes = this.validateScopes(request.scope, client.allowedScopes);
if (!validScopes) {
throw new Error('Invalid scopes requested');
}
// Generate authorization code
const authCode = this.generateAuthCode({
clientId: request.clientId,
userId: request.userId,
scope: request.scope,
redirectUri: request.redirectUri,
state: request.state
});
return {
code: authCode,
state: request.state
};
}
async exchangeCodeForToken(request: TokenRequest): Promise<TokenResponse> {
// Validate authorization code
const authCodeData = await this.tokenStore.getAuthCode(request.code);
if (!authCodeData) {
throw new Error('Invalid authorization code');
}
// Validate client credentials
const client = this.clientRegistry.get(request.clientId);
if (!client || !this.validateClientCredentials(client, request)) {
throw new Error('Invalid client credentials');
}
// Generate tokens
const accessToken = await this.generateAccessToken({
clientId: request.clientId,
userId: authCodeData.userId,
scope: authCodeData.scope
});
const refreshToken = await this.generateRefreshToken({
clientId: request.clientId,
userId: authCodeData.userId
});
// Store tokens
await this.tokenStore.storeTokens(accessToken, refreshToken);
return {
accessToken: accessToken.token,
refreshToken: refreshToken.token,
tokenType: 'Bearer',
expiresIn: accessToken.expiresIn,
scope: authCodeData.scope
};
}
async validateToken(token: string): Promise<TokenValidationResult> {
try {
const tokenData = await this.tokenStore.getAccessToken(token);
if (!tokenData) {
return { valid: false, error: 'Token not found' };
}
if (tokenData.expiresAt < Date.now()) {
return { valid: false, error: 'Token expired' };
}
if (tokenData.revoked) {
return { valid: false, error: 'Token revoked' };
}
return {
valid: true,
userId: tokenData.userId,
clientId: tokenData.clientId,
scope: tokenData.scope
};
} catch (error) {
return { valid: false, error: 'Token validation failed' };
}
}
private generateAuthCode(data: AuthCodeData): string {
const code = crypto.randomBytes(32).toString('hex');
this.tokenStore.storeAuthCode(code, data, 600); // 10 minutes
return code;
}
private async generateAccessToken(data: TokenData): Promise<AccessToken> {
const token = crypto.randomBytes(32).toString('hex');
const expiresAt = Date.now() + (data.expiresIn || 3600) * 1000;
return {
token,
userId: data.userId,
clientId: data.clientId,
scope: data.scope,
expiresAt,
revoked: false
};
}
}
OpenID Connect Implementation
class OpenIDConnectProvider extends OAuth2Provider {
private userInfoEndpoint = '/userinfo';
private discoveryEndpoint = '/.well-known/openid_configuration';
async getUserInfo(accessToken: string): Promise<UserInfo> {
const tokenValidation = await this.validateToken(accessToken);
if (!tokenValidation.valid) {
throw new Error('Invalid access token');
}
const user = await this.userStore.getUser(tokenValidation.userId);
return {
sub: user.id,
name: user.name,
email: user.email,
email_verified: user.emailVerified,
picture: user.picture,
given_name: user.givenName,
family_name: user.familyName,
locale: user.locale
};
}
async getDiscoveryDocument(): Promise<DiscoveryDocument> {
return {
issuer: process.env.OIDC_ISSUER,
authorization_endpoint: `${process.env.OIDC_ISSUER}/oauth/authorize`,
token_endpoint: `${process.env.OIDC_ISSUER}/oauth/token`,
userinfo_endpoint: `${process.env.OIDC_ISSUER}/userinfo`,
jwks_uri: `${process.env.OIDC_ISSUER}/.well-known/jwks.json`,
response_types_supported: ['code'],
grant_types_supported: ['authorization_code', 'refresh_token'],
subject_types_supported: ['public'],
id_token_signing_alg_values_supported: ['RS256'],
scopes_supported: ['openid', 'profile', 'email']
};
}
async generateIdToken(userId: string, clientId: string): Promise<string> {
const user = await this.userStore.getUser(userId);
const now = Math.floor(Date.now() / 1000);
const payload = {
iss: process.env.OIDC_ISSUER,
sub: user.id,
aud: clientId,
exp: now + 3600,
iat: now,
auth_time: now,
nonce: crypto.randomBytes(16).toString('hex'),
name: user.name,
email: user.email,
email_verified: user.emailVerified
};
return jwt.sign(payload, process.env.OIDC_PRIVATE_KEY, { algorithm: 'RS256' });
}
}
JWT Implementation
JWT Token Management
class JWTManager {
private privateKey: string;
private publicKey: string;
private algorithm = 'RS256';
constructor() {
this.privateKey = process.env.JWT_PRIVATE_KEY!;
this.publicKey = process.env.JWT_PUBLIC_KEY!;
}
async generateToken(payload: JWTPayload): Promise<string> {
const now = Math.floor(Date.now() / 1000);
const tokenPayload = {
...payload,
iat: now,
exp: now + (payload.expiresIn || 3600),
jti: crypto.randomUUID()
};
return jwt.sign(tokenPayload, this.privateKey, { algorithm: this.algorithm });
}
async verifyToken(token: string): Promise<JWTVerificationResult> {
try {
const decoded = jwt.verify(token, this.publicKey, { algorithms: [this.algorithm] }) as any;
// Check token blacklist
if (await this.isTokenBlacklisted(decoded.jti)) {
return { valid: false, error: 'Token blacklisted' };
}
return {
valid: true,
payload: decoded,
userId: decoded.sub,
clientId: decoded.aud,
scope: decoded.scope
};
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
return { valid: false, error: 'Token expired' };
}
if (error instanceof jwt.JsonWebTokenError) {
return { valid: false, error: 'Invalid token' };
}
return { valid: false, error: 'Token verification failed' };
}
}
async refreshToken(refreshToken: string): Promise<TokenRefreshResult> {
try {
const decoded = jwt.verify(refreshToken, this.publicKey) as any;
// Check if refresh token is blacklisted
if (await this.isTokenBlacklisted(decoded.jti)) {
return { success: false, error: 'Refresh token blacklisted' };
}
// Generate new access token
const newAccessToken = await this.generateToken({
sub: decoded.sub,
aud: decoded.aud,
scope: decoded.scope,
expiresIn: 3600
});
// Generate new refresh token
const newRefreshToken = await this.generateToken({
sub: decoded.sub,
aud: decoded.aud,
scope: decoded.scope,
expiresIn: 86400 * 30 // 30 days
});
// Blacklist old refresh token
await this.blacklistToken(decoded.jti);
return {
success: true,
accessToken: newAccessToken,
refreshToken: newRefreshToken
};
} catch (error) {
return { success: false, error: 'Invalid refresh token' };
}
}
async blacklistToken(jti: string): Promise<void> {
// Store in Redis with expiration
await this.redis.setex(`blacklist:${jti}`, 86400 * 30, '1');
}
private async isTokenBlacklisted(jti: string): Promise<boolean> {
const result = await this.redis.get(`blacklist:${jti}`);
return result !== null;
}
}
JWT Security Best Practices
class JWTSecurityManager {
private keyRotationInterval = 86400 * 7; // 7 days
private maxTokenAge = 3600; // 1 hour
constructor(private jwtManager: JWTManager) {
this.startKeyRotation();
}
private startKeyRotation(): void {
setInterval(() => {
this.rotateKeys();
}, this.keyRotationInterval * 1000);
}
private async rotateKeys(): Promise<void> {
// Generate new key pair
const newKeyPair = await this.generateKeyPair();
// Update keys
this.jwtManager.updateKeys(newKeyPair.privateKey, newKeyPair.publicKey);
// Publish new public key to JWKS endpoint
await this.updateJWKSEndpoint(newKeyPair.publicKey);
console.log('JWT keys rotated successfully');
}
async validateTokenSecurity(token: string): Promise<SecurityValidationResult> {
const verification = await this.jwtManager.verifyToken(token);
if (!verification.valid) {
return { secure: false, issues: [verification.error!] };
}
const issues: string[] = [];
const payload = verification.payload!;
// Check token age
const tokenAge = Date.now() / 1000 - payload.iat;
if (tokenAge > this.maxTokenAge) {
issues.push('Token too old');
}
// Check audience
if (!payload.aud || !this.isValidAudience(payload.aud)) {
issues.push('Invalid audience');
}
// Check issuer
if (!payload.iss || !this.isValidIssuer(payload.iss)) {
issues.push('Invalid issuer');
}
// Check scope
if (!payload.scope || !this.isValidScope(payload.scope)) {
issues.push('Invalid scope');
}
return {
secure: issues.length === 0,
issues
};
}
private async generateKeyPair(): Promise<KeyPair> {
return new Promise((resolve, reject) => {
crypto.generateKeyPair('rsa', {
modulusLength: 2048,
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
}, (err, publicKey, privateKey) => {
if (err) reject(err);
else resolve({ publicKey, privateKey });
});
});
}
}
API Key Implementation
API Key Management System
class APIKeyManager {
private keyStore: Map<string, APIKey> = new Map();
private keyPrefixes = {
'pk': 'public_key',
'sk': 'secret_key',
'tk': 'test_key'
};
async generateAPIKey(request: APIKeyRequest): Promise<APIKeyResponse> {
const keyId = this.generateKeyId();
const secret = this.generateSecret();
const keyType = this.determineKeyType(request.environment);
const apiKey: APIKey = {
id: keyId,
secret: secret,
type: keyType,
name: request.name,
description: request.description,
scopes: request.scopes,
environment: request.environment,
userId: request.userId,
createdAt: new Date(),
expiresAt: request.expiresAt,
lastUsedAt: null,
usageCount: 0,
rateLimit: request.rateLimit,
isActive: true
};
this.keyStore.set(keyId, apiKey);
return {
keyId,
secret,
keyType,
expiresAt: apiKey.expiresAt,
scopes: apiKey.scopes
};
}
async validateAPIKey(keyId: string, secret: string): Promise<APIKeyValidationResult> {
const apiKey = this.keyStore.get(keyId);
if (!apiKey) {
return { valid: false, error: 'API key not found' };
}
if (!apiKey.isActive) {
return { valid: false, error: 'API key inactive' };
}
if (apiKey.secret !== secret) {
return { valid: false, error: 'Invalid secret' };
}
if (apiKey.expiresAt && apiKey.expiresAt < new Date()) {
return { valid: false, error: 'API key expired' };
}
// Update usage statistics
apiKey.lastUsedAt = new Date();
apiKey.usageCount++;
return {
valid: true,
scopes: apiKey.scopes,
rateLimit: apiKey.rateLimit,
userId: apiKey.userId
};
}
async revokeAPIKey(keyId: string): Promise<void> {
const apiKey = this.keyStore.get(keyId);
if (apiKey) {
apiKey.isActive = false;
apiKey.revokedAt = new Date();
}
}
async rotateAPIKey(keyId: string): Promise<APIKeyResponse> {
const existingKey = this.keyStore.get(keyId);
if (!existingKey) {
throw new Error('API key not found');
}
// Revoke old key
await this.revokeAPIKey(keyId);
// Generate new key
const newKey = await this.generateAPIKey({
name: existingKey.name,
description: existingKey.description,
scopes: existingKey.scopes,
environment: existingKey.environment,
userId: existingKey.userId,
rateLimit: existingKey.rateLimit
});
return newKey;
}
private generateKeyId(): string {
return 'ak_' + crypto.randomBytes(16).toString('hex');
}
private generateSecret(): string {
return crypto.randomBytes(32).toString('hex');
}
private determineKeyType(environment: string): string {
return environment === 'production' ? 'sk' : 'tk';
}
}
API Key Rate Limiting
class APIKeyRateLimiter {
private rateLimiters: Map<string, RateLimiter> = new Map();
async checkRateLimit(keyId: string, endpoint: string): Promise<RateLimitResult> {
const key = `${keyId}:${endpoint}`;
if (!this.rateLimiters.has(key)) {
// Get rate limit from API key configuration
const apiKey = await this.getAPIKey(keyId);
const rateLimit = apiKey?.rateLimit || { requests: 1000, window: 3600 };
this.rateLimiters.set(key, new RateLimiter({
windowMs: rateLimit.window * 1000,
max: rateLimit.requests
}));
}
const limiter = this.rateLimiters.get(key)!;
const result = limiter.tryConsume();
return {
allowed: result.allowed,
remaining: result.remaining,
resetTime: result.resetTime,
limit: result.limit
};
}
private async getAPIKey(keyId: string): Promise<APIKey | null> {
// Implement API key lookup
return null;
}
}
class RateLimiter {
private requests: number[] = [];
constructor(private config: { windowMs: number; max: number }) {}
tryConsume(): RateLimitResult {
const now = Date.now();
const windowStart = now - this.config.windowMs;
// Remove old requests
this.requests = this.requests.filter(time => time > windowStart);
if (this.requests.length < this.config.max) {
this.requests.push(now);
return {
allowed: true,
remaining: this.config.max - this.requests.length,
resetTime: now + this.config.windowMs,
limit: this.config.max
};
}
return {
allowed: false,
remaining: 0,
resetTime: this.requests[0] + this.config.windowMs,
limit: this.config.max
};
}
}
Mutual TLS (mTLS) Implementation
mTLS Certificate Management
class mTLSManager {
private caCert: string;
private caKey: string;
private certificateStore: Map<string, Certificate> = new Map();
constructor() {
this.caCert = process.env.MTLS_CA_CERT!;
this.caKey = process.env.MTLS_CA_KEY!;
}
async generateClientCertificate(request: CertificateRequest): Promise<CertificateResponse> {
const keyPair = await this.generateKeyPair();
const csr = await this.generateCSR(keyPair.privateKey, request);
const certificate = await this.signCertificate(csr);
const cert: Certificate = {
id: request.clientId,
certificate: certificate,
privateKey: keyPair.privateKey,
publicKey: keyPair.publicKey,
subject: request.subject,
issuedAt: new Date(),
expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1 year
isActive: true
};
this.certificateStore.set(request.clientId, cert);
return {
certificate: certificate,
privateKey: keyPair.privateKey,
expiresAt: cert.expiresAt
};
}
async validateCertificate(certificate: string): Promise<CertificateValidationResult> {
try {
const cert = new X509Certificate(certificate);
// Check certificate validity
if (cert.validFrom > new Date() || cert.validTo < new Date()) {
return { valid: false, error: 'Certificate expired or not yet valid' };
}
// Verify certificate chain
if (!await this.verifyCertificateChain(cert)) {
return { valid: false, error: 'Invalid certificate chain' };
}
// Check certificate revocation
if (await this.isCertificateRevoked(cert.serialNumber)) {
return { valid: false, error: 'Certificate revoked' };
}
return {
valid: true,
subject: cert.subject,
issuer: cert.issuer,
serialNumber: cert.serialNumber,
validFrom: cert.validFrom,
validTo: cert.validTo
};
} catch (error) {
return { valid: false, error: 'Invalid certificate format' };
}
}
async revokeCertificate(serialNumber: string): Promise<void> {
// Add to Certificate Revocation List (CRL)
await this.addToCRL(serialNumber);
// Update certificate status
for (const [clientId, cert] of this.certificateStore) {
if (cert.serialNumber === serialNumber) {
cert.isActive = false;
cert.revokedAt = new Date();
}
}
}
private async generateKeyPair(): Promise<KeyPair> {
return new Promise((resolve, reject) => {
crypto.generateKeyPair('rsa', {
modulusLength: 2048,
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
}, (err, publicKey, privateKey) => {
if (err) reject(err);
else resolve({ publicKey, privateKey });
});
});
}
private async generateCSR(privateKey: string, request: CertificateRequest): Promise<string> {
// Implement CSR generation
return '';
}
private async signCertificate(csr: string): Promise<string> {
// Implement certificate signing
return '';
}
private async verifyCertificateChain(cert: X509Certificate): Promise<boolean> {
// Implement certificate chain verification
return true;
}
private async isCertificateRevoked(serialNumber: string): Promise<boolean> {
// Check CRL
return false;
}
private async addToCRL(serialNumber: string): Promise<void> {
// Add to Certificate Revocation List
}
}
mTLS Middleware
class mTLSMiddleware {
constructor(private mTLSManager: mTLSManager) {}
async authenticate(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const certificate = req.socket.getPeerCertificate();
if (!certificate) {
res.status(401).json({ error: 'Client certificate required' });
return;
}
const validation = await this.mTLSManager.validateCertificate(certificate.raw);
if (!validation.valid) {
res.status(401).json({ error: validation.error });
return;
}
// Add certificate info to request
req.clientCertificate = {
subject: validation.subject,
issuer: validation.issuer,
serialNumber: validation.serialNumber,
validFrom: validation.validFrom,
validTo: validation.validTo
};
next();
} catch (error) {
res.status(401).json({ error: 'Certificate validation failed' });
}
}
}
Security Considerations
Common Vulnerabilities and Mitigations
class SecurityAuditor {
async auditAuthentication(implementation: AuthImplementation): Promise<SecurityAuditResult> {
const issues: SecurityIssue[] = [];
// Check for common vulnerabilities
issues.push(...await this.checkTokenSecurity(implementation));
issues.push(...await this.checkKeyManagement(implementation));
issues.push(...await this.checkRateLimiting(implementation));
issues.push(...await this.checkInputValidation(implementation));
return {
score: this.calculateSecurityScore(issues),
issues,
recommendations: this.generateRecommendations(issues)
};
}
private async checkTokenSecurity(implementation: AuthImplementation): Promise<SecurityIssue[]> {
const issues: SecurityIssue[] = [];
// Check token expiration
if (implementation.tokenExpiration > 3600) {
issues.push({
severity: 'medium',
category: 'token_security',
description: 'Token expiration time too long',
recommendation: 'Reduce token expiration to 1 hour or less'
});
}
// Check token storage
if (implementation.storesTokensInLocalStorage) {
issues.push({
severity: 'high',
category: 'token_security',
description: 'Tokens stored in localStorage',
recommendation: 'Use httpOnly cookies or secure storage'
});
}
return issues;
}
private async checkKeyManagement(implementation: AuthImplementation): Promise<SecurityIssue[]> {
const issues: SecurityIssue[] = [];
// Check key rotation
if (!implementation.hasKeyRotation) {
issues.push({
severity: 'high',
category: 'key_management',
description: 'No key rotation implemented',
recommendation: 'Implement automatic key rotation'
});
}
// Check key storage
if (implementation.storesKeysInCode) {
issues.push({
severity: 'critical',
category: 'key_management',
description: 'Keys stored in source code',
recommendation: 'Use environment variables or key management service'
});
}
return issues;
}
private calculateSecurityScore(issues: SecurityIssue[]): number {
const weights = { critical: 4, high: 3, medium: 2, low: 1 };
const totalWeight = issues.reduce((sum, issue) => sum + weights[issue.severity], 0);
const maxWeight = issues.length * 4; // All critical
return Math.max(0, 100 - (totalWeight / maxWeight) * 100);
}
}
Security Checklist
# Security checklist for API authentication
security_checklist:
token_security:
- use_short_lived_tokens
- implement_token_rotation
- store_tokens_securely
- validate_token_signature
- check_token_expiration
- implement_token_blacklisting
key_management:
- use_strong_key_generation
- implement_key_rotation
- store_keys_securely
- use_environment_variables
- implement_key_escrow
- monitor_key_usage
rate_limiting:
- implement_per_endpoint_limits
- use_different_limits_per_user_type
- implement_burst_protection
- monitor_rate_limit_violations
- implement_graceful_degradation
input_validation:
- validate_all_inputs
- sanitize_user_data
- implement_csrf_protection
- validate_content_types
- check_request_sizes
- implement_sql_injection_protection
Implementation Examples
Express.js Middleware
// OAuth 2.0 middleware
class OAuth2Middleware {
constructor(private oauth2Provider: OAuth2Provider) {}
async authenticate(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
res.status(401).json({ error: 'Bearer token required' });
return;
}
const token = authHeader.substring(7);
const validation = await this.oauth2Provider.validateToken(token);
if (!validation.valid) {
res.status(401).json({ error: validation.error });
return;
}
req.user = { id: validation.userId };
req.client = { id: validation.clientId };
req.scope = validation.scope;
next();
} catch (error) {
res.status(401).json({ error: 'Authentication failed' });
}
}
requireScope(requiredScope: string) {
return (req: Request, res: Response, next: NextFunction): void => {
const userScope = req.scope;
if (!userScope || !userScope.includes(requiredScope)) {
res.status(403).json({ error: 'Insufficient scope' });
return;
}
next();
};
}
}
// JWT middleware
class JWTMiddleware {
constructor(private jwtManager: JWTManager) {}
async authenticate(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
res.status(401).json({ error: 'Bearer token required' });
return;
}
const token = authHeader.substring(7);
const verification = await this.jwtManager.verifyToken(token);
if (!verification.valid) {
res.status(401).json({ error: verification.error });
return;
}
req.user = { id: verification.userId };
req.client = { id: verification.clientId };
req.scope = verification.scope;
next();
} catch (error) {
res.status(401).json({ error: 'Token verification failed' });
}
}
}
// API Key middleware
class APIKeyMiddleware {
constructor(private apiKeyManager: APIKeyManager) {}
async authenticate(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const apiKey = req.headers['x-api-key'] as string;
const apiSecret = req.headers['x-api-secret'] as string;
if (!apiKey || !apiSecret) {
res.status(401).json({ error: 'API key and secret required' });
return;
}
const validation = await this.apiKeyManager.validateAPIKey(apiKey, apiSecret);
if (!validation.valid) {
res.status(401).json({ error: validation.error });
return;
}
req.user = { id: validation.userId };
req.scope = validation.scopes;
req.rateLimit = validation.rateLimit;
next();
} catch (error) {
res.status(401).json({ error: 'API key validation failed' });
}
}
}
FastAPI Implementation
# FastAPI OAuth 2.0 implementation
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import JWTError, jwt
from passlib.context import CryptContext
class OAuth2Provider:
def __init__(self):
self.secret_key = "your-secret-key"
self.algorithm = "HS256"
self.pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_token(self, token: str) -> dict:
try:
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
return payload
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
def create_access_token(self, data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, self.secret_key, algorithm=self.algorithm)
return encoded_jwt
security = HTTPBearer()
oauth2_provider = OAuth2Provider()
def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
token = credentials.credentials
payload = oauth2_provider.verify_token(token)
return payload
@app.get("/protected")
async def protected_route(current_user: dict = Depends(get_current_user)):
return {"message": "This is a protected route", "user": current_user}
Testing Strategies
Authentication Testing
class AuthenticationTester {
async runAuthenticationTests(): Promise<TestSuiteResult> {
const tests = [
this.testOAuth2Flow,
this.testJWTValidation,
this.testAPIKeyAuthentication,
this.testmTLSAuthentication,
this.testTokenExpiration,
this.testTokenRevocation,
this.testRateLimiting,
this.testSecurityVulnerabilities
];
const results: TestResult[] = [];
for (const test of tests) {
try {
const result = await test.call(this);
results.push(result);
} catch (error) {
results.push({
name: test.name,
passed: false,
error: error.message,
duration: 0
});
}
}
return this.generateTestSuiteResult(results);
}
private async testOAuth2Flow(): Promise<TestResult> {
const startTime = Date.now();
try {
// Test authorization request
const authRequest = {
clientId: 'test-client',
redirectUri: 'http://localhost:3000/callback',
scope: 'read write',
state: 'test-state'
};
const authResponse = await this.oauth2Provider.authorizeRequest(authRequest);
if (!authResponse.code) {
throw new Error('Authorization code not generated');
}
// Test token exchange
const tokenRequest = {
grantType: 'authorization_code',
code: authResponse.code,
clientId: 'test-client',
clientSecret: 'test-secret',
redirectUri: 'http://localhost:3000/callback'
};
const tokenResponse = await this.oauth2Provider.exchangeCodeForToken(tokenRequest);
if (!tokenResponse.accessToken) {
throw new Error('Access token not generated');
}
// Test token validation
const validation = await this.oauth2Provider.validateToken(tokenResponse.accessToken);
if (!validation.valid) {
throw new Error('Token validation failed');
}
return {
name: 'testOAuth2Flow',
passed: true,
duration: Date.now() - startTime,
metrics: {
authCodeGenerated: true,
tokenExchanged: true,
tokenValidated: true
}
};
} catch (error) {
return {
name: 'testOAuth2Flow',
passed: false,
error: error.message,
duration: Date.now() - startTime
};
}
}
private async testJWTValidation(): Promise<TestResult> {
const startTime = Date.now();
try {
// Generate test token
const token = await this.jwtManager.generateToken({
sub: 'test-user',
aud: 'test-client',
scope: 'read write',
expiresIn: 3600
});
// Test token verification
const verification = await this.jwtManager.verifyToken(token);
if (!verification.valid) {
throw new Error('JWT verification failed');
}
// Test expired token
const expiredToken = await this.jwtManager.generateToken({
sub: 'test-user',
aud: 'test-client',
scope: 'read write',
expiresIn: -1 // Expired
});
const expiredVerification = await this.jwtManager.verifyToken(expiredToken);
if (expiredVerification.valid) {
throw new Error('Expired token should not be valid');
}
return {
name: 'testJWTValidation',
passed: true,
duration: Date.now() - startTime,
metrics: {
tokenGenerated: true,
tokenVerified: true,
expirationChecked: true
}
};
} catch (error) {
return {
name: 'testJWTValidation',
passed: false,
error: error.message,
duration: Date.now() - startTime
};
}
}
}
Best Practices Summary
Development Best Practices
- Choose the Right Method: Select authentication method based on use case, security requirements, and complexity tolerance
- Implement Proper Validation: Validate all tokens, certificates, and API keys thoroughly
- Use Short-Lived Tokens: Implement token expiration and refresh mechanisms
- Implement Rate Limiting: Prevent abuse with appropriate rate limiting strategies
- Log Security Events: Monitor and log all authentication events for security analysis
Security Best Practices
- Secure Key Storage: Never store secrets in source code; use environment variables or key management services
- Implement Key Rotation: Regularly rotate keys and certificates
- Use HTTPS/TLS: Encrypt all communications
- Validate Input: Sanitize and validate all user inputs
- Monitor for Attacks: Implement intrusion detection and monitoring
Operational Best Practices
- Monitor Authentication Metrics: Track success rates, failure rates, and performance
- Implement Circuit Breakers: Handle authentication service failures gracefully
- Plan for Failures: Implement fallback authentication mechanisms
- Regular Security Audits: Conduct regular security assessments
- Document Everything: Maintain detailed documentation and runbooks
Conclusion
API authentication is a critical component of modern applications. The choice of authentication method depends on your specific requirements for security, complexity, and user experience. OAuth 2.0/OIDC provides the best user experience for web applications, JWT offers stateless token management, API keys are simple for service-to-service communication, and mTLS provides the highest security for sensitive applications.
The key to successful API authentication is implementing proper security measures, monitoring authentication events, and continuously improving based on real-world usage patterns. By following the patterns and best practices outlined in this guide, you can build secure, scalable authentication systems that protect your APIs while providing a good user experience.
Remember that authentication is just one part of a comprehensive security strategy. Implement proper authorization, input validation, rate limiting, and monitoring to create a robust security posture for your APIs.
FAQ
Q: When should I use OAuth 2.0 vs JWT vs API keys? A: Use OAuth 2.0 for user authentication in web applications, JWT for stateless token management, and API keys for simple service-to-service communication. Choose based on your security requirements and complexity tolerance.
Q: How do I implement proper token revocation? A: Implement token blacklisting, use short-lived tokens with refresh mechanisms, and consider using token introspection endpoints for real-time validation.
Q: What's the best way to store API keys securely? A: Never store API keys in source code. Use environment variables, key management services, or secure configuration management systems.
Q: How do I handle authentication failures gracefully? A: Implement proper error handling, provide clear error messages, implement circuit breakers, and have fallback authentication mechanisms.
Q: What metrics should I monitor for authentication systems? A: Monitor authentication success/failure rates, token usage patterns, rate limit violations, security events, and performance metrics.