πŸ›‘οΈ Unit 13 β€’ Web Security & Identity

Authentication & Security: Defense-in-Depth

The authoritative production guide to web application security engineering: Stateful session cookies (HttpOnly, Secure, SameSite), modern cryptographic password hashing (Bcrypt & Argon2), Dual-Token JWT architectures with Refresh Token Rotation (RTR), OAuth 2.0 PKCE authorization, TLS 1.3 cryptographic handshakes, Zod schema validation, defensive input sanitization, Cross-Site Scripting (XSS) neutralizers, Cross-Site Request Forgery (CSRF) defenses, and hardened Cross-Origin Resource Sharing (CORS) rules.

Track Full-Stack Web Dev
Code & Level WD-113 β€’ Security Engineering
Core Topics 10 Modules + Capstone
Pedagogy Formula 50% Theory β€’ 30% Code β€’ 20% Flow
Curriculum Structure: 50% In-Depth Explanatory Theory 30% Verified Security Hardening Code 20% Visual Architecture Flowcharts
01

The Modern Web Threat Model & Defense-in-Depth Architecture

In web security, assuming that any single defensive barrier is impenetrable is a fatal flaw. Security engineering operates on the doctrine of Defense-in-Depth: layering multiple independent defensive perimeters so that when one layer fails, subsequent layers prevent catastrophic system compromise.

🏰 The CIA Triad & The OWASP Top 10

All application security is measured against the CIA Triad:

  • Confidentiality: Guaranteeing that private data (passwords, tokens, personal customer records) is shielded from unauthorized observation.
  • Integrity: Guaranteeing that data cannot be modified or forged by an unauthorized third party in transit or at rest.
  • Availability: Ensuring authorized users have uninterrupted access to resources, even under distributed denial-of-service (DDoS) stress.

The OWASP (Open Worldwide Application Security Project) Top 10 standardizes the most prevalent web attack vectors: Broken Access Control, Cryptographic Failures, Injection (SQL/NoSQL/Command), Insecure Design, Security Misconfiguration, and Vulnerable/Outdated Components.

DEFENSE-IN-DEPTH PIPELINE: MULTI-TIER SECURITY HARDENING FLOW-01
1. Transport Tier
HTTPS / TLS 1.3 + Strict HSTS header. Eliminates packet sniffing & MITM.
βž”
2. Gateway Tier
Helmet CSP + CORS whitelist + Rate limiting. Neutralizes XSS & brute-force.
βž”
3. Application Tier
Zod validation + Bcrypt hashing + Refresh Token Rotation (RTR).
βž”
4. Persistence Tier
Parameterized SQL + Least Privilege DB user + At-Rest AES-256 encryption.
02

Sessions and Cookies: Stateful Identity & Protective Flags

Because HTTP is inherently stateless, servers must maintain an identity mechanism across subsequent requests. In Session-Based Authentication, the server stores the authenticated user session in memory (or a shared Redis cluster) and transmits an opaque session identifier cookie to the client browser.

πŸͺ The Three Essential Security Cookie Flags

Setting raw cookies exposes applications to credential theft. A production security cookie must always enforce:

  • HttpOnly: Prevents client-side scripts from reading the cookie via document.cookie. If an attacker discovers an XSS vulnerability on your site, HttpOnly ensures they cannot steal the session token!
  • Secure: Instructs the browser to transmit the cookie exclusively over encrypted HTTPS connections. Plaintext HTTP transmissions will drop the cookie.
  • SameSite: Dictates whether cookies are transmitted with cross-site requests (the foundational defense against CSRF):
    • SameSite=Strict: The cookie is never sent in cross-site requests (even following a link from an external email or website). Highest security.
    • SameSite=Lax (Default): Sent on top-level safe GET navigations (clicking a link), but blocked on cross-site POSTs and iframes.
    • SameSite=None: Sent on all cross-site requests. Requires the Secure flag.
