Files
security-patterns/authentication.md
T
Rodin 647928a0a1 Initial commit: 9 security patterns for code review
Fundamentals: secure-defaults, input-validation, credential-handling, audit-logging
Identity: authentication, authorization
Attack Prevention: injection-prevention, dos-prevention, prompt-injection
2026-05-10 22:45:03 -07:00

4.1 KiB

Authentication

Rule

Verify identity before granting access. Use proven libraries, not DIY crypto.

Source: OWASP Authentication Cheat Sheet

Password Handling

Correct Pattern

import bcrypt
import secrets

def hash_password(password: str) -> bytes:
    """Hash password using bcrypt with automatic salt."""
    return bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))

def verify_password(password: str, hashed: bytes) -> bool:
    """Verify password against hash. Constant-time comparison."""
    return bcrypt.checkpw(password.encode(), hashed)

# Password requirements
MIN_PASSWORD_LENGTH = 12
COMMON_PASSWORDS = load_common_passwords()  # Top 10k list

def validate_password(password: str) -> list[str]:
    """Return list of validation errors."""
    errors = []
    if len(password) < MIN_PASSWORD_LENGTH:
        errors.append(f"Password must be at least {MIN_PASSWORD_LENGTH} characters")
    if password.lower() in COMMON_PASSWORDS:
        errors.append("Password is too common")
    return errors

Incorrect Pattern

# Wrong: plain text storage
user.password = password

# Wrong: weak hashing
user.password = hashlib.md5(password.encode()).hexdigest()

# Wrong: SHA without salt
user.password = hashlib.sha256(password.encode()).hexdigest()

# Wrong: reversible encryption
user.password = encrypt(password, key)

# Wrong: timing attack vulnerable
if user.password == submitted_password:
    grant_access()

Token Management

Correct Pattern

import secrets
from datetime import datetime, timedelta

def generate_token() -> str:
    """Generate cryptographically secure token."""
    return secrets.token_urlsafe(32)

def generate_session(user_id: str) -> dict:
    """Create session with expiration."""
    return {
        "token": generate_token(),
        "user_id": user_id,
        "created_at": datetime.utcnow(),
        "expires_at": datetime.utcnow() + timedelta(hours=24),
    }

def validate_session(session: dict) -> bool:
    """Check session validity."""
    if datetime.utcnow() > session["expires_at"]:
        return False
    return True

Incorrect Pattern

# Wrong: predictable tokens
token = f"session_{user_id}_{int(time.time())}"

# Wrong: no expiration
session = {"token": token, "user_id": user_id}

# Wrong: client-controlled expiration
if request.cookies.get("expires") > now:  # User can modify!
    grant_access()

Multi-Factor Authentication

import pyotp

def setup_totp(user_id: str) -> str:
    """Generate TOTP secret for user."""
    secret = pyotp.random_base32()
    store_totp_secret(user_id, secret)
    return secret

def verify_totp(user_id: str, code: str) -> bool:
    """Verify TOTP code with time window."""
    secret = get_totp_secret(user_id)
    totp = pyotp.TOTP(secret)
    return totp.verify(code, valid_window=1)  # ±30 seconds

Brute Force Protection

from collections import defaultdict
import time

class LoginRateLimiter:
    def __init__(self):
        self.attempts = defaultdict(list)
        self.lockouts = {}
    
    def record_attempt(self, identifier: str, success: bool):
        now = time.time()
        
        if not success:
            self.attempts[identifier].append(now)
            # Clean old attempts
            self.attempts[identifier] = [
                t for t in self.attempts[identifier]
                if now - t < 3600  # 1 hour window
            ]
            
            # Lockout after 5 failures
            if len(self.attempts[identifier]) >= 5:
                self.lockouts[identifier] = now + 900  # 15 min lockout
        else:
            self.attempts[identifier] = []
            self.lockouts.pop(identifier, None)
    
    def is_locked(self, identifier: str) -> bool:
        lockout_until = self.lockouts.get(identifier, 0)
        return time.time() < lockout_until

Edge Cases

  • Timing attacks on username enumeration
  • Account lockout as DOS vector
  • Session fixation attacks
  • Token leakage in logs/URLs
  • Password reset token reuse