# Authentication best practices

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

### 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

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

### Dynamic security policy enforcement

Apply organization-specific security policies:

```javascript title="Dynamic security policies" {5-8,15-18} "getSecurityPolicy"
// Apply organization-specific security requirements
async 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);
}
```
```python title="Dynamic security policies" {5-8,15-18} "get_security_policy"
# Apply organization-specific security requirements
async 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)
```
```go title="Dynamic security policies" {5-8,15-18} "GetSecurityPolicy"
// Apply organization-specific security requirements
func 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 := scalekitClient.GetAuthorizationUrl(redirectUri, options)
	return authUrl.String(), err
}
```
```java title="Dynamic security policies" {5-8,15-18} "getSecurityPolicy"
// Apply organization-specific security requirements
public 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

Verify request integrity with signatures:

```javascript title="Request signing" {8-12,20-24} "signRequest"
const crypto = require('crypto');

// Sign sensitive requests with HMAC
function 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 signatures
function 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);
}
```
```python title="Request signing" {9-13,23-27} "sign_request"
import hmac
import hashlib
import json
import time
import secrets

# Sign sensitive requests with HMAC
def 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 signatures
def 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)
```
```go title="Request signing" {10-16,25-29} "SignRequest"
import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"time"
)

// Sign sensitive requests with HMAC
func 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 signatures
func 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
}
```
```java title="Request signing" {11-17,26-30} "signRequest"
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 HMAC
public 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 signatures
public 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

### 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

Implement secure refresh token rotation:

```javascript title="Token rotation" {15-20,30-35} "rotateTokens"
// Secure token refresh with rotation
async 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 middleware
function 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();
  }
}
```
```python title="Token rotation" {16-21,33-38} "refresh_access_token"
import asyncio
from datetime import datetime, timedelta

# Secure token refresh with rotation
async 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 decorator
def 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 wrapper
```
```go title="Token rotation" {17-23,35-40} "RefreshAccessToken"
import (
	"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 rotation
func 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 middleware
func 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)
	})
}
```
```java title="Token rotation" {18-24,38-43} "refreshAccessToken"
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 rotation
public 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
@Component
public 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

### Security event logging

Log security events for monitoring and analysis:

```javascript title="security-events.js"
// Define security event types
const 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 logger
async 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 detection
async 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

Apply rate limiting to prevent abuse:

```javascript title="Advanced rate limiting" {10-15,25-30} "RateLimiter"
// Multi-tier rate limiting
class 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 middleware
async 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();
}
```
```python title="Advanced rate limiting" {11-16,28-33} "SecurityRateLimiter"
import asyncio
import time
from 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 decorator
def 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 decorator
```
```go title="Advanced rate limiting" {12-17,30-35} "SecurityRateLimiter"
import (
	"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 risk
func (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 middleware
func (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()
	}
}
```
```java title="Advanced rate limiting" {13-18,32-37} "SecurityRateLimiter"
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...
}

@Component
public 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
@Component
public 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

### Pre-deployment validation

1. **Environment security**
   - [ ] All secrets stored in secure environment variables
   - [ ] HTTPS enforced in production (no mixed content)
   - [ ] Security headers configured (HSTS, CSP, X-Frame-Options)
   - [ ] Database connections encrypted

2. **Authentication configuration**
   - [ ] Redirect URIs validated and restricted
   - [ ] Token lifetimes appropriate for security requirements
   - [ ] Refresh token rotation enabled
   - [ ] State parameter validation implemented

3. **Session management**
   - [ ] Secure session storage configured
   - [ ] Session timeout policies defined
   - [ ] Concurrent session limits set
   - [ ] Session invalidation on logout

4. **Rate limiting and monitoring**
   - [ ] Rate limiting configured for all auth endpoints
   - [ ] Security event logging implemented
   - [ ] Anomaly detection systems deployed
   - [ ] Alert systems configured

### Security testing procedures

Test security measures before production deployment:

```bash title="Security testing commands"
# OWASP ZAP security scan
zap-cli quick-scan --self-contained \
  --start-options '-config api.disablekey=true' \
  https://your-app.com

# SSL/TLS configuration test
testssl --full https://your-app.com

# CSRF protection test
curl -X POST https://your-app.com/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com"}'

# Rate limiting test
for 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"}'
done
```

### Incident response procedures

Define procedures for handling security incidents:

1. **Detection** - Automated alerts for suspicious activities
2. **Assessment** - Rapid impact evaluation and threat classification
3. **Containment** - Immediate actions to limit damage
4. **Investigation** - Forensic analysis and root cause identification
5. **Recovery** - System restoration and security improvements
6. **Communication** - Stakeholder notifications and compliance reporting
**Security is an ongoing process:** Security implementation continues after deployment. Review and update security measures regularly, monitor for new threats, and maintain incident response capabilities.

### 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.