Security Dimension Stateful Sessions (Cookies + Redis) Stateless JWT Tokens (Header / Memory)
Storage Location Server-side state (Redis) + HttpOnly Cookie. Client-side memory (Access Token) + Cookie (Refresh Token).
Revocation Speed Instantaneous: Deleting session ID from Redis immediately revokes access. Delayed: A signed access token remains valid until its exp timestamp passes.
Server Memory Overhead Grows linearly ($O(N)$) with the number of concurrent logged-in users. Zero server RAM ($O(1)$): Validated mathematically via cryptographic signature.
XSS Vulnerability Immune if using HttpOnly cookies. High risk if stored in localStorage; Protected if kept in memory.
03

User Registration & Modern Password Hashing (Bcrypt & Argon2)

Storing user passwords in plaintext is an unforgivable engineering negligence. When a database is compromised, passwords must remain mathematically irrecoverable.

⚠️ The GPU Brute-Force Threat & Timing Attacks

General-purpose hashes (MD5, SHA-1, SHA-256) are designed for speed, allowing commodity GPU clusters to test 100 billion combinations per second. Modern password hashing algorithms must enforce two cryptographic properties:

  • Salting (Unique Randomness): Prepends a cryptographically secure 128-bit random string to each password. Defeats precomputed Rainbow Table lookup databases and ensures identical passwords produce distinct hashes.
  • Configurable Work Factor (Adaptive Slowdown): Uses algorithms like Bcrypt or Argon2id (winner of the Password Hashing Competition). Configures memory and CPU cost to take ~250ms per verification.
  • Constant-Time Verification: Standard string comparison (hash === input) exits early on the first mismatched byte, allowing attackers to deduce the password character-by-character via microsecond timing differences (Timing Attacks). Bcrypt uses constant-time comparison algorithms that always examine every byte.
πŸ’» Hardened User Registration with Bcrypt 12 Rounds (authService.js)
import bcrypt from 'bcrypt';

// Work factor: 12 salt rounds = 2^12 (4,096 iterations) ~250ms on modern servers
const SALT_ROUNDS = 12;

export async function registerUserAccount(email, rawPassword) {
  // 1. Enforce strict password entropy standards
  if (!rawPassword || rawPassword.length < 10) {
    throw new Error('Password must be at least 10 characters long.');
  }

  // 2. Cryptographic Salt Generation & Hashing
  const passwordHash = await bcrypt.hash(rawPassword, SALT_ROUNDS);

  // 3. Store in Database (rawPassword is discarded and never logged!)
  return { email, passwordHash, createdAt: new Date() };
}

export async function verifyUserCredentials(incomingPassword, storedHash) {
  // Constant-time comparison defeats side-channel timing attacks
  const isValid = await bcrypt.compare(incomingPassword, storedHash);
  return isValid;
}
Execution Trace
bcrypt.hash() generates salt + hash into single 60-character modular crypt string: $2b$12$R9h/cIPz0gi.URNNXRkh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
04

JWT Authentication: Dual-Token Architecture & Refresh Token Rotation (RTR)

Storing long-lived JWT access tokens in browser localStorage is a severe security vulnerability: any cross-site scripting (XSS) vulnerability allows an attacker to execute localStorage.getItem('token') and permanently compromise the user's account.

πŸ”„ The Dual-Token Architecture & Refresh Token Rotation (RTR)
  • Access Token (Short-Lived, e.g., 15 minutes): Stored strictly in JavaScript runtime memory (React state / closure variable). Transmitted via Authorization: Bearer <token>. If an XSS vulnerability occurs, the attacker only has a brief window before the token expires.
  • Refresh Token (Long-Lived, e.g., 7 days): Stored strictly in an HttpOnly, Secure, SameSite=Strict Cookie. JavaScript cannot read this cookie.
  • Refresh Token Rotation (RTR): When the access token expires, the client calls /auth/refresh. The server invalidates the old refresh token and issues a brand-new refresh token alongside the new access token. If a stolen refresh token is reused, the server detects token duplication and immediately revokes all active sessions for that user!
