System Design: Security and Authentication

Introduction

In modern software systems, security and authentication are foundational pillars that safeguard data, ensure user trust, and protect business assets. This tutorial walks through the essential concepts, design patterns, and practical implementations required to build robust authentication mechanisms within large‑scale system architectures.

Why Security & Authentication Matter in System Design

A well‑designed system must anticipate threat vectors, enforce principle of least privilege, and provide resilient identity management. Failure to embed these concerns early leads to costly retrofits, compliance violations, and potential data breaches.

Fundamental Concepts

Authentication vs. Authorization

  • Authentication – verifying the identity of a user or service (e.g., login, token validation).
  • Authorization – determining what an authenticated entity is allowed to do (e.g., role‑based access control).

Types of Authentication Factors

  1. Something you know – passwords, PINs.
  2. Something you have – hardware tokens, OTP apps.
  3. Something you are – biometrics (fingerprint, facial recognition).

Designing Secure Authentication Flows

Password Management Best Practices

Passwords remain the most common first factor. Secure handling includes:

  • Enforce minimum length (≥12 characters) and complexity.
  • Use a slow, memory‑hard hashing algorithm such as Argon2id or bcrypt with a cost factor calibrated to your hardware.
  • Never store passwords in plaintext or reversible encryption.
# Python example using Argon2id
from argon2 import PasswordHasher
ph = PasswordHasher(time_cost=3, memory_cost=64 * 1024, parallelism=4)
hashed = ph.hash('UserSuppliedPassword')
# Store `hashed` in DB
# Verify later
ph.verify(hashed, 'UserSuppliedPassword')
// Node.js example using bcrypt
const bcrypt = require('bcrypt');
const saltRounds = 12;
const password = 'UserSuppliedPassword';
bcrypt.hash(password, saltRounds, function(err, hash) {
  // Store `hash` in DB
});
// Verification
bcrypt.compare(password, hashFromDb, function(err, result) {
  // result === true if match
});
⚠ Warning: Never use fast hash functions like MD5, SHA1, or even plain SHA256 for password storage. They are vulnerable to GPU‑accelerated brute‑force attacks.

Multi‑Factor Authentication (MFA)

MFA dramatically reduces the risk of credential stuffing. Typical implementations:

  • Time‑Based One‑Time Passwords (TOTP) – RFC 6238 (Google Authenticator, Authy).
  • Push‑based verification – services send a prompt to a registered device.
  • Hardware security keys – FIDO2 / WebAuthn (YubiKey, Feitian).
# Python TOTP generation using pyotp
import pyotp
secret = pyotp.random_base32()
totp = pyotp.TOTP(secret)
print('Current OTP:', totp.now())
# Store `secret` for the user securely.
# During login, verify with `totp.verify(user_provided_code)`

Token‑Based Authentication

Stateless tokens enable scalable distributed systems. The most common format is JSON Web Token (JWT).

  • Access token – short‑lived (5‑15 min) bearer token containing scopes/claims.
  • Refresh token – long‑lived (days‑weeks) stored securely (HTTP‑Only, SameSite) and used to obtain new access tokens.
// Node.js example issuing a JWT with jsonwebtoken
const jwt = require('jsonwebtoken');
const payload = { sub: user.id, role: user.role };
const secret = process.env.JWT_SECRET;
const accessToken = jwt.sign(payload, secret, { expiresIn: '10m' });
const refreshToken = jwt.sign(payload, secret, { expiresIn: '30d' });
// Return both tokens to the client
📝 Note: Never store JWTs in localStorage. Use secure, HTTP‑Only cookies with the SameSite=Lax or Strict attribute to mitigate XSS attacks.

OAuth 2.0 and OpenID Connect (OIDC)

OAuth 2.0 provides delegated authorization, while OIDC adds identity verification on top of OAuth.

  • Authorization Code Flow with PKCE – recommended for native and SPA clients.
  • Client Credentials Flow – for service‑to‑service communication.
  • Implicit Flow – deprecated; avoid in new designs.
The OAuth 2.0 Security Best Current Practice (BCP) recommends the Authorization Code Flow with PKCE as the default for all public clients.

PKCE Overview

