Authentication Remediation

Risk Severity
๐Ÿ”ด Critical
Fix Effort
๐Ÿ”ง Medium
Est. Time
โฑ๏ธ 2-4 hours
Reference
A07:2021 CWE-287

Broken authentication vulnerabilities allow attackers to compromise passwords, keys, or session tokens, or to exploit other implementation flaws to assume other users' identities.

Never Roll Your Own Crypto

Authentication is one of the easiest things to get wrong. Always use battle-tested libraries and frameworks rather than implementing your own password hashing or session management. A single oversight can compromise all user accounts.

Secure Password Storage

Passwords must never be stored in plain text. Use strong, slow hashing algorithms like Argon2, bcrypt, or scrypt.

Argon2 โญ

Winner of Password Hashing Competition (2015). Memory-hard, resistant to GPU/ASIC attacks. Recommended for new projects.

bcrypt

Industry standard since 1999. Well-tested, widely supported. Work factor (cost) adjustable. Great choice for most applications.

scrypt

Memory-hard like Argon2. Good for high-security applications. More complex to configure correctly.

โŒ Never Use These for Passwords

MD5, SHA-1, SHA-256 โ€” These are fast hashes designed for integrity checking, not password storage. GPUs can compute billions of SHA-256 hashes per second, making brute-force attacks trivial.

Python Implementation

Using bcrypt:

python
import bcrypt

def hash_password(password: str) -> bytes:
    # Generate salt and hash (work factor 12)
    return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(12))

def verify_password(password: str, hashed: bytes) -> bool:
    return bcrypt.checkpw(password.encode('utf-8'), hashed)

Using argon2 (Preferred for new applications):

python
from argon2 import PasswordHasher

ph = PasswordHasher()

def hash_password(password: str) -> str:
    return ph.hash(password)

def verify_password(password: str, hashed: str) -> bool:
    try:
        return ph.verify(hashed, password)
    except:
        return False

Node.js Implementation

Using bcrypt:

javascript
const bcrypt = require('bcrypt');
const SALT_ROUNDS = 12;

async function hashPassword(password) {
    return await bcrypt.hash(password, SALT_ROUNDS);
}

async function verifyPassword(password, hash) {
    return await bcrypt.compare(password, hash);
}

PHP Implementation

Using password_hash (uses bcrypt by default):

php
// Hash password
$hash = password_hash($password, PASSWORD_DEFAULT);

// Verify password
if (password_verify($password, $hash)) {
    // Password correct
}

// For Argon2 (PHP 7.2+)
$hash = password_hash($password, PASSWORD_ARGON2ID);

Session Management

Sessions must be protected against hijacking and fixation attacks. Ensure cookies are secure and sessions are properly invalidated.

๐Ÿช Cookie Security Flags Explained

Secure

Cookie only sent over HTTPS. Prevents interception on insecure networks.

HttpOnly

Cookie inaccessible to JavaScript. Prevents XSS from stealing session tokens.

SameSite

Controls cross-site cookie sending. Lax prevents CSRF on POST requests.

Max-Age / Expires

Session timeout. Shorter = more secure but less convenient. 1 hour is a good balance.

Flask (Python)

python
from flask import Flask, session
from datetime import timedelta

app = Flask(__name__)
app.config.update(
    SECRET_KEY='your-secure-secret-key-here',
    SESSION_COOKIE_SECURE=True,       # HTTPS only
    SESSION_COOKIE_HTTPONLY=True,     # No JavaScript access
    SESSION_COOKIE_SAMESITE='Lax',    # CSRF protection
    PERMANENT_SESSION_LIFETIME=timedelta(hours=1),
    SESSION_REFRESH_EACH_REQUEST=True
)

Express.js (Node.js)

javascript
const session = require('express-session');
const RedisStore = require('connect-redis')(session);

app.use(session({
    store: new RedisStore({ client: redisClient }),
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: {
        secure: true,          // HTTPS only
        httpOnly: true,        // No JavaScript access
        sameSite: 'lax',       // CSRF protection
        maxAge: 3600000        // 1 hour
    }
}));

PHP

php
// Session configuration
ini_set('session.cookie_secure', 1);     // HTTPS only
ini_set('session.cookie_httponly', 1);   // No JavaScript
ini_set('session.cookie_samesite', 'Lax');
ini_set('session.use_strict_mode', 1);
ini_set('session.gc_maxlifetime', 3600); // 1 hour

session_start();

// Regenerate session ID after login
session_regenerate_id(true);

Authentication Hardening Checklist

  • โœ… Use bcrypt, Argon2, or scrypt for password hashing
  • โœ… Enforce minimum password length (12+ characters)
  • โœ… Check passwords against known breached password lists
  • โœ… Implement account lockout after failed attempts
  • โœ… Use MFA for sensitive operations
  • โœ… Regenerate session IDs after authentication
  • โœ… Set secure cookie flags (Secure, HttpOnly, SameSite)
  • โœ… Implement proper logout (invalidate server-side session)
  • โœ… Use rate limiting on authentication endpoints

๐Ÿงช Testing Verification

Use these tests to verify your authentication implementation is secure.

Password Storage Tests

# Check if passwords are hashed (not plain text)

Database query should return hash starting with $2b$ (bcrypt), $argon2 (Argon2)

# Verify timing attack resistance

Response time should be constant regardless of username validity

# Test account enumeration

Error messages should be generic: "Invalid credentials" (not "User not found")

Session Security Tests

bash
# Check cookie flags with curl
curl -I https://yoursite.com/login -c - | grep -i "set-cookie"

# Expected flags in cookie:
# Secure; HttpOnly; SameSite=Lax

# Test session fixation
1. Note session ID before login
2. Login successfully  
3. Verify session ID changed after login

Rate Limiting Tests

bash
# Test brute force protection with hydra (test environment only!)
hydra -l admin -P /usr/share/wordlists/rockyou.txt \
  http-post-form "//login:username=^USER^&password=^PASS^:Invalid"

# Expected: Account lockout after 5-10 failed attempts
# Expected: 429 Too Many Requests response

โš ๏ธ Common Mistakes

โŒ Weak Password Hashing

python
# DON'T: Using MD5/SHA for passwords
hash = hashlib.md5(password).hexdigest()
hash = hashlib.sha256(password).hexdigest()

Fast hashes allow billions of guesses per second

โœ… Correct Approach

python
# DO: Use purpose-built password hashing
from argon2 import PasswordHasher
ph = PasswordHasher()
hash = ph.hash(password)

Memory-hard algorithms resist GPU attacks

โŒ User Enumeration

python
# DON'T: Different messages reveal info
if not user_exists(email):
    return "User not found"
if not password_correct:
    return "Wrong password"

Attackers can discover valid usernames

โœ… Correct Approach

python
# DO: Generic error message
if not authenticate(email, password):
    return "Invalid credentials"
# Same message whether user exists or not

No information leakage to attackers

โŒ Missing Session Regeneration

python
# DON'T: Keep same session ID after login
session['user_id'] = user.id
# Session ID unchanged = session fixation!

Allows session fixation attacks

โœ… Correct Approach

python
# DO: Regenerate session after authentication
session.clear()
session.regenerate()  # New session ID
session['user_id'] = user.id

Prevents session fixation attacks