REFRESH TOKEN ROTATION (RTR): SILENT REFRESH WITH THEFT DETECTION FLOW-02
1. Access Token Expires
Client API call receives 401. Triggers silent refresh request to /auth/refresh.
βž” Sends HttpOnly Cookie βž”
2. Token Verification & Rotation
Server verifies RefreshToken-A, marks it USED, generates RefreshToken-B + new Access Token.
βž” New Credentials βž”
3. Session Restored
Client updates in-memory access token. Seamless user experience without re-login!
πŸ’» Refresh Token Exchange Endpoint with Rotation (routes/auth.js)
import express from 'express';
import jwt from 'jsonwebtoken';

const router = express.Router();

router.post('/refresh', async (req, res) => {
  // Read refresh token strictly from HttpOnly cookie
  const incomingRefreshToken = req.cookies?.refreshToken;
  
  if (!incomingRefreshToken) {
    return res.status(401).json({ error: 'Unauthorized: Missing refresh token.' });
  }

  try {
    const payload = jwt.verify(incomingRefreshToken, process.env.JWT_REFRESH_SECRET);
    
    // 1. Generate fresh 15-minute Access Token
    const newAccessToken = jwt.sign(
      { userId: payload.userId, role: payload.role },
      process.env.JWT_ACCESS_SECRET,
      { expiresIn: '15m' }
    );

    // 2. Generate new 7-day Refresh Token (Rotation)
    const newRefreshToken = jwt.sign(
      { userId: payload.userId },
      process.env.JWT_REFRESH_SECRET,
      { expiresIn: '7d' }
    );

    // 3. Overwrite cookie with rotated refresh token
    res.cookie('refreshToken', newRefreshToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'Strict',
      maxAge: 7 * 24 * 60 * 60 * 1000
    });

    return res.status(200).json({ accessToken: newAccessToken });
  } catch (err) {
    return res.status(403).json({ error: 'Forbidden: Invalid or expired refresh token.' });
  }
});

export default router;
Architecture Validation
The client receives the short-lived access token in the JSON body for memory storage, while the refresh token is encapsulated in an inaccessible HttpOnly cookie.
05

OAuth 2.0 & OpenID Connect (OIDC) Architectural Foundations

Prior to modern standards, third-party apps required users to supply their raw Google or Facebook credentials directly to the app. OAuth 2.0 is an open-standard authorization framework that enables applications to obtain limited access to user accounts without exposing user passwords.

πŸ‘₯ The Four Roles of OAuth 2.0
  • Resource Owner (The User): The person who owns the data (e.g., your GitHub account).
  • Client (The Application): The web app seeking access to the user's data (e.g., DevAscent Academy).
  • Authorization Server: The identity provider that authenticates the user and issues tokens (e.g., Google Accounts / GitHub Auth).
  • Resource Server: The API hosting the protected user resources (e.g., Google Drive API / GitHub Repositories API).
πŸ›‘οΈ Authorization Code Flow with PKCE (Proof Key for Code Exchange)

In modern Single-Page Applications (SPAs) and mobile devices, client applications are considered "Public Clients" because client secrets cannot be securely hidden in browser code. Modern OAuth mandates the PKCE Flow:

  1. The client generates a high-entropy random secret called a Code Verifier, hashes it with SHA-256 to create a Code Challenge, and initiates the redirect to Google with the challenge.
  2. The user logs into Google and approves permissions. Google redirects back with an Authorization Code.
  3. The client exchanges the Authorization Code plus the original Code Verifier for an Access Token. Google hashes the verifier and compares it against the original challenge. Even if an attacker intercepts the Authorization Code, they cannot exchange it without the secret verifier!
OAUTH 2.0 AUTHORIZATION CODE FLOW WITH PKCE FLOW-03
1. Generate PKCE Pair
Client generates code_verifier & code_challenge (SHA-256). Redirects to OAuth server.
βž” User Consents βž”
2. Authorization Code
Auth server redirects back to client with short-lived ?code=xyz in URL.
βž” Code + Verifier βž”
3. Token Issuance
Auth server checks SHA256(verifier) == challenge. Issues Access Token + ID Token!
06

Transport Layer Security: HTTPS, SSL/TLS 1.3 & HSTS

Transmitting data over unencrypted HTTP exposes every packet to eavesdropping, packet alteration, and Man-in-the-Middle (MitM) attacks by intermediate routers and ISPs. HTTPS (HTTP Secure) encrypts the communication channel using Transport Layer Security (TLS).

