API Authentication: OAuth 2.0, JWT, and API Keys Compared

Oct 26, 2025
apiauthenticationoauthjwt
0

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

  1. Choose the Right Method: Select authentication method based on use case, security requirements, and complexity tolerance
  2. Implement Proper Validation: Validate all tokens, certificates, and API keys thoroughly
  3. Use Short-Lived Tokens: Implement token expiration and refresh mechanisms
  4. Implement Rate Limiting: Prevent abuse with appropriate rate limiting strategies
  5. Log Security Events: Monitor and log all authentication events for security analysis

Security Best Practices

  1. Secure Key Storage: Never store secrets in source code; use environment variables or key management services
  2. Implement Key Rotation: Regularly rotate keys and certificates
  3. Use HTTPS/TLS: Encrypt all communications
  4. Validate Input: Sanitize and validate all user inputs
  5. Monitor for Attacks: Implement intrusion detection and monitoring

Operational Best Practices

  1. Monitor Authentication Metrics: Track success rates, failure rates, and performance
  2. Implement Circuit Breakers: Handle authentication service failures gracefully
  3. Plan for Failures: Implement fallback authentication mechanisms
  4. Regular Security Audits: Conduct regular security assessments
  5. 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.

Related posts