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.
All application security is measured against the CIA Triad:
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.
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.
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. |
Storing user passwords in plaintext is an unforgivable engineering negligence. When a database is compromised, passwords must remain mathematically irrecoverable.
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:
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.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;
}
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.
Authorization: Bearer <token>. If an XSS vulnerability occurs, the attacker only has a brief window before the token expires.HttpOnly, Secure, SameSite=Strict Cookie. JavaScript cannot read this cookie./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!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;
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.
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:
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).
TLS combines asymmetric and symmetric cryptography:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload. Neutralizes SSL stripping attacks!The cardinal rule of software security is: All user input is hostile until proven otherwise. Defending backend services requires both Validation and Sanitization.
email matches RFC standards and age is an integer between 1 and 120). Unmatched requests are rejected with 400 Bad Request.<script> to <script> or stripping MongoDB operator keys like $gt, $where).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 }))
});
}
};
XSS and CSRF remain two of the most devastating attack vectors targeting web application clients.
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.
element.innerHTML = location.search.dangerouslySetInnerHTML or raw innerHTML with unsanitized data.HttpOnly cookies.Content-Security-Policy: default-src 'self'.
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!
SameSite=Strict or SameSite=Lax on all session cookies. The browser will refuse to attach the cookie when requests originate from third-party sites!X-CSRF-Token). Third-party sites cannot read or guess this token.
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).
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:
Access-Control-Allow-Origin header. If the origin doesn't match, the browser blocks JavaScript from reading the response!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?".Access-Control-Allow-Credentials: true. When credentials are enabled, Access-Control-Allow-Origin CANNOT be a wildcard (*); it must explicitly name the requesting origin!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:
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}`));
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).
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.
OPTIONS request.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.
<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.
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.
{"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.