Proof Key for Code Exchange (PKCE) mitigates authorization code interception attacks by binding the code exchange to a secret generated by the client.

# Example of PKCE generation (JavaScript)
function generatePKCE() {
  const verifier = crypto.randomUUID().replace(/-/g, '');
  const challenge = crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier))
    .then(buf => btoa(String.fromCharCode(...new Uint8Array(buf)))
      .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''));
  return { verifier, challenge };
}

Secure Session Management

  • Rotate session identifiers after privilege elevation (e.g., login).
  • Set Secure, HttpOnly, and SameSite=Strict cookie attributes.
  • Implement idle and absolute session timeouts.
  • Store minimal data in the session; avoid sensitive PII.

Transport Layer Security (TLS) in System Design

All authentication traffic must be protected by TLS 1.2 or higher. Key considerations:

  • Terminate TLS at the edge (load balancer) and re‑encrypt traffic between internal services (mTLS).
  • Enable forward secrecy (ECDHE) cipher suites.
  • Pin certificates for critical service‑to‑service calls.

Security‑Centric Design Patterns

PatternPurposeTypical Use‑Case
Gateway AuthenticationCentralizes auth checks at API gatewayMicroservice APIs behind Kong/Envoy
Zero‑Trust NetworkNever trust network locationInternal service calls with mutual TLS
Credential VaultSecure storage of secretsHashiCorp Vault, AWS Secrets Manager
Token Revocation List (TRL)Invalidate compromised tokensOAuth refresh token revocation

Checklist for Secure Authentication Design

  1. Define threat model (STRIDE).
  2. Select appropriate authentication factors (MFA).
  3. Implement secure password hashing (Argon2id).
  4. Adopt OAuth 2.0 Authorization Code Flow with PKCE for public clients.
  5. Enforce short‑lived access tokens and rotating refresh tokens.
  6. Use HTTP‑Only, Secure, SameSite cookies for token storage.
  7. Enable TLS 1.3 with forward secrecy across all communication paths.
  8. Apply rate‑limiting and account lockout policies.
  9. Log authentication events with tamper‑evident storage.
  10. Conduct regular penetration testing and code reviews.

Common Pitfalls & How to Avoid Them

  • Storing passwords in reversible encryption – use one‑way hash functions only.
  • Embedding secrets in source code – use environment variables or secret managers.
  • Relying on client‑side validation alone – always enforce server‑side checks.
  • Using long‑lived JWTs without revocation – implement token blacklists or short TTL.
💡 Tip: When designing a multi‑region system, store refresh tokens in a shared, encrypted datastore (e.g., DynamoDB with server‑side encryption) to allow seamless token rotation across zones.
📘 Summary: Security and authentication are not afterthoughts; they are integral to system architecture. By combining strong password policies, modern MFA, token‑based flows, and rigorous TLS enforcement, engineers can build resilient, scalable services that protect users and business assets alike.

Q: What is the difference between OAuth 2.0 and OpenID Connect?
A: OAuth 2.0 is an authorization framework that allows a client to obtain limited access to a resource server. OpenID Connect builds on OAuth 2.0 by adding an identity layer, enabling the client to verify the end‑user's identity and retrieve profile information.


Q: Should I store refresh tokens in a database?
A: Yes. Persisting refresh tokens allows you to revoke them, detect reuse, and enforce rotation policies. Store them encrypted and associate them with user/device metadata.


Q: Is it safe to use JWTs without encryption?
A: JWTs are signed, not encrypted. They can be read by anyone possessing the token. Do not place sensitive data (PII, passwords) in JWT claims. If confidentiality is required, use JWE (JSON Web Encryption) or encrypt the payload separately.


Q. Which hash algorithm is recommended for password storage?
  • SHA‑256
  • MD5
  • Argon2id
  • Plaintext

Answer: Argon2id
Argon2id is a memory‑hard algorithm designed to resist GPU/ASIC attacks, making it the current best practice for password hashing.

Q. In the OAuth 2.0 Authorization Code Flow, what does PKCE add?
  • Long‑lived access tokens
  • Client secret verification
  • Proof key binding to the code
  • Refresh token revocation

Answer: Proof key binding to the code
PKCE generates a code verifier and challenge that ties the authorization request to the token exchange, preventing code interception.

References