Skip to content
Back to skills

Secure Auth Patterns

ASecurity

Authentication and session implementation patterns: password hashing, session cookie settings, JWT validation and its pitfalls, and access control checks, with language-specific examples. Use when building or reviewing login, sessions, tokens, or authorization code.

  • 4 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 1, 2026
securityjavascriptpythongojavanodeexpressdjangorailsspringgit

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 1, 2026

npx -y skills add HermeticOrmus/LibreSecOps-Claude-Code --skill secure-auth-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Secure Auth Patterns?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Secure Auth Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/hermeticormus-secure-auth-patterns/badge)](https://www.skillsdirectory.com/skills/hermeticormus-secure-auth-patterns)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: "secure-auth-patterns"
description: "Authentication and session implementation patterns: password hashing, session cookie settings, JWT validation and its pitfalls, and access control checks, with language-specific examples. Use when building or reviewing login, sessions, tokens, or authorization code."
---

# Secure Auth Patterns

> Authentication, session management, and access control implementation patterns with language-specific secure code examples.

## Knowledge Base

### Password Storage

Never store passwords in plaintext, encrypted form, or with weak hashes (MD5, SHA-1, unsalted SHA-256). Use adaptive hashing algorithms designed for password storage.

**Algorithm selection** (OWASP 2023 recommendations):

| Algorithm | Recommended Parameters | Notes |
|-----------|----------------------|-------|
| Argon2id | Memory: 19456 KB, Iterations: 2, Parallelism: 1 | Preferred. Memory-hard, GPU-resistant |
| bcrypt | Cost factor: 12+ | Widely supported, 72-byte input limit |
| scrypt | N: 2^17, r: 8, p: 1 | Memory-hard, less common than Argon2 |
| PBKDF2-SHA256 | 600,000 iterations | Only if above are unavailable (FIPS) |

**Implementation examples**:

```python
# Python - Argon2 (preferred)
from argon2 import PasswordHasher
ph = PasswordHasher(time_cost=2, memory_cost=19456, parallelism=1)
hash = ph.hash(password)
# Verify
try:
    ph.verify(hash, password)
    if ph.check_needs_rehash(hash):  # Upgrade params over time
        new_hash = ph.hash(password)
except VerifyMismatchError:
    # Invalid password

# Python - bcrypt
import bcrypt
hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
if bcrypt.checkpw(password.encode(), hash):
    # Valid
```

```javascript
// Node.js - bcrypt
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash(password, 12);
const valid = await bcrypt.compare(password, hash);

// Node.js - Argon2
const argon2 = require('argon2');
const hash = await argon2.hash(password, {
  type: argon2.argon2id,
  memoryCost: 19456,
  timeCost: 2,
  parallelism: 1
});
const valid = await argon2.verify(hash, password);
```

```go
// Go - bcrypt
import "golang.org/x/crypto/bcrypt"
hash, err := bcrypt.GenerateFromPassword([]byte(password), 12)
err = bcrypt.CompareHashAndPassword(hash, []byte(password))

// Go - Argon2
import "golang.org/x/crypto/argon2"
salt := make([]byte, 16)
rand.Read(salt)
hash := argon2.IDKey([]byte(password), salt, 2, 19456, 1, 32)
```

```java
// Java - bcrypt (jBCrypt)
String hash = BCrypt.hashpw(password, BCrypt.gensalt(12));
boolean valid = BCrypt.checkpw(password, hash);

// Java - Argon2 (argon2-jvm)
Argon2 argon2 = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id);
String hash = argon2.hash(2, 19456, 1, password.toCharArray());
boolean valid = argon2.verify(hash, password.toCharArray());
```

### Session Management

**Session token requirements**:
- Minimum 128 bits of entropy from a CSPRNG
- Transmitted only over HTTPS
- Cookie attributes: `Secure`, `HttpOnly`, `SameSite=Strict` (or `Lax`)
- Server-side session storage (not just a signed cookie containing all session data)
- Regenerate session ID on privilege level change (login, role change)
- Absolute timeout (max session lifetime) and idle timeout

**Cookie configuration**:
```
Set-Cookie: session=<token>; Secure; HttpOnly; SameSite=Strict; Path=/; Max-Age=3600
```

**Session lifecycle**:
1. **Creation**: Generate new session ID after successful authentication. Invalidate any pre-authentication session.
2. **Validation**: Check session exists, is not expired, belongs to the authenticated user, and has not been revoked.
3. **Renewal**: Regenerate session ID on privilege changes. Maintain session data but change the identifier.
4. **Termination**: Invalidate session server-side on logout. Clear the cookie client-side. Provide "logout everywhere" capability.

### JWT Security

JWTs are frequently misimplemented. Key requirements:

**Algorithm enforcement**:
```javascript
// VULNERABLE - Accepts any algorithm
jwt.verify(token, secret);

// SECURE - Explicitly specify allowed algorithms
jwt.verify(token, secret, { algorithms: ['HS256'] });

// For RS256 (asymmetric)
jwt.verify(token, publicKey, { algorithms: ['RS256'] });
```

**Critical claims to validate**:
- `exp` (expiration) -- always set, always check
- `iss` (issuer) -- verify against expected value
- `aud` (audience) -- verify the token is intended for this service
- `iat` (issued at) -- reject tokens issued in the future
- `nbf` (not before) -- reject tokens not yet valid

**JWT pitfalls**:
- Algorithm confusion: RS256 public key used as HS256 secret
- `alg: "none"` -- some libraries accept unsigned tokens
- Storing sensitive data in payload (JWT payload is base64, not encrypted)
- No revocation mechanism without additional infrastructure (token blacklist)
- Overly long expiration times

### Multi-Factor Authentication (MFA)

**TOTP implementation requirements** (RFC 6238):
- Secret: 160+ bits, generated server-side
- Time step: 30 seconds
- Allow clock skew: +/- 1 time step
- Rate limit verification attempts (prevent brute force)
- Provide backup codes (one-time use, stored hashed)
- Validate TOTP token before enabling MFA (user proves they set up authenticator correctly)

**MFA bypass prevention**:
- Do not issue session tokens until MFA is verified
- Do not reveal whether username/password was correct before MFA
- Implement step-up authentication for sensitive operations even within authenticated sessions
- Log MFA failures for anomaly detection

### Access Control (Authorization)

**Principles**:
- Deny by default: If no rule explicitly grants access, deny
- Check on every request: Do not rely on UI hiding features
- Server-side enforcement: Client-side checks are supplementary
- Resource-level checks: Verify the user can access THIS specific resource, not just this type of resource

**IDOR prevention**:
```python
# VULNERABLE - No ownership check
@app.route('/api/documents/<doc_id>')
def get_document(doc_id):
    return Document.query.get(doc_id)

# SECURE - Ownership verification
@app.route('/api/documents/<doc_id>')
@login_required
def get_document(doc_id):
    doc = Document.query.filter_by(
        id=doc_id,
        owner_id=current_user.id  # Scoped to user
    ).first_or_404()
    return doc
```

**Role-based access control (RBAC) pattern**:
```python
# Decorator-based authorization
def requires_role(role):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            if not current_user.has_role(role):
                abort(403)
            return f(*args, **kwargs)
        return wrapper
    return decorator

@app.route('/admin/users')
@login_required
@requires_role('admin')
def manage_users():
    ...
```

### CSRF Protection

**Token-based CSRF protection**:
- Generate a cryptographically random token per session
- Include in forms as a hidden field and validate on every state-changing request
- Use `SameSite=Strict` or `SameSite=Lax` cookies as defense-in-depth
- For APIs: Use custom request headers (e.g., `X-CSRF-Token`) that cannot be set by cross-origin requests

**Framework CSRF protection** (use it, do not disable it):
- Django: `{% csrf_token %}` in forms, `@csrf_protect` decorator
- Rails: `protect_from_forgery with: :exception`
- Spring: `CsrfFilter` (enabled by default in Spring Security)
- Express: `csurf` middleware (or custom double-submit cookie)

## Patterns

### Pattern: Authentication Service Isolation
Centralize authentication logic in a single service/module. All authentication decisions flow through this module. This prevents inconsistent authentication checks across endpoints and makes auditing straightforward.

### Pattern: Fail-Secure Authentication
On any error during authentication (database timeout, MFA service unavailable, token parsing failure), deny access. Never let an exception propagate to an implicit "allow."

### Pattern: Constant-Time Comparison
Use constant-time comparison for all security-sensitive string comparisons (tokens, hashes, API keys) to prevent timing attacks.
```python
import hmac
hmac.compare_digest(provided_token, stored_token)
```
```javascript
const crypto = require('crypto');
crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
```

### Pattern: Account Lockout with Progressive Delay
After N failed attempts, implement progressive delays rather than permanent lockout (which enables denial of service). Combine with CAPTCHA and IP-based rate limiting.

## Anti-Patterns

- **Rolling your own auth**: Writing custom password hashing, session management, or token generation instead of using established libraries and frameworks
- **Plaintext password logging**: Logging authentication attempts with the password included, even in "debug" mode
- **Client-side authorization**: Hiding UI elements but not enforcing access control server-side. Attackers do not use your UI
- **Sequential/predictable session IDs**: Using auto-incrementing integers, timestamps, or low-entropy values as session tokens
- **Password length limits**: Capping password length at 8-20 characters. Allow at least 128 characters (bcrypt's 72-byte limit is handled by pre-hashing with SHA-256)
- **Security questions as authentication factors**: "Mother's maiden name" is knowledge, not a factor. It is publicly discoverable and not resettable
- **JWT for sessions**: JWTs as session tokens add complexity (no server-side revocation) without clear benefit for most applications. Server-side sessions with opaque tokens are simpler and more secure
- **Shared secrets across services**: Each service should have its own API key/secret. Compromising one service should not compromise all

## References

- OWASP Authentication Cheat Sheet -- https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
- OWASP Session Management Cheat Sheet -- https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
- OWASP Password Storage Cheat Sheet -- https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
- OWASP CSRF Prevention Cheat Sheet -- https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
- OWASP JWT Security Cheat Sheet -- https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html
- NIST SP 800-63B: Digital Identity Guidelines (Authentication) -- https://pages.nist.gov/800-63-3/sp800-63b.html
- RFC 6238: TOTP Algorithm -- https://tools.ietf.org/html/rfc6238
- CWE-287: Improper Authentication -- https://cwe.mitre.org/data/definitions/287.html
- CWE-284: Improper Access Control -- https://cwe.mitre.org/data/definitions/284.html

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…