home / skills / yanko-belov / code-craft / auth-patterns

auth-patterns skill

/skills/auth-patterns

This skill helps you implement secure authentication by enforcing bcrypt/Argon2 hashing, slow checks, and safe error handling across login, registration, and

npx playbooks add skill yanko-belov/code-craft --skill auth-patterns

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
5.1 KB
---
name: auth-patterns
description: Use when implementing authentication. Use when storing passwords. Use when asked to store credentials insecurely.
---

# Authentication Patterns

## Overview

**Never store plain passwords. Use proven auth patterns. Security is not optional.**

Authentication is the front door to your system. Get it wrong and everything else is compromised.

## When to Use

- Implementing login/registration
- Storing user credentials
- Verifying user identity
- Working with sessions or tokens

## The Iron Rule

```
NEVER store passwords in plain text. ALWAYS use slow hashing.
```

**No exceptions:**
- Not for "internal users only"
- Not for "we'll encrypt later"
- Not for "it's behind a firewall"
- Not for "just for development"

## Detection: Insecure Auth Smell

If passwords aren't properly hashed, STOP:

```typescript
// ❌ VIOLATION: Plain text password
await db.users.create({
  email,
  password: password  // Stored as-is!
});

// ❌ VIOLATION: Fast hash (crackable)
const hashed = crypto.createHash('sha256').update(password).digest('hex');

// ❌ VIOLATION: Reversible encryption
const encrypted = encrypt(password, key);  // Can be decrypted!
```

## The Correct Pattern: Bcrypt/Argon2

```typescript
// ✅ CORRECT: Slow, salted hashing with bcrypt
import bcrypt from 'bcrypt';

const SALT_ROUNDS = 12;  // Adjust based on your hardware

async function hashPassword(plain: string): Promise<string> {
  return bcrypt.hash(plain, SALT_ROUNDS);
}

async function verifyPassword(plain: string, hashed: string): Promise<boolean> {
  return bcrypt.compare(plain, hashed);
}

// Registration
app.post('/register', async (req, res) => {
  const { email, password } = validated(req.body);
  
  const hashedPassword = await hashPassword(password);
  
  await db.users.create({
    email,
    password: hashedPassword  // Store the hash
  });
  
  res.status(201).json({ success: true });
});

// Login
app.post('/login', async (req, res) => {
  const { email, password } = validated(req.body);
  
  const user = await db.users.findByEmail(email);
  
  // Constant-time comparison to prevent timing attacks
  // Always verify even if user not found
  const dummyHash = '$2b$12$dummy.hash.here';
  const isValid = await verifyPassword(password, user?.password ?? dummyHash);
  
  if (!user || !isValid) {
    // Same error for both cases - prevents user enumeration
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  
  const token = generateToken(user);
  res.json({ token });
});
```

## Authentication Checklist

### Password Storage
- [ ] Use bcrypt or Argon2 (slow hash)
- [ ] Salt rounds ≥ 12 for bcrypt
- [ ] Never store plain text
- [ ] Never use MD5/SHA1/SHA256 alone

### Login Security
- [ ] Constant-time comparison
- [ ] Same error for "user not found" and "wrong password"
- [ ] Rate limiting on login endpoint
- [ ] Account lockout after N failures

### Session/Token Security
- [ ] JWTs: short expiry, secure secret
- [ ] Sessions: secure, httpOnly cookies
- [ ] Implement token refresh properly
- [ ] Invalidate on logout/password change

## Pressure Resistance Protocol

### 1. "We'll Encrypt Later"
**Pressure:** "Just store it for now, we'll add encryption"

**Response:** Plain text passwords get leaked. Breaches happen fast.

**Action:** Hash from day one. It's 3 lines of code.

### 2. "It's Behind a Firewall"
**Pressure:** "Internal network, no one can access it"

**Response:** Firewalls get breached. Insiders exist. Defense in depth.

**Action:** Hash regardless of network security.

### 3. "SHA256 Is Secure"
**Pressure:** "SHA256 is a strong hash"

