📚 Module: Unit 7 • Security & Identity
Authentication, Authorization & Identity Systems
A first-principles engineering textbook on securing modern distributed web architectures. Master cryptographic key derivation functions (Argon2id and Bcrypt), stateful session stores vs stateless JSON Web Tokens (JWT), dual-token refresh rotation with replay detection, OAuth 2.0 PKCE authorization code flows, Role-Based Access Control (RBAC), secure credential recovery, and Time-based One-Time Password (TOTP) multi-factor authentication.
Section 01
Authentication vs Authorization & Memory-Hard Password Hashing
Security in backend engineering begins by separating two distinct identity concepts:
- Authentication (AuthN — "Who are you?"): The cryptographic verification of a principal's identity through credentials (e.g., email/password verification, public key challenge, biometric assertion).
- Authorization (AuthZ — "What are you permitted to do?"): Evaluating whether a verified identity possesses the necessary rights, roles, or scopes to execute an operation on a target resource (e.g., can User 42 delete Order 99?).
The Physics of Password Hashing: Why General Hashes Fail
General-purpose cryptographic hash functions (such as MD5, SHA-1, SHA-256) were engineered for file integrity verification and digital signatures. They are intentionally designed to be ultra-fast. A modern high-end GPU cluster can compute over 100,000,000,000 SHA-256 hashes per second. If an attacker dumps a database containing SHA-256 hashed passwords, every standard human password can be cracked via pre-computed Rainbow Tables and brute-force dictionaries in hours.
Key Derivation Functions: Salts & Adaptive Cost
Password hashing algorithms must be computationally expensive, deliberately slow, and unique per record:
- Cryptographic Salt: A cryptographically secure pseudo-random string (minimum 16 bytes) generated uniquely for each user. Stored alongside the hash in the database, the salt ensures that two users with identical passwords produce completely different hash outputs, rendering pre-computed Rainbow Tables useless.
- Bcrypt: Based on the Blowfish cipher. Introduces an adaptive work factor (cost parameter $2^{\text{cost}}$). When hardware speeds double, engineers increment the cost factor, doubling the hashing duration without changing code logic.
- Argon2 (Argon2id — Winner of the Password Hashing Competition): The state-of-the-art standard. Unlike Bcrypt which is CPU-bound, Argon2 is memory-hard. It forces the hashing function to fill hundreds of megabytes of RAM during computation. While GPUs have thousands of parallel cores, they have very limited memory per core, rendering massive GPU and ASIC cracking economically unviable.
Figure 7.1 — Rainbow Table Attack Vector vs Salted Argon2id Memory-Hard Pipeline
[UNSALTED FAST HASH: VULNERABLE TO RAINBOW TABLES]
Password: "Password123!" ===> SHA-256 ===> 5e884898da28... <=== Match found in pre-computed table!
(Cracked in 0.000001 seconds)
[SALTED ARGON2ID PIPELINE: ASIC & GPU IMMUNE]
User Password: "Password123!"
|
v
+-------------------------+
| CSPRNG Salt Generator | ===> Generates 16 Bytes Unique Salt: 0xa94b81c2...
+-------------------------+
|
v
+---------------------------------------------------------------------------------------+
| ARGON2ID KEY DERIVATION ENGINE |
| - Time Cost: 3 Iterations |
| - Memory Cost: 64 MB RAM per hash (Defeats GPU Parallel Core Architecture) |
| - Parallelism: 4 Threads |
+---------------------------------------------------------------------------------------+
|
v
Final Formatted Hash String Stored in Database:
$argon2id$v=19$m=65536,t=3,p=4$qUKBw...$8fK19Z... (Takes ~250ms to verify on server)
import * as argon2 from 'argon2';
export class PasswordService {
// Argon2id Configuration conforming to OWASP specifications
private static readonly ARGON_CONFIG: argon2.Options = {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB of RAM
timeCost: 3, // 3 iterations
parallelism: 4, // 4 concurrent threads
hashLength: 32
};
public static async hashPassword(plainPassword: string): Promise<string> {
return await argon2.hash(plainPassword, this.ARGON_CONFIG);
}
public static async verifyPassword(storedHash: string, plainAttempt: string): Promise<boolean> {
try {
return await argon2.verify(storedHash, plainAttempt);
} catch {
return false;
}
}
}
Section 02
Stateful Sessions vs Stateless Tokens & Cookie Security
Once credentials are authenticated, the server must issue a proof-of-identity token for subsequent HTTP requests:
1. Stateful Session-Based Authentication
The server generates a cryptographically random Session ID string, persists session metadata inside a centralized in-memory store (Redis), and issues the Session ID to the client via an HTTP cookie.
- Advantage: Instant administrative revocation. Deleting the session key in Redis instantly logs the user out across all devices.
- Disadvantage: Scalability bottleneck. Every incoming API request across every microservice requires a network database lookup to Redis.
2. Stateless Token-Based Authentication (JWT)
The server cryptographically signs a payload containing the user's identity and claims. Any microservice with the public/shared key can verify the token in-memory using pure mathematics with zero database queries.
Hardening HTTP Cookies: Defeating XSS & CSRF
Storing authentication tokens in browser localStorage is a severe security vulnerability: any third-party script, analytics tag, or Cross-Site Scripting (XSS) vulnerability can execute localStorage.getItem('token') and exfiltrate credentials. Tokens must be stored in hardened HTTP cookies:
HttpOnly: Strictly prevents browser JavaScript from accessing the cookie via document.cookie, completely neutralizing XSS token theft.
Secure: Instructs the browser to only transmit the cookie over encrypted HTTPS TLS connections.
SameSite=Strict / SameSite=Lax: Defeats Cross-Site Request Forgery (CSRF). In Strict mode, the browser never sends the cookie in cross-site requests (e.g., clicking a malicious external link).
Section 03
JSON Web Tokens (JWT) & Dual-Token Rotation Architecture
A JSON Web Token (RFC 7519) consists of three Base64URL-encoded strings separated by dots:
Header.Payload.Signature
- Header: Metadata specifying token type (JWT) and cryptographic signing algorithm (HS256 HMAC or RS256 RSA).
- Payload (Claims): Statements about the entity (sub: User ID, exp: expiration, role: user roles).
- Signature: Cryptographic hash calculated across Base64(Header) + "." + Base64(Payload) using the server's private secret key. If a malicious client modifies a single byte in the payload (e.g., changing role: "user" to role: "admin"), the signature verification mathematically fails.
The Dual-Token Pattern & Refresh Token Rotation
Stateless JWTs cannot be revoked without a database query. If an access token with a 30-day expiration is stolen, the attacker has 30 days of uninhibited access.
The Solution: The Dual-Token Architecture with Rotation & Replay Detection:
- Access Token: Short-lived (15 minutes). Held in client memory. Used in Authorization: Bearer <token> to access APIs.
- Refresh Token: Long-lived (7 days). Stored in an HttpOnly cookie.
- Rotation: When the access token expires, the client calls /auth/refresh. The server issues a new Access Token and a new Refresh Token, invalidating the old refresh token.
- Automatic Replay Detection: If an attacker steals a refresh token and uses it after the legitimate user has already refreshed, the server detects that an invalidated token was submitted! The server flags this as an active breach and instantly revokes the entire token family, forcing all parties to re-authenticate.
Figure 7.2 — Dual-Token Rotation with Automatic Replay Detection
CLIENT APP AUTH SERVER & REDIS
| |
| 1. POST /auth/refresh (Sends RefreshToken #1) |
| ======================================================================> |
| | Check: Is RT #1 valid?
| | Yes -> Invalidate RT #1
| 2. Issues AccessToken #2 + RefreshToken #2 | Store RT #2 in Family
| <====================================================================== |
| |
| |
[ATTACKER EXPLOITS STOLEN REFRESH TOKEN #1] |
| |
| 3. Malicious POST /auth/refresh (Submits Already-Used RT #1) |
| ======================================================================> |
| | Detects REPLAY ATTACK!
| | Token #1 was already used!
| | ACTION: WIPE TOKEN FAMILY!
| 4. Returns 401 Unauthorized (Entire Session Revoked) | Invalidate RT #2 in Redis
| <====================================================================== |
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
export interface AuthenticatedUser {
userId: string;
roles: string[];
}
export function requireAuthentication(req: any, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
type: 'https://api.devascentacademy.me/errors/unauthorized',
title: 'Unauthorized',
status: 401,
detail: 'Missing or malformed Bearer authorization token.'
});
}
const token = authHeader.split(' ')[1];
try {
// Verify digital signature and token expiration in-memory (Zero DB overhead)
const payload = jwt.verify(token, process.env.JWT_ACCESS_SECRET!) as AuthenticatedUser;
req.user = payload;
return next();
} catch (err) {
return res.status(401).json({
type: 'https://api.devascentacademy.me/errors/token-expired',
title: 'Token Expired or Invalid',
status: 401,
detail: 'The provided access token is invalid or expired. Refresh credentials.'
});
}
}
Section 04
OAuth 2.0 & Social Login (Authorization Code with PKCE)
The OAuth 2.0 authorization framework (RFC 6749) enables third-party applications to obtain limited access to a HTTP service on behalf of a resource owner without sharing the user's password. OpenID Connect (OIDC) sits directly on top of OAuth 2.0 to provide an identity authentication layer (issuing a signed id_token).
Why PKCE (Proof Key for Code Exchange) is Mandatory
In public clients (Single Page Applications like React, mobile apps), client secrets cannot be securely stored in client code. An attacker could intercept the incoming authorization code via operating system URI scheme hijacking.
PKCE (RFC 7636) eliminates this attack vector:
- Client generates a high-entropy random cryptographic string:
code_verifier.
- Client hashes it via SHA-256 to create the
code_challenge.
- Client initiates the OAuth redirect to the provider, passing the code_challenge.
- After the user logs in (e.g., via Google), Google redirects back with an authorization code.
- Client exchanges the authorization code for tokens, transmitting the original unhashed code_verifier.
- The Authorization Server verifies: SHA256(code_verifier) === code_challenge. An attacker who intercepted the authorization code cannot exchange it because they do not have the raw verifier!
Figure 7.3 — OAuth 2.0 Authorization Code Flow with PKCE
USER BROWSER / CLIENT AUTHORIZATION SERVER (Google/GitHub)
| |
| 1. Generate code_verifier & SHA256 code_challenge |
| 2. GET /oauth/authorize?challenge=ABC&method=S256 |
| =================================================> |
| | User Authenticates
| 3. Redirect back with: ?code=AUTH_CODE_99 | & Grants Consent
| <================================================= |
| |
| 4. POST /oauth/token |
| code=AUTH_CODE_99 & code_verifier=RAW_SECRET |
| =================================================> | Verify:
| | SHA256(verifier) == ABC
| 5. Return: id_token (OIDC) + access_token |
| <================================================= | [TOKENS ISSUED]
Section 05
Role-Based Access Control (RBAC) & Permission Guards
In an enterprise backend, access control is governed by Role-Based Access Control (RBAC). Permissions are mapped to abstract operational roles, and users are assigned one or more roles.
import { Response, NextFunction } from 'express';
// Declarative RBAC Authorization Middleware Factory
export const requireRoles = (...allowedRoles: string[]) => {
return (req: any, res: Response, next: NextFunction) => {
if (!req.user || !req.user.roles) {
return res.status(403).json({
type: 'https://api.devascentacademy.me/errors/forbidden',
title: 'Access Forbidden',
status: 403,
detail: 'Principal does not possess required security roles.'
});
}
const hasPermission = req.user.roles.some((role: string) =>
allowedRoles.includes(role)
);
if (!hasPermission) {
return res.status(403).json({
type: 'https://api.devascentacademy.me/errors/insufficient-privileges',
title: 'Insufficient Privileges',
status: 403,
detail: `Requires one of the following roles: [${allowedRoles.join(', ')}]`
});
}
return next();
};
};
Section 06
Password Reset Lifecycles & Multi-Factor Authentication (TOTP)
Securing the edge extends beyond login to credential recovery and multi-factor authentication (MFA).
Secure Password Reset Architecture
- Never transmit passwords via email.
- Generate a cryptographically random token: crypto.randomBytes(32).toString('hex').
- Hash the reset token in the database using SHA-256 before storing it alongside a strict 15-minute expiration timestamp. If the database is breached, attackers cannot read reset tokens from the table.
- Upon successful password reset, destroy all existing session families and refresh tokens to evict potential intruders.
Time-Based One-Time Password (TOTP — RFC 6238)
TOTP provides a secondary possession factor without SMS carrier vulnerabilities (SIM swapping). It uses a shared cryptographic base32 secret seed key. Both the server and the authenticator app (Google Authenticator, 1Password) divide the current Unix epoch time into 30-second windows ($T_0 = 30$):
Figure 7.4 — TOTP Mathematical Token Generation Pipeline (RFC 6238)
CURRENT UNIX EPOCH TIME (e.g., 1718900015 seconds)
|
v
[DIVIDE BY 30-SECOND WINDOW]: Counter T = floor(1718900015 / 30) = 57296667
|
v
+-------------------------------------------------------------+
| SHARED SECRET SEED KEY (Base32) |
| (Scanned via QR Code during registration: "JBSWY3DPEHPK3PXP")|
+-------------------------------------------------------------+
|
v
[HMAC-SHA1 ALGORITHM]: Hash = HMAC-SHA1(Secret, Counter T)
|
v
[DYNAMIC TRUNCATION]: Extract 4-byte dynamic binary code
|
v
[MODULO 1,000,000]: Generates exactly 6 decimal digits: ===> "491823"
Section 07
University & Industrial Examination Review
1. Why is Argon2id mathematically superior to SHA-256 and Bcrypt for password storage in high-security applications?
Answer: SHA-256 is an ultra-fast general hash with zero memory overhead, allowing GPUs to test billions of combinations per second using pre-computed Rainbow Tables. Bcrypt introduces adaptive work factor iterations, but remains CPU-bound. Argon2id is a memory-hard key derivation function that mandates allocating tens of megabytes of physical RAM during hash computation. Because specialized ASIC and GPU crackers possess limited memory per execution thread, Argon2id renders massive parallel hardware brute-forcing computationally and economically impossible.
2. Explain how storing access tokens in `HttpOnly` and `SameSite=Strict` cookies defeats both Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF).
Answer: The HttpOnly flag forbids the browser's JavaScript execution engine from accessing the cookie via document.cookie. If an attacker injects malicious JavaScript via an XSS flaw, the script cannot read or exfiltrate the token. The SameSite=Strict flag instructs the browser to omit the cookie on cross-site requests (requests initiated from third-party websites). If a victim visits an attacker's malicious webpage that attempts a forged POST request to the bank API, the browser refuses to attach the authentication cookie, neutralizing CSRF.
3. Detail the mechanics of Refresh Token Rotation with Automatic Replay Detection. What action must the auth server take upon detecting an invalid replayed token?
Answer: Under Refresh Token Rotation, every time an access token is refreshed, the server invalidates the submitted refresh token and issues a brand-new refresh token paired with the new access token. If an attacker intercepts a refresh token and attempts to submit it after the legitimate user has already refreshed, the server detects that an already-invalidated token was presented (a Replay Attack). Because this signals a credential breach, the server immediately revokes the entire refresh token family in Redis/DB, immediately terminating all active sessions for that user across all devices.
4. Why is PKCE (Proof Key for Code Exchange) mandatory for public clients executing the OAuth 2.0 Authorization Code flow?
Answer: Public clients (Single Page Apps and mobile native apps) cannot securely store a static client_secret. An attacker could intercept the incoming authorization code via custom URI scheme sniffing. Under PKCE, the client generates a unique cryptographic code_verifier and sends its hash (code_challenge) in the authorization request. When exchanging the authorization code, the client supplies the unhashed verifier. The server verifies SHA256(verifier) === challenge. An intercepting attacker possessing only the authorization code cannot redeem tokens because they lack the raw verifier generated inside the legitimate client's memory.
5. Deconstruct the Time-Based One-Time Password (TOTP) algorithm defined in RFC 6238. Why does TOTP remain synchronized without network communication?
Answer: TOTP calculates tokens by evaluating HMAC-SHA1(Secret, Counter T), where T = floor(UnixTime / 30). Because both the server and the client authenticator app share the identical cryptographic base32 secret seed (exchanged during initial QR code registration) and agree on the standardized 30-second time step, their computed counter T values match identically based purely on the synchronized global Unix clock. No cellular data or internet network connection is required on the user's authenticator device.