📚 Module: Unit 09 • Identity, Cryptography & Access Control
Authentication & Authorization: Zero-Trust Identity Engineering
A deep, first-principles textbook on enterprise web security. Master password hashing algorithms (Bcrypt work factors vs SHA-256), stateful Sessions vs stateless JSON Web Tokens (JWTs), secure Cookie policies (HttpOnly, SameSite, Secure), dual-token refresh rotation architecture, OAuth 2.0 Authorization Code flow with PKCE, Role-Based Access Control (RBAC), cryptographically signed password resets, and Multi-Factor Authentication (TOTP RFC 6238).
Section 01
Identity Fundamentals: AuthN vs. AuthZ & Cryptographic Password Hashing
Full-stack security begins with a rigorous distinction between two foundational concepts:
- Authentication (AuthN): "Who are you?" The verification of an entity's claimed identity through provided credentials (passwords, biometrics, signed tokens, or cryptographic keys).
- Authorization (AuthZ): "What are you permitted to do?" The policy enforcement mechanism determining whether an authenticated subject possesses the necessary permissions to perform an operation on a specific resource.
1. The Physics of Password Hashing: Why Fast Hashes Are Dangerous
Cryptographic hash algorithms such as MD5, SHA-1, and SHA-256 were designed for high-throughput data integrity verification. A modern GPU cluster can compute billions of SHA-256 hashes per second. If an attacker exfiltrates a database of SHA-256 password hashes, they can execute offline dictionary and brute-force attacks in minutes, pre-computing outputs into Rainbow Tables.
Password hashing requires intentionally slow, computationally expensive, adaptive key derivation functions:
$2b$12$e8Y4J2zB6q7A9eM1kL3oPuN9gV5tW8yX0rZ3sC1vB4nQ7wE2rT5uY
--- -- ---------------------- -----------------------------------
| | | |
| | | +--> 31-character checksum (184 bits)
| | +--> 22-character Base64 salt (128 bits)
| +--> Cost Factor = 12 (2^12 = 4,096 iterations)
+--> Algorithm Identifier: $2b$ (Bcrypt standard)
Section 02
Session-Based Authentication vs. Stateless JSON Web Tokens (JWT)
Enterprise architectures authenticate subsequent HTTP requests using one of two primary paradigms:
1. Stateful Session-Based Authentication
Upon successful login, the server generates a cryptographically random 128-bit Session ID, stores session state (e.g., user ID, permissions, IP address) in a shared in-memory data store (such as Redis), and transmits the Session ID to the browser inside a Set-Cookie response header. On subsequent requests, the browser automatically transmits the cookie. The server retrieves the session record from Redis.
- Pros: Immediate server-side revocation (calling redis.del(sessionId) instantly logs out the user); compact cookie size.
- Cons: Requires stateful infrastructure; every incoming request triggers an external database or Redis network round-trip; challenges in cross-domain multi-service topologies.
2. Stateless Token-Based Authentication (JWT)
A JSON Web Token (RFC 7519) is a compact, URL-safe container format consisting of three base64url-encoded parts separated by periods (header.payload.signature):
- Header: Specifies token type and cryptographic signature algorithm ({ "alg": "HS256", "typ": "JWT" }).
- Payload: Contains identity claims (sub: user ID, iat: issued at, exp: expiration, roles: ['admin']). The payload is signed, not encrypted—never store plaintext passwords or secrets inside a JWT!
- Signature: Cryptographic hash calculated over HMACSHA256(base64Url(header) + "." + base64Url(payload), secretKey).
System Architecture: Stateful Sessions vs Stateless JWT Authentication
MODEL A: STATEFUL SESSIONS (Shared In-Memory Store)
Browser Load Balancer Node Server Redis Cache
| | | |
|-- 1. POST /login -------------->|------------------------>| |
| | |-- 2. Validate Creds ----|
| | |-- 3. Set Session ID --->|
|<-- 4. Set-Cookie: sid=abc92x ---|<------------------------| |
| | | |
|-- 5. GET /api/profile --------->|------------------------>| (Read sid from cookie) |
| (Cookie: sid=abc92x) | |-- 6. Query Redis ------>|
| | |<-- 7. Return User State-|
|<-- 8. Return 200 JSON Profile --|<------------------------| |
MODEL B: STATELESS JSON WEB TOKENS (Decentralized Verification)
Browser Load Balancer Node Server Replica 1 Replica 2
| | | |
|-- 1. POST /login -------------->|------------------------>| |
| | |-- 2. Signs JWT locally -|
|<-- 3. Return { accessToken } ---|<------------------------| |
| | |
|-- 4. GET /api/orders ---------->|-------------------------------------------------->|
| (Header: Bearer eyJhbG...) | (Validates signature |
| | locally with public key; |
| | ZERO database lookups!) |
|<-- 5. Return 200 JSON Orders ---|<--------------------------------------------------|
Section 03
Browser Storage Security: Cookie Flags & Attack Vector Defenses
Where should client applications store authentication tokens? Storing tokens in browser localStorage or sessionStorage is an existential vulnerability: any malicious script injected through a Cross-Site Scripting (XSS) flaw can immediately access localStorage.getItem('token') and exfiltrate user credentials to an attacker's command-and-control server.
1. The Four Mandatory Security Cookie Flags
Tokens must be transmitted using HTTP Cookies configured with strict defensive flags:
- HttpOnly: Completely blocks client-side JavaScript from accessing the cookie via document.cookie. Completely neutralizes token exfiltration via XSS.
- Secure: Mandates that the browser will only transmit the cookie over encrypted TLS/HTTPS connections, preventing cleartext sniffing on public Wi-Fi networks.
- SameSite (CSRF Mitigation): Controls whether cookies are attached to cross-site requests:
- SameSite=Strict: Cookie is never sent on cross-site requests (e.g., following a link from an external website). Highest security.
- SameSite=Lax (Default): Cookie is withheld on cross-site subrequests (images, iframes), but included when a user navigates to the origin via top-level link clicks.
- SameSite=None: Cookie is sent on all cross-site requests; requires Secure flag (used for third-party widget embeds).
- Domain & Path: Strictly limits the scope of cookie transmission to specific subdomains (Domain=api.devascent.me) and endpoint roots (Path=/api/v1/auth).
Section 04
The Dual-Token Refresh Rotation Architecture
Stateless JWTs suffer from a major architectural drawback: once issued, a token cannot be revoked until it naturally expires. If an access token with a 30-day lifetime is compromised, the attacker retains full access for 30 days.
The enterprise solution is the Dual-Token Refresh Pattern with Automatic Rotation:
- Short-Lived Access Token: Lifetime of 5 to 15 minutes. Stored in memory (React JavaScript memory or closure). Transmitted in the Authorization: Bearer <token> header. Used for rapid, stateless endpoint access.
- Long-Lived Refresh Token: Lifetime of 7 to 30 days. Stored inside an HttpOnly, Secure, SameSite=Strict cookie restricted to the /api/v1/auth/refresh path. Its cryptographic hash is recorded in the database.
System Architecture: Dual-Token Refresh Rotation & Family Invalidation
1. Access Token Expired (HTTP 401 Unauthorized)
Browser Client Auth Server (Node / Express)
| |
|-- POST /api/v1/auth/refresh ------------------------->|
| (Carries HttpOnly Cookie: refreshToken_v1) |
| |-- 1. Verify Signature & DB lookup
| |-- 2. Is refreshToken_v1 already used?
| | / \
| | YES NO
| | / \
| | [BREACH DETECTED!] [NORMAL ROTATION]
| | Revoke ENTIRE family Generate refreshToken_v2
| | Invalidate all sessions Persist v2 hash in DB
| | Return HTTP 403 Issue new accessToken
| | Set-Cookie: v2
|<-- Set-Cookie: refreshToken_v2, { accessToken } ------|<-----------------------------+
|
2. Client updates accessToken in memory and transparently retries original API call
⚠️ Refresh Token Rotation & Reuse Detection
Every time a refresh token is exchanged, the server invalidates that token and issues a brand-new refresh token (Single-Use Tokens). If an attacker steals a refresh token and uses it after the legitimate user already rotated it, the server detects that an already-invalidated token was presented. The server treats this as an active security breach, instantly revoking the entire family of refresh tokens associated with that user account and forcing re-authentication across all devices!
Section 05
Federated Identity: OAuth 2.0, OpenID Connect & PKCE
OAuth 2.0 (RFC 6749) is an authorization delegation framework allowing a third-party application to obtain limited access to a user's HTTP resources without obtaining their password. OpenID Connect (OIDC) is an identity layer built directly atop OAuth 2.0 that provides standardized user authentication via an id_token.
1. The Four Core OAuth 2.0 Roles
- Resource Owner: The end-user who grants access to their account.
- Client: The web or mobile application requesting access (e.g., DevAscent Portal).
- Authorization Server: The identity provider authenticating the user and issuing tokens (e.g., Google Accounts, GitHub).
- Resource Server: The API hosting protected user data (e.g., Google Drive API, GitHub Repositories API).
2. The Authorization Code Flow with PKCE
For Single Page Applications (React, Vue) where client secrets cannot be securely stored on client machines, OAuth 2.0 mandates the Authorization Code Flow with Proof Key for Code Exchange (PKCE):
System Architecture: OAuth 2.0 Authorization Code Flow with PKCE
Browser SPA (React) Authorization Server (Google) Backend API Server
| | |
|-- 1. Generate code_verifier | |
| code_challenge = SHA256(verifier)| |
| | |
|-- 2. Redirect to /authorize --------->| |
| ?client_id=... | |
| &code_challenge=... | |
| &code_challenge_method=S256 | |
| | |
| [User logs in & consents] | |
| | |
|<-- 3. Redirect back with ?code=XYZ ---| |
| |
|-- 4. POST /api/v1/auth/google/callback -------------------------------->|
| Payload: { code: XYZ, code_verifier: ... } |
| |-- 5. POST /oauth/token
| |<--------------------------------| (Exchange code + verifier)
| | |
| |-- 6. Verifies SHA256(verifier) -|
| |-- 7. Returns id_token & user --->|
| |
|<-- 8. Returns Session Cookie + JWT Access Token ------------------------|
Section 06
Access Control Architectures: Role-Based Access Control (RBAC)
Once authenticated, authorization governs user capabilities across API endpoints. Role-Based Access Control (RBAC) models permissions hierarchically:
- Users are assigned one or more Roles (e.g., STUDENT, INSTRUCTOR, ADMIN).
- Roles encapsulate discrete Permissions (e.g., course:read, course:write, user:delete).
- Route guard middlewares inspect user claims on incoming requests: if req.user.role lacks the required capability, execution immediately terminates with HTTP 403 Forbidden.
Section 07
Cryptographic Reset Flows & Email Verification Architecture
Password reset and account verification workflows are frequent targets for account takeover attacks. Secure systems adhere to strict cryptographic workflows:
- High-Entropy Unpredictable Tokens: Generate a 256-bit cryptographically secure random token (crypto.randomBytes(32).toString('hex')).
- Hashed Token Storage: Never store the raw token in the database! Compute a SHA-256 hash of the token (crypto.createHash('sha256').update(rawToken).digest('hex')) and persist only the hash with an expiration timestamp (typically 15 to 30 minutes). If the database is compromised, attackers cannot forge reset links.
- Single-Use Invalidation & Session Termination: When the user submits their new password alongside the token, verify the hash, update the Bcrypt password, immediately clear the token field, and revoke all active refresh tokens to disconnect unauthorized sessions.
- Anti-Enumeration Responses: When a user requests a reset for an email address, always return a uniform response: "If an account exists with this email, a reset link has been dispatched." Never reveal whether the email exists in the database.
Section 08
Multi-Factor Authentication (MFA): TOTP RFC 6238 Internals
Multi-factor authentication requires verification across at least two distinct authentication categories: (1) Something you know (password), (2) Something you have (authenticator app, hardware key), or (3) Something you are (biometrics).
1. Mathematical Mechanics of TOTP (RFC 6238)
The Time-Based One-Time Password (TOTP) algorithm operates completely offline without SMS networks:
- During enrollment, server and client share an unguessable secret key (Base32 encoded), typically exchanged via a QR code (otpauth://totp/...).
- Both parties compute a counter representing the current epoch timestamp divided into 30-second time steps:
Counter = Floor(UnixTime / 30)
- Compute an HMAC-SHA1 (or SHA256) digest of the counter using the shared secret:
Hash = HMAC-SHA1(SharedSecret, Counter)
- Perform Dynamic Truncation to extract a 4-byte integer from the hash, then compute modulo 1,000,000 to produce a 6-digit numeric verification code.
- The server allows a drift window of +/- 1 time step (allowing for +/- 30 seconds clock drift between phone and server).
Section 09
Production Implementations: Express Auth Engine & RBAC Route Guards
1. Enterprise Express Authentication Controller & Dual-Token Issuance
The code below demonstrates production user registration, secure Bcrypt hashing, dual-token generation, and HTTP-only cookie setting.
import { Request, Response, NextFunction, Router } from 'express';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import crypto from 'node:crypto';
const router = Router();
const BCRYPT_ROUNDS = 12;
const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET || 'super-secret-access-key-2026';
const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET || 'super-secret-refresh-key-2026';
interface TokenPayload {
userId: string;
role: string;
}
router.post('/register', async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password, fullName } = req.body;
const salt = await bcrypt.genSalt(BCRYPT_ROUNDS);
const passwordHash = await bcrypt.hash(password, salt);
const newUser = {
id: crypto.randomUUID(),
email,
passwordHash,
role: 'STUDENT',
isEmailVerified: false
};
res.status(201).json({
success: true,
message: 'User registered. Please verify your email.',
userId: newUser.id
});
} catch (err) {
next(err);
}
});
router.post('/login', async (req: Request, res: Response, next: NextFunction) => {
try {
const { email, password } = req.body;
const user = { id: 'usr_9182', email, role: 'STUDENT', hash: '$2b$12$...' };
const isMatch = await bcrypt.compare(password, user.hash);
if (!isMatch) {
return res.status(401).json({ error: 'Invalid email or password credentials' });
}
const payload: TokenPayload = { userId: user.id, role: user.role };
const accessToken = jwt.sign(payload, ACCESS_SECRET, { expiresIn: '15m' });
const refreshToken = jwt.sign(payload, REFRESH_SECRET, { expiresIn: '7d' });
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000,
path: '/api/v1/auth/refresh'
});
res.status(200).json({ success: true, accessToken });
} catch (err) {
next(err);
}
});
export const requireAuth = (allowedRoles: string[] = []) => {
return (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing or malformed Authorization Bearer token' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, ACCESS_SECRET) as TokenPayload;
(req as any).user = decoded;
if (allowedRoles.length > 0 && !allowedRoles.includes(decoded.role)) {
return res.status(403).json({ error: 'Forbidden: Insufficient role permissions' });
}
next();
} catch (err) {
return res.status(401).json({ error: 'Expired or invalid access token' });
}
};
};
export default router;
2. Production React Protected Route Guard
The component below prevents unauthorized layout rendering, checks user roles against required privileges, and routes unauthenticated users to the login screen with redirection memory.
import React from 'react';
import { Navigate, Outlet, useLocation } from 'react-router-dom';
interface ProtectedRouteProps {
isAuthenticated: boolean;
userRole?: string;
allowedRoles?: string[];
}
export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
isAuthenticated,
userRole,
allowedRoles = []
}) => {
const location = useLocation();
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
if (allowedRoles.length > 0 && (!userRole || !allowedRoles.includes(userRole))) {
return <Navigate to="/unauthorized" replace />;
}
return <Outlet />;
};
Section 10
University & Industrial Examination Review
1. Explain why fast cryptographic hash functions like SHA-256 are dangerous for password storage. Detail how Bcrypt's salt and adaptive work factor neutralize modern GPU brute-force attacks.
Answer: Cryptographic hashes like SHA-256 are engineered for raw computational throughput (verifying gigabytes of data per second). A modern GPU cluster can compute upwards of 100 billion SHA-256 hashes per second, making offline brute-force and dictionary attacks trivially fast if a hash database is leaked. Bcrypt neutralizes this through two mechanisms: (1) Per-User Cryptographic Salt: A 128-bit random salt ensures identical passwords produce distinct hashes, rendering pre-computed Rainbow Tables useless. (2) Adaptive Work Factor: Bcrypt runs an expensive key expansion loop whose iteration count scales exponentially (2 ^ CostFactor). At cost factor 12, each hash computation requires ~300 milliseconds of CPU time. This caps a high-end GPU cluster to only a few thousand guesses per second, rendering brute-force attacks computationally and financially intractable.
2. Contrast storing access tokens in `localStorage` versus `HttpOnly` cookies. What security vulnerabilities does each approach expose or mitigate?
Answer: Storing tokens in localStorage leaves them vulnerable to Cross-Site Scripting (XSS) attacks. If an attacker injects any malicious JavaScript (via a compromised third-party npm package, user input field, or CDN script), the script can execute localStorage.getItem('token') and exfiltrate the token silently. Storing tokens in cookies configured with the HttpOnly flag guarantees that client-side JavaScript cannot read the token, completely immunizing the token from XSS exfiltration. However, cookies are vulnerable to Cross-Site Request Forgery (CSRF), where malicious websites trick the browser into automatically sending the cookie. This is mitigated by configuring SameSite=Strict or SameSite=Lax and validating anti-CSRF tokens.
3. Detail the mechanics of Refresh Token Rotation. How does the server detect token reuse, and what actions must it take upon detecting a breach?
Answer: Under Refresh Token Rotation, every time a client exchanges a refresh token for a new access token, the used refresh token is immediately invalidated, and a brand-new refresh token is issued (Single-Use Token rule). The server tracks token lineage by grouping tokens into a "Family ID" or chaining pointers in the database. If an attacker intercepts a refresh token and attempts to use it after the legitimate user has already exchanged it, the server detects that an already-invalidated token was presented. Because the server cannot determine whether the attacker or the legitimate user executed the original exchange, it treats the reuse as an active compromise: it immediately revokes the entire family of tokens associated with that user session, deletes active refresh hashes from the database, and forces all clients back to the login screen.
4. Why was the OAuth 2.0 Implicit Grant deprecated in favor of the Authorization Code Flow with PKCE for Single Page Applications (SPAs)?
Answer: The legacy OAuth 2.0 Implicit Grant returned access tokens directly in the URL hash fragment (#access_token=...) following user authorization. This exposed the token in browser history, web server access logs, and referrer headers, allowing malicious extensions or scripts to intercept it. Furthermore, SPAs cannot securely store a static client_secret. The Authorization Code Flow with PKCE (Proof Key for Code Exchange) resolves this dynamically: the client generates a cryptographic secret (code_verifier) and sends its SHA-256 hash (code_challenge) in the authorization request. When exchanging the temporary code, the client sends the raw code_verifier. The authorization server hashes it and verifies it matches the original challenge. Even if an attacker intercepts the authorization code in transit, they cannot exchange it without the unhashed verifier, providing bulletproof security for public clients.
5. Detail the mathematical algorithm behind Time-Based One-Time Passwords (TOTP RFC 6238). Why does TOTP remain secure even when the user is completely offline without an active network connection?
Answer: TOTP is a deterministic, offline algorithm requiring no real-time communication between the client (authenticator app) and server. During initial setup, a shared secret key K is exchanged via QR code. To generate a 6-digit passcode: (1) The client reads the current Unix timestamp T and calculates a counter C = Floor(T / 30), representing the 30-second epoch step. (2) It computes an HMAC hash HMAC-SHA1(K, C). (3) It performs dynamic truncation using the last 4 bits of the hash to extract an offset, taking a 31-bit integer. (4) Modulo 10^6 yields the 6-digit code. Because both the user's phone and the server share the secret K and have synchronized real-time clocks, both calculate the exact same numeric code independently without transmitting any cellular or network packets.