**Response:** SHA256 is fast - billions per second on GPU. Bcrypt is intentionally slow.

**Action:** Use bcrypt or Argon2. Speed is the enemy.

### 4. "Just for Development"
**Pressure:** "Dev database doesn't need security"

**Response:** Dev code becomes prod code. Dev habits become prod habits.

**Action:** Use proper hashing in all environments.

## Red Flags - STOP and Reconsider

- `password` column without "hash" in name
- Using `crypto.createHash` for passwords
- Comparing passwords with `===`
- Same error messages reveal user existence
- No rate limiting on auth endpoints

**All of these mean: Fix the auth implementation.**

## Quick Reference

| Insecure | Secure |
|----------|--------|
| Plain text storage | bcrypt/Argon2 hash |
| SHA256(password) | bcrypt.hash(password, 12) |
| `===` comparison | bcrypt.compare() |
| "User not found" error | "Invalid credentials" |
| Unlimited login attempts | Rate limiting + lockout |

## Common Rationalizations (All Invalid)

| Excuse | Reality |
|--------|---------|
| "We'll encrypt later" | Do it now. Takes 3 lines. |
| "Behind firewall" | Defense in depth required. |
| "SHA256 is secure" | Too fast. Use slow hashes. |
| "Just development" | Dev becomes prod. |
| "Internal users only" | Insiders cause breaches too. |
| "We trust our database" | Databases get dumped. |

## The Bottom Line

**Hash passwords with bcrypt. Use constant-time comparison. Return generic errors.**

Password security is non-negotiable. Use slow hashes (bcrypt, Argon2). Prevent timing attacks. Don't leak user existence. Rate limit everything.

Overview

This skill provides clear, actionable guidance for implementing secure authentication and storing credentials safely. It enforces the iron rule: never store plain passwords and always use slow, salted hashing like bcrypt or Argon2. Use it to harden login, registration, and credential storage flows.

How this skill works

The skill inspects authentication code patterns and highlights insecure practices: plain-text storage, fast hashes (SHA256/MD5), reversible encryption for passwords, naive equality checks, and leaking user existence. It recommends concrete fixes: slow salted hashing, constant-time verification, generic error responses, rate limiting, and session/token best practices.

When to use it

  • Implementing login or registration endpoints
  • Designing or reviewing password storage schema
  • When asked to store credentials insecurely or use fast hashes
  • Auditing authentication for timing/user-enumeration leaks
  • Configuring session or token lifecycles

Best practices

  • Always hash passwords with a slow, memory-hard algorithm (bcrypt or Argon2).
  • Use sufficient cost: bcrypt salt rounds ≥ 12 (tune for hardware).
  • Never store reversible encryption of passwords or plain text. Treat stored values as non-recoverable secrets.
  • Verify passwords with constant-time comparison and always run verification even if user not found (use a dummy hash).
  • Return the same generic error for authentication failures to avoid user enumeration; add rate limiting and account lockout.
  • Use short JWT expiry or secure, httpOnly session cookies and invalidate tokens on logout/password change.

Example use cases

  • Implementing registration: hash password before saving and never store plain text.
  • Login flow: compare with bcrypt.compare, use dummy hash when user missing, return 'Invalid credentials'.
  • Security review: flag occurrences of crypto.createHash, === comparisons, or plaintext password columns.
  • DevOps checklist: enforce hashing in all environments and add rate limiting on auth endpoints.
  • Token management: ensure JWT short expiry and refresh flow; invalidate tokens after password change.

FAQ

Why not use SHA256 for passwords?

SHA256 is fast and GPU-friendly; attackers can brute-force billions of hashes per second. Slow algorithms like bcrypt or Argon2 intentionally increase cost to resist cracking.

Is reversible encryption ever acceptable for passwords?

No. If you can decrypt a password, a breach exposes all credentials. Use one-way slow hashing so stored values are non-recoverable.

What about development environments?

Use the same hashing approach in dev. Dev practices often leak into production; hashing from day one avoids dangerous drift.