Authentication best practices
Security best practices for authentication implementation, including threat modeling, advanced patterns, and security checklists.
This guide covers security best practices for implementing authentication with Scalekit. Use it for threat modeling, advanced security patterns, and production-ready configurations.
Security threat model
Section titled “Security threat model”Common authentication threats
Section titled “Common authentication threats”Identify potential security threats to implement appropriate countermeasures:
| Threat | Description | Mitigation |
|---|---|---|
| CSRF attacks | Malicious requests executed on behalf of authenticated users | Use state parameter, validate origins |
| Token theft | Access tokens intercepted or stolen | Secure storage, short lifetimes, refresh rotation |
| Session fixation | Attacker fixes session ID before authentication | Regenerate sessions, secure cookies |
| Phishing | Users tricked into entering credentials on fake sites | Domain validation, HTTPS enforcement |
| Replay attacks | Intercepted requests replayed by attackers | Nonces, timestamps, request signing |
Multi-tenant security considerations
Section titled “Multi-tenant security considerations”B2B applications face additional security challenges:
- Tenant isolation - Prevent data leakage between organizations
- Admin privilege escalation - Secure organization admin roles
- SSO configuration tampering - Protect identity provider settings
- Cross-tenant user enumeration - Prevent user discovery across organizations
Advanced security patterns
Section titled “Advanced security patterns”Dynamic security policy enforcement
Section titled “Dynamic security policy enforcement”Apply organization-specific security policies:
// Apply organization-specific security requirementsasync function createAuthorizationUrl(orgId, userEmail) { const redirectUri = 'https://yourapp.com/auth/callback';
// Fetch organization security policy const securityPolicy = await getSecurityPolicy(orgId);
// Apply conditional authentication requirements const options = { scopes: ['openid', 'profile', 'email', 'offline_access'], organizationId: orgId, loginHint: userEmail, state: generateSecureState(),
// Force re-authentication for high-security orgs prompt: securityPolicy.requireReauth ? 'login' : undefined, maxAge: securityPolicy.maxSessionAge || 3600, acrValues: securityPolicy.requiredAuthLevel || 'aal1' };
return scalekit.getAuthorizationUrl(redirectUri, options);}# Apply organization-specific security requirementsasync def create_authorization_url(org_id, user_email): redirect_uri = 'https://yourapp.com/auth/callback'
# Fetch organization security policy security_policy = await get_security_policy(org_id)
# Apply conditional authentication requirements options = AuthorizationUrlOptions( scopes=['openid', 'profile', 'email', 'offline_access'], organization_id=org_id, login_hint=user_email, state=generate_secure_state(),
# Force re-authentication for high-security orgs prompt='login' if security_policy.require_reauth else None, max_age=security_policy.max_session_age or 3600, acr_values=security_policy.required_auth_level or 'aal1' )
return scalekit.get_authorization_url(redirect_uri, options)// Apply organization-specific security requirementsfunc createAuthorizationUrl(orgId, userEmail string) (string, error) { redirectUri := "https://yourapp.com/auth/callback"
// Fetch organization security policy securityPolicy, err := getSecurityPolicy(orgId) if err != nil { return "", err }
// Apply conditional authentication requirements options := scalekit.AuthorizationUrlOptions{ Scopes: []string{"openid", "profile", "email", "offline_access"}, OrganizationId: orgId, LoginHint: userEmail, State: generateSecureState(),
// Force re-authentication for high-security orgs Prompt: conditionalPrompt(securityPolicy.RequireReauth), MaxAge: securityPolicy.MaxSessionAge, AcrValues: securityPolicy.RequiredAuthLevel, }
authUrl, err := scalekit.GetAuthorizationUrl(redirectUri, options) return authUrl.String(), err}// Apply organization-specific security requirementspublic String createAuthorizationUrl(String orgId, String userEmail) { String redirectUri = "https://yourapp.com/auth/callback";
// Fetch organization security policy SecurityPolicy securityPolicy = getSecurityPolicy(orgId);
// Apply conditional authentication requirements AuthorizationUrlOptions options = new AuthorizationUrlOptions(); options.setScopes(Arrays.asList("openid", "profile", "email", "offline_access")); options.setOrganizationId(orgId); options.setLoginHint(userEmail); options.setState(generateSecureState());
// Force re-authentication for high-security orgs if (securityPolicy.isRequireReauth()) { options.setPrompt("login"); } options.setMaxAge(securityPolicy.getMaxSessionAge()); options.setAcrValues(securityPolicy.getRequiredAuthLevel());
URL authUrl = scalekit.authentication().getAuthorizationUrl(redirectUri, options); return authUrl.toString();}Request signing and validation
Section titled “Request signing and validation”Verify request integrity with signatures:
const crypto = require('crypto');
// Sign sensitive requests with HMACfunction signRequest(payload, secret) { const timestamp = Date.now().toString(); const nonce = crypto.randomBytes(16).toString('hex');
// Create signature payload const signaturePayload = `${timestamp}.${nonce}.${JSON.stringify(payload)}`; const signature = crypto .createHmac('sha256', secret) .update(signaturePayload) .digest('hex');
return { payload, timestamp, nonce, signature: `sha256=${signature}` };}
// Verify request signaturesfunction verifyRequest(receivedPayload, receivedSignature, secret, maxAge = 300) { const [timestamp, nonce, payload] = receivedPayload.split('.');
// Check timestamp to prevent replay attacks if (Date.now() - parseInt(timestamp) > maxAge * 1000) { throw new Error('Request timestamp too old'); }
// Verify signature const expectedPayload = `${timestamp}.${nonce}.${payload}`; const expectedSignature = crypto .createHmac('sha256', secret) .update(expectedPayload) .digest('hex');
if (!crypto.timingSafeEqual( Buffer.from(receivedSignature, 'hex'), Buffer.from(`sha256=${expectedSignature}`, 'hex') )) { throw new Error('Invalid signature'); }
return JSON.parse(payload);}import hmacimport hashlibimport jsonimport timeimport secrets
# Sign sensitive requests with HMACdef sign_request(payload, secret): timestamp = str(int(time.time() * 1000)) nonce = secrets.token_hex(16)
# Create signature payload signature_payload = f"{timestamp}.{nonce}.{json.dumps(payload)}" signature = hmac.new( secret.encode(), signature_payload.encode(), hashlib.sha256 ).hexdigest()
return { 'payload': payload, 'timestamp': timestamp, 'nonce': nonce, 'signature': f"sha256={signature}" }
# Verify request signaturesdef verify_request(received_payload, received_signature, secret, max_age=300): timestamp, nonce, payload = received_payload.split('.')
# Check timestamp to prevent replay attacks if time.time() * 1000 - int(timestamp) > max_age * 1000: raise ValueError('Request timestamp too old')
# Verify signature expected_payload = f"{timestamp}.{nonce}.{payload}" expected_signature = hmac.new( secret.encode(), expected_payload.encode(), hashlib.sha256 ).hexdigest()
if not hmac.compare_digest( received_signature, f"sha256={expected_signature}" ): raise ValueError('Invalid signature')
return json.loads(payload)import ( "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "time")
// Sign sensitive requests with HMACfunc signRequest(payload interface{}, secret string) (map[string]interface{}, error) { timestamp := fmt.Sprintf("%d", time.Now().UnixMilli())
nonceBytes := make([]byte, 16) rand.Read(nonceBytes) nonce := hex.EncodeToString(nonceBytes)
// Create signature payload payloadJSON, _ := json.Marshal(payload) signaturePayload := fmt.Sprintf("%s.%s.%s", timestamp, nonce, payloadJSON)
h := hmac.New(sha256.New, []byte(secret)) h.Write([]byte(signaturePayload)) signature := hex.EncodeToString(h.Sum(nil))
return map[string]interface{}{ "payload": payload, "timestamp": timestamp, "nonce": nonce, "signature": fmt.Sprintf("sha256=%s", signature), }, nil}
// Verify request signaturesfunc verifyRequest(receivedPayload, receivedSignature, secret string, maxAge int64) (interface{}, error) { // Parse payload components parts := strings.Split(receivedPayload, ".") if len(parts) != 3 { return nil, fmt.Errorf("invalid payload format") }
timestamp, err := strconv.ParseInt(parts[0], 10, 64) if err != nil { return nil, fmt.Errorf("invalid timestamp") }
// Check timestamp to prevent replay attacks if time.Now().UnixMilli()-timestamp > maxAge*1000 { return nil, fmt.Errorf("request timestamp too old") }
// Verify signature expectedPayload := receivedPayload h := hmac.New(sha256.New, []byte(secret)) h.Write([]byte(expectedPayload)) expectedSignature := fmt.Sprintf("sha256=%s", hex.EncodeToString(h.Sum(nil)))
if !hmac.Equal([]byte(receivedSignature), []byte(expectedSignature)) { return nil, fmt.Errorf("invalid signature") }
var payload interface{} if err := json.Unmarshal([]byte(parts[2]), &payload); err != nil { return nil, fmt.Errorf("invalid payload JSON") }
return payload, nil}import javax.crypto.Mac;import javax.crypto.spec.SecretKeySpec;import java.security.SecureRandom;import java.nio.charset.StandardCharsets;import java.util.HashMap;import java.util.Map;
// Sign sensitive requests with HMACpublic Map<String, Object> signRequest(Object payload, String secret) throws Exception { String timestamp = String.valueOf(System.currentTimeMillis());
SecureRandom random = new SecureRandom(); byte[] nonceBytes = new byte[16]; random.nextBytes(nonceBytes); String nonce = bytesToHex(nonceBytes);
// Create signature payload String payloadJson = objectMapper.writeValueAsString(payload); String signaturePayload = timestamp + "." + nonce + "." + payloadJson;
Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); mac.init(secretKey); byte[] signatureBytes = mac.doFinal(signaturePayload.getBytes(StandardCharsets.UTF_8)); String signature = "sha256=" + bytesToHex(signatureBytes);
Map<String, Object> result = new HashMap<>(); result.put("payload", payload); result.put("timestamp", timestamp); result.put("nonce", nonce); result.put("signature", signature);
return result;}
// Verify request signaturespublic Object verifyRequest(String receivedPayload, String receivedSignature, String secret, long maxAge) throws Exception { String[] parts = receivedPayload.split("\\."); if (parts.length != 3) { throw new SecurityException("Invalid payload format"); }
long timestamp = Long.parseLong(parts[0]);
// Check timestamp to prevent replay attacks if (System.currentTimeMillis() - timestamp > maxAge * 1000) { throw new SecurityException("Request timestamp too old"); }
// Verify signature Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); mac.init(secretKey); byte[] expectedSignatureBytes = mac.doFinal(receivedPayload.getBytes(StandardCharsets.UTF_8)); String expectedSignature = "sha256=" + bytesToHex(expectedSignatureBytes);
if (!MessageDigest.isEqual( receivedSignature.getBytes(StandardCharsets.UTF_8), expectedSignature.getBytes(StandardCharsets.UTF_8) )) { throw new SecurityException("Invalid signature"); }
return objectMapper.readValue(parts[2], Object.class);}Secure token management
Section titled “Secure token management”Token storage strategies
Section titled “Token storage strategies”Select storage methods based on your application architecture:
| Storage Method | Security Level | Use Case | Considerations |
|---|---|---|---|
| HTTP-only cookies | High | Web applications | Prevents XSS, requires CSRF protection |
| Secure memory | High | Mobile/desktop apps | Cleared on app termination |
| Encrypted storage | Medium | Persistent sessions | Key management complexity |
| LocalStorage | Low | Not recommended | Vulnerable to XSS attacks |
Token rotation implementation
Section titled “Token rotation implementation”Implement secure refresh token rotation:
// Secure token refresh with rotationasync function refreshAccessToken(refreshToken, userId) { try { // Exchange refresh token for new tokens const tokenResponse = await scalekit.exchangeCodeForTokens({ refresh_token: refreshToken, grant_type: 'refresh_token' });
// Store new tokens securely const newTokens = { accessToken: tokenResponse.access_token, refreshToken: tokenResponse.refresh_token, // New refresh token expiresAt: Date.now() + (tokenResponse.expires_in * 1000), refreshExpiresAt: Date.now() + (30 * 24 * 60 * 60 * 1000) // 30 days };
// Update token storage atomically await updateUserTokens(userId, newTokens);
// Invalidate old refresh token await invalidateRefreshToken(refreshToken);
return newTokens;
} catch (error) { // Handle refresh failure if (error.code === 'invalid_grant') { // Refresh token expired or revoked await logoutUser(userId); throw new Error('Session expired, please login again'); }
// Log security event await logSecurityEvent('token_refresh_failed', { userId, error: error.message, timestamp: new Date().toISOString() });
throw error; }}
// Automatic token refresh middlewarefunction autoRefreshMiddleware(req, res, next) { const { accessToken, refreshToken, expiresAt } = req.session.tokens || {};
// Check if token expires within 5 minutes if (accessToken && Date.now() + (5 * 60 * 1000) >= expiresAt) { refreshAccessToken(refreshToken, req.session.userId) .then(newTokens => { req.session.tokens = newTokens; next(); }) .catch(error => { // Clear session on refresh failure req.session.destroy(); res.status(401).json({ error: 'Authentication required' }); }); } else { next(); }}import asynciofrom datetime import datetime, timedelta
# Secure token refresh with rotationasync def refresh_access_token(refresh_token, user_id): try: # Exchange refresh token for new tokens token_response = await scalekit.exchange_code_for_tokens({ 'refresh_token': refresh_token, 'grant_type': 'refresh_token' })
# Store new tokens securely new_tokens = { 'access_token': token_response['access_token'], 'refresh_token': token_response['refresh_token'], # New refresh token 'expires_at': datetime.now() + timedelta(seconds=token_response['expires_in']), 'refresh_expires_at': datetime.now() + timedelta(days=30) }
# Update token storage atomically await update_user_tokens(user_id, new_tokens)
# Invalidate old refresh token await invalidate_refresh_token(refresh_token)
return new_tokens
except Exception as error: # Handle refresh failure if hasattr(error, 'code') and error.code == 'invalid_grant': # Refresh token expired or revoked await logout_user(user_id) raise Exception('Session expired, please login again')
# Log security event await log_security_event('token_refresh_failed', { 'user_id': user_id, 'error': str(error), 'timestamp': datetime.now().isoformat() })
raise error
# Automatic token refresh decoratordef auto_refresh_tokens(func): async def wrapper(*args, **kwargs): request = kwargs.get('request') or args[0] tokens = getattr(request.session, 'tokens', {})
access_token = tokens.get('access_token') refresh_token = tokens.get('refresh_token') expires_at = tokens.get('expires_at')
# Check if token expires within 5 minutes if access_token and expires_at and datetime.now() + timedelta(minutes=5) >= expires_at: try: new_tokens = await refresh_access_token(refresh_token, request.session.user_id) request.session.tokens = new_tokens except Exception: # Clear session on refresh failure request.session.clear() raise AuthenticationError('Authentication required')
return await func(*args, **kwargs) return wrapperimport ( "context" "fmt" "time")
type TokenSet struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` ExpiresAt time.Time `json:"expires_at"` RefreshExpiresAt time.Time `json:"refresh_expires_at"`}
// Secure token refresh with rotationfunc refreshAccessToken(ctx context.Context, refreshToken, userID string) (*TokenSet, error) { // Exchange refresh token for new tokens tokenResponse, err := scalekit.ExchangeCodeForTokens(ctx, &scalekit.TokenRequest{ RefreshToken: refreshToken, GrantType: "refresh_token", }) if err != nil { return nil, fmt.Errorf("token exchange failed: %w", err) }
// Store new tokens securely newTokens := &TokenSet{ AccessToken: tokenResponse.AccessToken, RefreshToken: tokenResponse.RefreshToken, // New refresh token ExpiresAt: time.Now().Add(time.Duration(tokenResponse.ExpiresIn) * time.Second), RefreshExpiresAt: time.Now().Add(30 * 24 * time.Hour), // 30 days }
// Update token storage atomically if err := updateUserTokens(ctx, userID, newTokens); err != nil { return nil, fmt.Errorf("failed to update tokens: %w", err) }
// Invalidate old refresh token if err := invalidateRefreshToken(ctx, refreshToken); err != nil { // Log but don't fail the operation logSecurityEvent(ctx, "refresh_token_invalidation_failed", map[string]interface{}{ "user_id": userID, "error": err.Error(), }) }
return newTokens, nil}
// Automatic token refresh middlewarefunc autoRefreshMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session := getSession(r) tokens := session.Tokens
// Check if token expires within 5 minutes if tokens != nil && time.Until(tokens.ExpiresAt) <= 5*time.Minute { newTokens, err := refreshAccessToken(r.Context(), tokens.RefreshToken, session.UserID) if err != nil { // Clear session on refresh failure clearSession(w, r) http.Error(w, "Authentication required", http.StatusUnauthorized) return }
session.Tokens = newTokens saveSession(w, r, session) }
next.ServeHTTP(w, r) })}import java.time.Instant;import java.time.temporal.ChronoUnit;import java.util.concurrent.CompletableFuture;
public class TokenSet { private String accessToken; private String refreshToken; private Instant expiresAt; private Instant refreshExpiresAt;
// constructors, getters, setters...}
// Secure token refresh with rotationpublic CompletableFuture<TokenSet> refreshAccessToken(String refreshToken, String userId) { return CompletableFuture.supplyAsync(() -> { try { // Exchange refresh token for new tokens TokenResponse tokenResponse = scalekit.authentication() .exchangeCodeForTokens(TokenRequest.builder() .refreshToken(refreshToken) .grantType("refresh_token") .build());
// Store new tokens securely TokenSet newTokens = new TokenSet(); newTokens.setAccessToken(tokenResponse.getAccessToken()); newTokens.setRefreshToken(tokenResponse.getRefreshToken()); // New refresh token newTokens.setExpiresAt(Instant.now().plusSeconds(tokenResponse.getExpiresIn())); newTokens.setRefreshExpiresAt(Instant.now().plus(30, ChronoUnit.DAYS));
// Update token storage atomically updateUserTokens(userId, newTokens);
// Invalidate old refresh token invalidateRefreshToken(refreshToken);
return newTokens;
} catch (Exception e) { // Handle refresh failure if (e instanceof InvalidGrantException) { // Refresh token expired or revoked logoutUser(userId); throw new AuthenticationException("Session expired, please login again"); }
// Log security event logSecurityEvent("token_refresh_failed", Map.of( "user_id", userId, "error", e.getMessage(), "timestamp", Instant.now().toString() ));
throw new RuntimeException(e); } });}
// Automatic token refresh interceptor@Componentpublic class AutoRefreshInterceptor implements HandlerInterceptor {
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(false); if (session == null) return true;
TokenSet tokens = (TokenSet) session.getAttribute("tokens"); if (tokens == null) return true;
// Check if token expires within 5 minutes if (tokens.getExpiresAt().minus(5, ChronoUnit.MINUTES).isBefore(Instant.now())) { try { String userId = (String) session.getAttribute("userId"); TokenSet newTokens = refreshAccessToken(tokens.getRefreshToken(), userId).get(); session.setAttribute("tokens", newTokens); } catch (Exception e) { // Clear session on refresh failure session.invalidate(); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("{\"error\":\"Authentication required\"}"); return false; } }
return true; }}Security monitoring and incident response
Section titled “Security monitoring and incident response”Security event logging
Section titled “Security event logging”Log security events for monitoring and analysis:
// Define security event typesconst SECURITY_EVENTS = { LOGIN_SUCCESS: 'login_success', LOGIN_FAILURE: 'login_failure', TOKEN_REFRESH: 'token_refresh', SUSPICIOUS_ACTIVITY: 'suspicious_activity', PRIVILEGE_ESCALATION: 'privilege_escalation', DATA_ACCESS: 'sensitive_data_access'};
// Security event loggerasync function logSecurityEvent(eventType, details) { const event = { type: eventType, timestamp: new Date().toISOString(), severity: getSeverityLevel(eventType), details: { ...details, userAgent: details.userAgent, ipAddress: details.ipAddress, sessionId: details.sessionId } };
// Store in security log await securityLogger.log(event);
// Trigger alerts for high-severity events if (event.severity === 'HIGH' || event.severity === 'CRITICAL') { await triggerSecurityAlert(event); }}
// Anomaly detectionasync function detectAnomalies(userId, loginEvent) { const recentLogins = await getRecentLogins(userId, '24h');
// Check for unusual patterns const anomalies = [];
// Geographic anomaly if (isUnusualLocation(loginEvent.location, recentLogins)) { anomalies.push('unusual_location'); }
// Time-based anomaly if (isUnusualTime(loginEvent.timestamp, recentLogins)) { anomalies.push('unusual_time'); }
// Device anomaly if (isUnusualDevice(loginEvent.device, recentLogins)) { anomalies.push('unusual_device'); }
if (anomalies.length > 0) { await logSecurityEvent(SECURITY_EVENTS.SUSPICIOUS_ACTIVITY, { userId, anomalies, loginEvent }); }
return anomalies;}Rate limiting and abuse prevention
Section titled “Rate limiting and abuse prevention”Apply rate limiting to prevent abuse:
// Multi-tier rate limitingclass SecurityRateLimiter { constructor() { this.limits = { // Per-IP limits login_attempts: { window: 900, max: 10 }, // 10 attempts per 15 min token_requests: { window: 3600, max: 100 }, // 100 requests per hour
// Per-user limits user_login_attempts: { window: 3600, max: 5 }, // 5 attempts per hour user_token_refresh: { window: 3600, max: 50 }, // 50 refreshes per hour
// Global limits total_requests: { window: 60, max: 10000 } // 10k requests per minute }; }
async checkLimit(type, identifier, customLimit = null) { const limit = customLimit || this.limits[type]; if (!limit) return { allowed: true };
const key = `${type}:${identifier}`; const current = await redis.get(key) || 0;
if (current >= limit.max) { await this.logRateLimitExceeded(type, identifier, current); return { allowed: false, retryAfter: await redis.ttl(key), current: current, max: limit.max }; }
// Increment counter with expiration await redis.multi() .incr(key) .expire(key, limit.window) .exec();
return { allowed: true, current: current + 1, max: limit.max }; }
// Dynamic rate limiting based on risk async getDynamicLimit(type, riskScore) { const baseLimit = this.limits[type]; if (riskScore > 0.8) { return { ...baseLimit, max: Math.floor(baseLimit.max * 0.2) }; } else if (riskScore > 0.6) { return { ...baseLimit, max: Math.floor(baseLimit.max * 0.5) }; } return baseLimit; }}
// Rate limiting middlewareasync function rateLimitMiddleware(req, res, next) { const limiter = new SecurityRateLimiter(); const clientIP = req.ip; const userId = req.session?.userId;
// Check IP-based limits const ipLimit = await limiter.checkLimit('login_attempts', clientIP); if (!ipLimit.allowed) { return res.status(429).json({ error: 'Too many requests', retryAfter: ipLimit.retryAfter }); }
// Check user-based limits if authenticated if (userId) { const userLimit = await limiter.checkLimit('user_login_attempts', userId); if (!userLimit.allowed) { return res.status(429).json({ error: 'Too many login attempts', retryAfter: userLimit.retryAfter }); } }
next();}import asyncioimport timefrom typing import Dict, Optional
class SecurityRateLimiter: def __init__(self): self.limits = { # Per-IP limits 'login_attempts': {'window': 900, 'max': 10}, # 10 attempts per 15 min 'token_requests': {'window': 3600, 'max': 100}, # 100 requests per hour
# Per-user limits 'user_login_attempts': {'window': 3600, 'max': 5}, # 5 attempts per hour 'user_token_refresh': {'window': 3600, 'max': 50}, # 50 refreshes per hour
# Global limits 'total_requests': {'window': 60, 'max': 10000} # 10k requests per minute }
async def check_limit(self, limit_type: str, identifier: str, custom_limit: Optional[Dict] = None): limit = custom_limit or self.limits.get(limit_type) if not limit: return {'allowed': True}
key = f"{limit_type}:{identifier}" current = await redis.get(key) or 0 current = int(current)
if current >= limit['max']: await self.log_rate_limit_exceeded(limit_type, identifier, current) ttl = await redis.ttl(key) return { 'allowed': False, 'retry_after': ttl, 'current': current, 'max': limit['max'] }
# Increment counter with expiration pipeline = redis.pipeline() pipeline.incr(key) pipeline.expire(key, limit['window']) await pipeline.execute()
return {'allowed': True, 'current': current + 1, 'max': limit['max']}
# Dynamic rate limiting based on risk async def get_dynamic_limit(self, limit_type: str, risk_score: float): base_limit = self.limits[limit_type].copy() if risk_score > 0.8: base_limit['max'] = int(base_limit['max'] * 0.2) elif risk_score > 0.6: base_limit['max'] = int(base_limit['max'] * 0.5) return base_limit
# Rate limiting decoratordef rate_limit(limit_type: str): def decorator(func): async def wrapper(*args, **kwargs): request = kwargs.get('request') or args[0] limiter = SecurityRateLimiter() client_ip = request.client.host user_id = getattr(request.session, 'user_id', None)
# Check IP-based limits ip_limit = await limiter.check_limit(limit_type, client_ip) if not ip_limit['allowed']: raise HTTPException( status_code=429, detail={ 'error': 'Too many requests', 'retry_after': ip_limit['retry_after'] } )
# Check user-based limits if authenticated if user_id: user_limit = await limiter.check_limit(f'user_{limit_type}', user_id) if not user_limit['allowed']: raise HTTPException( status_code=429, detail={ 'error': 'Too many attempts', 'retry_after': user_limit['retry_after'] } )
return await func(*args, **kwargs) return wrapper return decoratorimport ( "context" "fmt" "time")
type RateLimit struct { Window time.Duration Max int}
type SecurityRateLimiter struct { limits map[string]RateLimit redis RedisClient}
func NewSecurityRateLimiter(redis RedisClient) *SecurityRateLimiter { return &SecurityRateLimiter{ redis: redis, limits: map[string]RateLimit{ // Per-IP limits "login_attempts": {Window: 15 * time.Minute, Max: 10}, "token_requests": {Window: time.Hour, Max: 100},
// Per-user limits "user_login_attempts": {Window: time.Hour, Max: 5}, "user_token_refresh": {Window: time.Hour, Max: 50},
// Global limits "total_requests": {Window: time.Minute, Max: 10000}, }, }}
type LimitResult struct { Allowed bool RetryAfter int64 Current int Max int}
func (rl *SecurityRateLimiter) CheckLimit(ctx context.Context, limitType, identifier string, customLimit *RateLimit) (*LimitResult, error) { limit := customLimit if limit == nil { l, exists := rl.limits[limitType] if !exists { return &LimitResult{Allowed: true}, nil } limit = &l }
key := fmt.Sprintf("%s:%s", limitType, identifier) current, err := rl.redis.Get(ctx, key).Int() if err != nil && err != redis.Nil { return nil, err }
if current >= limit.Max { ttl, _ := rl.redis.TTL(ctx, key).Result() await rl.logRateLimitExceeded(limitType, identifier, current) return &LimitResult{ Allowed: false, RetryAfter: int64(ttl.Seconds()), Current: current, Max: limit.Max, }, nil }
// Increment counter with expiration pipe := rl.redis.Pipeline() pipe.Incr(ctx, key) pipe.Expire(ctx, key, limit.Window) _, err = pipe.Exec(ctx) if err != nil { return nil, err }
return &LimitResult{ Allowed: true, Current: current + 1, Max: limit.Max, }, nil}
// Dynamic rate limiting based on riskfunc (rl *SecurityRateLimiter) GetDynamicLimit(limitType string, riskScore float64) *RateLimit { baseLimit, exists := rl.limits[limitType] if !exists { return nil }
if riskScore > 0.8 { return &RateLimit{ Window: baseLimit.Window, Max: int(float64(baseLimit.Max) * 0.2), } } else if riskScore > 0.6 { return &RateLimit{ Window: baseLimit.Window, Max: int(float64(baseLimit.Max) * 0.5), } }
return &baseLimit}
// Rate limiting middlewarefunc (rl *SecurityRateLimiter) RateLimitMiddleware(limitType string) gin.HandlerFunc { return func(c *gin.Context) { clientIP := c.ClientIP() userID, _ := c.Get("userID")
// Check IP-based limits ipLimit, err := rl.CheckLimit(c.Request.Context(), limitType, clientIP, nil) if err != nil { c.JSON(500, gin.H{"error": "Internal server error"}) c.Abort() return }
if !ipLimit.Allowed { c.JSON(429, gin.H{ "error": "Too many requests", "retry_after": ipLimit.RetryAfter, }) c.Abort() return }
// Check user-based limits if authenticated if userID != nil { userLimit, err := rl.CheckLimit(c.Request.Context(), "user_"+limitType, userID.(string), nil) if err != nil { c.JSON(500, gin.H{"error": "Internal server error"}) c.Abort() return }
if !userLimit.Allowed { c.JSON(429, gin.H{ "error": "Too many attempts", "retry_after": userLimit.RetryAfter, }) c.Abort() return } }
c.Next() }}import java.time.Duration;import java.time.Instant;import java.util.Map;import java.util.HashMap;import java.util.concurrent.CompletableFuture;
public class RateLimit { private final Duration window; private final int max;
// constructors, getters...}
@Componentpublic class SecurityRateLimiter { private final Map<String, RateLimit> limits; private final RedisTemplate<String, String> redisTemplate;
public SecurityRateLimiter(RedisTemplate<String, String> redisTemplate) { this.redisTemplate = redisTemplate; this.limits = Map.of( // Per-IP limits "login_attempts", new RateLimit(Duration.ofMinutes(15), 10), "token_requests", new RateLimit(Duration.ofHours(1), 100),
// Per-user limits "user_login_attempts", new RateLimit(Duration.ofHours(1), 5), "user_token_refresh", new RateLimit(Duration.ofHours(1), 50),
// Global limits "total_requests", new RateLimit(Duration.ofMinutes(1), 10000) ); }
public static class LimitResult { private final boolean allowed; private final long retryAfter; private final int current; private final int max;
// constructors, getters... }
public CompletableFuture<LimitResult> checkLimit(String limitType, String identifier, RateLimit customLimit) { return CompletableFuture.supplyAsync(() -> { RateLimit limit = customLimit != null ? customLimit : limits.get(limitType); if (limit == null) { return new LimitResult(true, 0, 0, 0); }
String key = limitType + ":" + identifier; String currentStr = redisTemplate.opsForValue().get(key); int current = currentStr != null ? Integer.parseInt(currentStr) : 0;
if (current >= limit.getMax()) { Long ttl = redisTemplate.getExpire(key); logRateLimitExceeded(limitType, identifier, current); return new LimitResult(false, ttl, current, limit.getMax()); }
// Increment counter with expiration redisTemplate.opsForValue().increment(key); redisTemplate.expire(key, limit.getWindow());
return new LimitResult(true, 0, current + 1, limit.getMax()); }); }
// Dynamic rate limiting based on risk public RateLimit getDynamicLimit(String limitType, double riskScore) { RateLimit baseLimit = limits.get(limitType); if (baseLimit == null) return null;
if (riskScore > 0.8) { return new RateLimit(baseLimit.getWindow(), (int) (baseLimit.getMax() * 0.2)); } else if (riskScore > 0.6) { return new RateLimit(baseLimit.getWindow(), (int) (baseLimit.getMax() * 0.5)); }
return baseLimit; }}
// Rate limiting interceptor@Componentpublic class RateLimitInterceptor implements HandlerInterceptor {
private final SecurityRateLimiter rateLimiter;
public RateLimitInterceptor(SecurityRateLimiter rateLimiter) { this.rateLimiter = rateLimiter; }
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String clientIP = getClientIP(request); String userID = getUserID(request);
// Check IP-based limits LimitResult ipLimit = rateLimiter.checkLimit("login_attempts", clientIP, null).get(); if (!ipLimit.isAllowed()) { response.setStatus(429); response.getWriter().write(String.format( "{\"error\":\"Too many requests\",\"retry_after\":%d}", ipLimit.getRetryAfter() )); return false; }
// Check user-based limits if authenticated if (userID != null) { LimitResult userLimit = rateLimiter.checkLimit("user_login_attempts", userID, null).get(); if (!userLimit.isAllowed()) { response.setStatus(429); response.getWriter().write(String.format( "{\"error\":\"Too many attempts\",\"retry_after\":%d}", userLimit.getRetryAfter() )); return false; } }
return true; }
private String getClientIP(HttpServletRequest request) { String xForwardedFor = request.getHeader("X-Forwarded-For"); if (xForwardedFor != null && !xForwardedFor.isEmpty()) { return xForwardedFor.split(",")[0].trim(); } return request.getRemoteAddr(); }
private String getUserID(HttpServletRequest request) { HttpSession session = request.getSession(false); return session != null ? (String) session.getAttribute("userID") : null; }}Production security checklist
Section titled “Production security checklist”Pre-deployment validation
Section titled “Pre-deployment validation”-
Environment security
-
Authentication configuration
-
Session management
-
Rate limiting and monitoring
Security testing procedures
Section titled “Security testing procedures”Test security measures before production deployment:
# OWASP ZAP security scanzap-cli quick-scan --self-contained \ --start-options '-config api.disablekey=true' \ https://your-app.com
# SSL/TLS configuration testtestssl --full https://your-app.com
# CSRF protection testcurl -X POST https://your-app.com/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"test@example.com"}'
# Rate limiting testfor i in {1..20}; do curl -X POST https://your-app.com/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"test@example.com","password":"wrong"}'doneIncident response procedures
Section titled “Incident response procedures”Define procedures for handling security incidents:
- Detection - Automated alerts for suspicious activities
- Assessment - Rapid impact evaluation and threat classification
- Containment - Immediate actions to limit damage
- Investigation - Forensic analysis and root cause identification
- Recovery - System restoration and security improvements
- Communication - Stakeholder notifications and compliance reporting
Production requirements
Section titled “Production requirements”- Use HTTPS - Required in production for secure token transmission
- Store tokens securely - Use HTTP-only cookies or secure server-side storage
- Validate redirects - Configure allowed redirect URIs in your dashboard
This guide provides the foundation for implementing robust authentication security. Combine these patterns with regular security assessments and stay updated on emerging threats.