πŸ”’ The Hybrid Encryption Handshake (TLS 1.3)

TLS combines asymmetric and symmetric cryptography:

  • Asymmetric Cryptography (Slow, Handshake Only): Uses public/private key pairs. The server provides a digital certificate signed by a trusted Certificate Authority (CA, e.g., Let's Encrypt). The client verifies the certificate and negotiates a shared session key using Diffie-Hellman (ECDHE).
  • Symmetric Cryptography (Fast, Data Transport): Once the shared session key is derived, all subsequent HTTP traffic is encrypted using ultra-fast symmetric ciphers like AES-256-GCM or ChaCha20.
  • HSTS (HTTP Strict Transport Security): An HTTP response header instructing the browser to permanently upgrade all future connections to HTTPS: Strict-Transport-Security: max-age=63072000; includeSubDomains; preload. Neutralizes SSL stripping attacks!
07

Input Validation & Sanitization: Defending Against Injection

The cardinal rule of software security is: All user input is hostile until proven otherwise. Defending backend services requires both Validation and Sanitization.

πŸ” Validation vs. Sanitization
  • Validation (Contract Enforcement): Verifies that incoming data conforms strictly to expected types, formats, lengths, and value ranges (e.g., verifying that email matches RFC standards and age is an integer between 1 and 120). Unmatched requests are rejected with 400 Bad Request.
  • Sanitization (Payload Neutralization): Cleaning or stripping dangerous characters before processing or storing in databases (e.g., converting <script> to &lt;script&gt; or stripping MongoDB operator keys like $gt, $where).
πŸ’» Fail-Fast Schema Validation Middleware with Zod
import { z } from 'zod';

// 1. Declare Strict Validation Schema
export const UserRegistrationSchema = z.object({
  body: z.object({
    email: z.string().email('Invalid email format').toLowerCase().trim(),
    password: z.string()
      .min(10, 'Password must be at least 10 characters')
      .regex(/[A-Z]/, 'Must contain at least 1 uppercase letter')
      .regex(/[0-9]/, 'Must contain at least 1 digit'),
    username: z.string().min(3).max(30).regex(/^[a-zA-Z0-9_-]+$/, 'Alphanumeric only')
  })
});

// 2. Generic Validation Middleware Factory
export const validateRequest = (schema) => async (req, res, next) => {
  try {
    // Validates and strips unauthorized keys
    const parsed = await schema.parseAsync({
      body: req.body,
      query: req.query,
      params: req.params
    });
    req.body = parsed.body; // Assign sanitized, validated payload
    next();
  } catch (err) {
    return res.status(400).json({
      error: 'Validation Failure',
      issues: err.errors.map(e => ({ field: e.path.join('.'), message: e.message }))
    });
  }
};
Validation Rejection Output
{ "error": "Validation Failure", "issues": [ { "field": "body.password", "message": "Password must be at least 10 characters" } ] }
08

Protecting Against XSS (Cross-Site Scripting) & CSRF

XSS and CSRF remain two of the most devastating attack vectors targeting web application clients.

πŸ’₯ XSS (Cross-Site Scripting) Attacks & Defense

XSS occurs when an attacker injects malicious JavaScript code into a trusted web application. When other users view the affected page, their browser executes the attacker's script.

  • Stored XSS: Injected code is saved permanently in the database (e.g., in a forum comment) and served to every user who opens the thread.
  • Reflected XSS: Malicious script is injected via URL search parameters and reflected back by the server into the HTML response.
  • DOM-Based XSS: Vulnerable client-side code writes untrusted data directly to dangerous sinks: element.innerHTML = location.search.
  • Primary Defenses:
    1. Never use dangerouslySetInnerHTML or raw innerHTML with unsanitized data.
    2. Store tokens in HttpOnly cookies.
    3. Content Security Policy (CSP): An HTTP response header that restricts the domains from which scripts, styles, and images can be loaded and executed: Content-Security-Policy: default-src 'self'.
🎭 CSRF (Cross-Site Request Forgery) Attacks & Defense

CSRF tricks an authenticated user's browser into executing unwanted actions on a trusted website where they are currently logged in. Because browsers automatically attach cookies to cross-origin requests, a malicious website (evil.com) can submit a hidden POST form to bank.com/transfer, and the browser will attach your authentication cookies automatically!

  • Primary Defense: Enforce SameSite=Strict or SameSite=Lax on all session cookies. The browser will refuse to attach the cookie when requests originate from third-party sites!
  • Anti-CSRF Tokens (Synchronizer Token Pattern): The server generates a cryptographically random token tied to the user's session and embeds it in form bodies or request headers (X-CSRF-Token). Third-party sites cannot read or guess this token.
09

Cross-Origin Resource Sharing (CORS) Mechanics

By default, web browsers enforce the Same-Origin Policy (SOP): client-side scripts running on https://app.devascent.me are strictly forbidden from reading HTTP responses from a different origin, such as https://api.devascent.me (differing subdomain).

🌐 How CORS Relaxes the Same-Origin Policy Safely

CORS (Cross-Origin Resource Sharing) is an HTTP-header-based mechanism that allows servers to explicitly declare which foreign origins have permission to read its responses:

  • Simple Requests: Safe GET and POST requests without custom headers are dispatched directly. The browser inspects the returned Access-Control-Allow-Origin header. If the origin doesn't match, the browser blocks JavaScript from reading the response!
  • Preflight Requests (OPTIONS): For requests with custom headers (Authorization) or non-standard verbs (PUT, DELETE), the browser automatically dispatches a pre-flight OPTIONS request first to ask permission: "Are PUT requests with Authorization headers allowed from this origin?".
  • The Credentials Trap: If an application requires cookies or Authorization headers, the server must set Access-Control-Allow-Credentials: true. When credentials are enabled, Access-Control-Allow-Origin CANNOT be a wildcard (*); it must explicitly name the requesting origin!
CORS PREFLIGHT PROTOCOL: OPTIONS HANDSHAKE βž” ACTUAL REQUEST FLOW-04
1. Browser Preflight
Browser sends OPTIONS /api/data with Origin & Access-Control-Request-Method: DELETE.
βž” Verifies βž”
2. Server Permission
Server returns 204 with Access-Control-Allow-Origin: https://app.devascent.me.
βž” Dispatches βž”
3. Actual HTTP Call
Browser now permits real DELETE request to fly. Response delivered to JavaScript!
10

Unit 13 Practical Capstone: The Hardened Security Shield Server

This capstone builds a production-grade Hardened Security Shield Server integrating Helmet CSP headers, strict CORS with dynamic origin matching, Zod request validation, password hashing, and dual-token authentication:

πŸš€ Verified Hardened Express Security Shield (securityShield.js)
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import rateLimit from 'express-rate-limit';

const app = express();

// 1. Helmet: Sets 15+ Secure HTTP Headers (CSP, HSTS, X-Content-Type-Options)
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'", "https://fonts.googleapis.com"],
      imgSrc: ["'self'", "data:", "https://images.devascent.me"],
    }
  },
  hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }
}));

