blog.dopana

Back

In software engineering, Authentication is notoriously deceptive. It is deceptively easy to get functioning—a junior developer can write basic registration and login flows in an afternoon—but extraordinarily difficult to make bulletproof and secure.

Many catastrophic data breaches across the industry did not originate from elite zero-day exploits, but rather from fundamental mistakes embedded right into custom authentication systems.

1. Explain Like I’m 10 (ELI5): The Steel Vault and the Doormat#

Imagine building a high-security house:

  • You spend thousands of dollars installing a massive 8-inch steel vault door at the front entrance (Enforcing 16-character complex passwords).
  • But you leave the spare house key under the front doormat (Storing JWT tokens in localStorage vulnerable to XSS).
  • You leave the ground floor back window unlocked (Omitting login rate limits and brute-force throttling).
  • And when a stranger rings the doorbell asking “Does Bob live here?”, you honestly answer: “Yes, Bob lives here, but he’s asleep” (User enumeration vulnerability confirming account existence).

No matter how thick your front door is, an intruder walks in without breaking a sweat.

flowchart TD
    subgraph Vulnerable["Vulnerable Auth Pipeline"]
        A1["Wrong password entered"] --> B1["Error: 'User exists, wrong password' - Leaks User"]
        A2["Brute Force Attempt"] --> B2["No Rate Limiting - Password cracked"]
        A3["Password Reset"] --> B3["Token never expires + Sessions remain active"]
    end

    subgraph Secure["Hardened Auth Pipeline"]
        C1["Wrong password entered"] --> D1["Generic: 'Invalid credentials'"]
        C2["5 Failed Attempts"] --> D2["15-min IP Throttling + CAPTCHA"]
        C3["Password Reset"] --> D3["Secure Token TTL 15m + All sessions revoked"]
    end

2. The 7 Most Dangerous Authentication Mistakes#

Mistake 1: Hashing Passwords with Fast Algorithms (MD5, SHA-1, Vanilla SHA-256)#

A widespread misconception among developers is that passing a password through SHA-256 makes it secure. This is critically wrong.

  • The Problem: General-purpose algorithms like SHA-256 and MD5 are designed to be extremely fast, calculating billions of hashes per second.
  • The Risk: In the event of a database leak, an attacker using modern consumer GPUs can calculate tens of billions of SHA-256 hashes per second to reverse passwords via rainbow tables and brute-force dictionaries.

[!TIP] The Fix: Always use dedicated memory-hard, slow password hashing algorithms:

  1. Argon2id (The #1 OWASP recommended algorithm).
  2. bcrypt (with a work factor >= 12).
  3. scrypt or PBKDF2.

Mistake 2: Leaking Account Existence (User Enumeration)#

When an authentication request fails, systems frequently reveal too much detail:

  • ❌ “Email not found in our database.”
  • ❌ “Incorrect password for user@example.com.”

An attacker can automate a credential-checking script against your endpoint with a list of 100,000 corporate emails. Your API neatly categorizes which people have accounts, setting the stage for targeted spear-phishing and password spraying.

[!NOTE] The Fix: Return uniform, neutral responses:

  • Failed Login: “Invalid email or password.”
  • Password Reset: “If an account exists with that email, a password reset link has been dispatched.”

Mistake 3: Missing Rate Limiting and Timing Attack Protections#

Without request rate limiting:

  • Attackers run endless credential stuffing attacks against user accounts.
  • Automated bots exhaust server CPU resources on password verification routines.

Furthermore, naive string comparisons (===) can leak secrets via Timing Attacks (where the server responds micro-seconds faster when leading characters match):

src/utils/crypto.ts
import crypto from 'crypto';

// ❌ BAD: Leaks string length and character matches via execution time
// if (userProvidedToken === serverSecret) ...

// ✅ GOOD: Constant-time comparison
export function safeEqual(a: string, b: string): boolean {
  const bufA = Buffer.from(a);
  const bufB = Buffer.from(b);
  
  if (bufA.length !== bufB.length) return false;
  return crypto.timingSafeEqual(bufA, bufB);
}
typescript

Mistake 4: Flawed Password Reset Workflows#

Password reset is the single most targeted component of authentication:

  1. Weak or Predictable Tokens: Generating 4-digit codes or using pseudo-random generators (Math.random()) instead of cryptographic entropy (crypto.randomBytes(32)).
  2. Infinite Token Lifetimes: Reset links that never expire.
  3. Failing to Invalidate Active Sessions: After changing a password, existing session cookies and refresh tokens remain valid on stolen devices!

[!WARNING] Upon any password reset or password change, you MUST immediately invalidate all active Sessions and Refresh Tokens across all connected devices!

Mistake 5: Confusing Authentication with Authorization (IDOR)#

  • Authentication (401 Unauthorized): Verifies who you are (e.g., User A).
  • Authorization (403 Forbidden): Verifies what you are allowed to access.

Developers often assume that once a user is authenticated, route parameters can be trusted. This causes critical Insecure Direct Object Reference (IDOR) vulnerabilities:

src/routes/invoice.ts
// ❌ IDOR VULNERABILITY: Any authenticated user can read other users' invoices!
app.get('/api/invoices/:id', authenticateUser, async (req, res) => {
  const invoice = await db.invoices.findById(req.params.id);
  // Missing Check: if (invoice.userId !== req.user.id) return res.status(403);
  return res.json(invoice);
});
typescript

Mistake 6: Insecure Client Token Storage (LocalStorage vs. Cookies)#

  • Storing Tokens in localStorage: Vulnerable to Cross-Site Scripting (XSS). Any rogue third-party npm package or injected script can extract all user tokens instantly.
  • Storing in Cookies without Protection Flags: Forgetting to apply HttpOnly, Secure, and SameSite=Lax/Strict flags, leaving apps exposed to CSRF.

Mistake 7: “Rolling Your Own Cryptography”#

Writing bespoke cryptographic scrambling functions or homebrewed auth handshakes under the belief that “hackers cannot break what they cannot see” is known as Security through Obscurity—a strategy guaranteed to fail.

Established protocols like OAuth 2.0, OpenID Connect, and WebAuthn have undergone decades of rigorous academic and industry scrutiny.

3. Production Authentication Pre-Flight Checklist#

Security RequirementStatusRecommendation
Password Storage🟩Argon2id or bcrypt (cost factor >= 12)
Rate Limiting🟩Max 5 failed attempts per IP/Account per minute
Error Messages🟩Opaque responses; eliminate user enumeration
Reset Tokens🟩256-bit cryptographically secure, single-use, TTL < 15m
Password Change🟩Revoke all existing active sessions and refresh tokens
Cookie Flags🟩Mandatory HttpOnly, Secure, and SameSite=Lax
Object Authorization🟩Explicitly verify resource.ownerId === req.user.id

4. Summary#

A resilient authentication system does not require forcing users into arbitrary 30-character password rules. It relies on architectural rigor, defensive programming, and adherence to proven cryptographic standards.

Audit your auth pipelines against these common vulnerabilities before deploying to production.

References#