// 2. Hardened Dynamic CORS Configuration with Credentials
const ALLOWED_ORIGINS = ['https://app.devascent.me', 'https://admin.devascent.me'];
app.use(cors({
  origin: (origin, callback) => {
    // Allow requests with no origin (like mobile apps or curl) or matching origins
    if (!origin || ALLOWED_ORIGINS.indexOf(origin) !== -1) {
      callback(null, true);
    } else {
      callback(new Error('CORS Blocked: Origin unauthorized.'));
    }
  },
  credentials: true, // Permits transmission of HttpOnly cookies
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

// 3. Middleware Stack
app.use(express.json({ limit: '10kb' })); // Defends against large payload Denial-of-Service
app.use(cookieParser());

// 4. Global Anti-Brute Force Rate Limiter
app.use('/api/', rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  message: { error: 'Too many requests. Back off.' }
}));

// Protected Endpoint
app.get('/api/v1/secure-vault', (req, res) => {
  res.status(200).json({ message: 'Welcome to the hardened telemetry vault.' });
});

const PORT = 5500;
app.listen(PORT, () => console.log(`πŸ›‘οΈ Security Shield Server online at port ${PORT}`));
Hardened Response Headers Verification
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload Content-Security-Policy: default-src 'self'; script-src 'self'... X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN Access-Control-Allow-Credentials: true
11

University Exam & Technical Interview Viva Preparation Guide

Q1. Detail the security implications of the HttpOnly, Secure, and SameSite cookie attributes.
HttpOnly blocks client-side JavaScript access via document.cookie, completely neutralizing session-cookie theft via Cross-Site Scripting (XSS). Secure ensures the browser only transmits the cookie over encrypted TLS/HTTPS connections, preventing plaintext packet sniffing. SameSite=Strict/Lax restricts when cookies are attached to cross-origin requests, forming the primary browser defense against Cross-Site Request Forgery (CSRF).
Q2. Why is storing JWT access tokens in localStorage considered an antipattern? Where should tokens be stored?
localStorage is fully accessible to any JavaScript running on the origin domain. If an application contains a single Cross-Site Scripting (XSS) vulnerability or imports a compromised npm dependency, an attacker can execute localStorage.getItem('token') and permanently exfiltrate the credentials. In production, Access Tokens should be stored strictly in JavaScript runtime memory (React state/closure), while Refresh Tokens must be stored in HttpOnly, Secure, SameSite=Strict cookies.
Q3. What is the fundamental difference between an XSS attack and a CSRF attack?
XSS (Cross-Site Scripting) exploits the user's trust in a vulnerable website by injecting malicious JavaScript into the application to steal credentials, alter DOM elements, or perform actions. CSRF (Cross-Site Request Forgery) exploits a website's trust in the user's browser: it tricks an already-authenticated victim's browser into transmitting unauthorized HTTP commands to the target application by leveraging automatically attached session cookies.
Q4. How does Refresh Token Rotation (RTR) identify and contain token theft?
Under RTR, whenever a client exchanges a refresh token for a new access token, the used refresh token is invalidated and a brand-new refresh token is issued. If a malicious actor intercepted an old refresh token and attempts to redeem it, the server notices that an invalidated token is being presented twice (Token Reuse). The server recognizes that a theft occurred and immediately invalidates the entire refresh token family, revoking all active sessions for that user.
Q5. Explain what occurs during a CORS Preflight OPTIONS request.
For requests with non-simple methods (PUT, DELETE, PATCH) or custom headers (Authorization), the browser automatically dispatches an OPTIONS request prior to sending the actual request. The preflight includes headers like Origin and Access-Control-Request-Method. The server replies with Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers. If the origin or method is unauthorized, the browser aborts the transaction before the actual payload is ever dispatched.
Q6. Why is a Content Security Policy (CSP) considered the ultimate defense-in-depth against XSS?
A Content Security Policy (CSP) is an HTTP header that allows developers to define a whitelist of trusted domains and sources from which scripts, styles, fonts, and images can be fetched and executed. Even if an attacker succeeds in injecting a malicious <script src="https://evil.com/logger.js"> into a database, the browser’s CSP engine intercepts the injection and refuses to load or execute scripts originating from unauthorized external domains.
Q7. Why is OAuth 2.0 PKCE (Proof Key for Code Exchange) required for Single-Page and Mobile Applications?
Standard OAuth 2.0 Authorization Code flow requires a client_secret to exchange the authorization code for an access token. SPAs and mobile apps are "Public Clients" that cannot securely hide a secret key (anyone can inspect client code). PKCE eliminates the client secret by dynamically generating a random code_verifier and cryptographic code_challenge on each login attempt. The authorization code can only be redeemed by presenting the matching verifier, preventing authorization code interception attacks.
Q8. What is NoSQL Operator Injection and how does input validation prevent it?
NoSQL Operator Injection occurs in document databases like MongoDB when user input is parsed as a raw object rather than a string. An attacker submits {"username": "admin", "password": {"$gt": ""}}. Because any non-empty string in MongoDB is greater than "", the query evaluates to true and logs the attacker in as admin without knowing the password! Schema validation (e.g., using Zod) guarantees that password is strictly a primitive string, rejecting object payloads with 400 Bad Request.