System Design: Web Security — OWASP Top 10, XSS, CSRF, SQL Injection, CORS, CSP, JWT, OAuth

Web security is a core competency for every backend and full-stack engineer. Security vulnerabilities are consistently the most expensive bugs to fix, and interviewers at top companies test security knowledge as part of system design and coding rounds. This guide covers the OWASP Top 10 vulnerabilities, practical defenses, and secure authentication patterns — essential knowledge for building production systems.

SQL Injection

SQL injection occurs when user input is concatenated directly into a SQL query without sanitization. Example: a login form that constructs the query SELECT * FROM users WHERE email = (user_input) AND password = (user_input). If the user enters: admin@example.com OR 1=1 –, the query becomes SELECT * FROM users WHERE email = admin@example.com OR 1=1 — AND password = (anything). The OR 1=1 is always true, and — comments out the password check. The attacker logs in as any user. Prevention: (1) Parameterized queries (prepared statements) — the only reliable defense. The query and data are sent separately to the database: SELECT * FROM users WHERE email = $1 AND password = $2. The database treats $1 and $2 as literal values, never as SQL code. Every modern database driver supports parameterized queries. (2) ORMs (SQLAlchemy, Hibernate, Prisma) — generate parameterized queries automatically. Use the ORM query API; never construct raw SQL from user input. (3) Input validation — validate that user input matches expected patterns (email format, numeric ranges). This is defense in depth, not a primary defense — parameterized queries are required regardless.

Cross-Site Scripting (XSS)

XSS occurs when an attacker injects malicious JavaScript into a web page viewed by other users. The script executes in the victim browser with the same privileges as the legitimate page — it can steal cookies, modify the page, or perform actions as the user. Types: (1) Stored XSS — the malicious script is stored in the database (e.g., a forum post containing a script tag) and served to every user who views the page. Most dangerous. (2) Reflected XSS — the script is included in a URL parameter and reflected in the response. The attacker tricks the victim into clicking a crafted link. (3) DOM-based XSS — the script is injected via client-side JavaScript that reads from an untrusted source (URL hash, local storage) and writes to the DOM. Prevention: (1) Output encoding — escape HTML entities before rendering user content. Convert less-than to <, greater-than to >, quotes to ". Modern template engines (React JSX, Jinja2 with autoescape, Go html/template) do this automatically. (2) Content Security Policy (CSP) — an HTTP header that restricts which scripts can execute. Content-Security-Policy: script-src self prevents inline scripts and scripts from external domains. (3) HTTPOnly cookies — set cookies as HttpOnly so JavaScript cannot access them, preventing cookie theft via XSS.

Cross-Site Request Forgery (CSRF)

CSRF exploits the browser automatic inclusion of cookies with every request. If a user is logged into bank.com (has a session cookie), and visits evil.com, evil.com can include an image tag or form that submits a request to bank.com/transfer?to=attacker&amount=10000. The browser attaches the bank.com session cookie, and the bank processes the transfer as if the user initiated it. Prevention: (1) CSRF tokens — the server generates a unique token for each form/session and includes it as a hidden field. The server rejects requests without a valid token. The attacker site cannot read the token (same-origin policy prevents reading another domain HTML). (2) SameSite cookies — Set-Cookie: session=abc; SameSite=Lax. Lax prevents the cookie from being sent on cross-site POST requests (blocks CSRF via forms). Strict prevents the cookie on all cross-site requests (blocks CSRF entirely but breaks legitimate cross-site navigation like clicking a link from email). (3) Check the Origin or Referer header — reject requests where the Origin header does not match your domain. Less reliable than CSRF tokens (headers can be absent in some edge cases). Modern frameworks (Django, Rails, Spring) include CSRF protection by default.

Authentication: JWT vs Sessions

Session-based authentication: the server creates a session on login and stores it (in memory, Redis, or a database). The session ID is sent to the client as a cookie. On each request, the server looks up the session by ID. Pros: sessions can be invalidated instantly (delete from the store), session data is server-side (not exposed to the client). Cons: requires a shared session store for multi-server deployments, stateful. JWT (JSON Web Token) authentication: the server issues a signed token containing user claims (user_id, roles, expiration). The client stores the token (localStorage or httpOnly cookie) and sends it with each request. The server verifies the signature without a database lookup. Pros: stateless (no session store), works across services (any service with the signing key can verify). Cons: tokens cannot be revoked before expiration (the server has no record of issued tokens — a compromised token is valid until it expires), token size is larger than a session ID (especially with many claims). Best practice: use short-lived JWTs (15 minutes) with refresh tokens (stored in httpOnly cookies, rotated on use). The short expiration limits the damage window of a compromised access token. For applications requiring immediate revocation (logout, account suspension), maintain a token blocklist in Redis.

CORS: Cross-Origin Resource Sharing

CORS is a browser security mechanism that restricts which origins (domain + protocol + port) can access your API. Without CORS, the browser same-origin policy blocks frontend JavaScript from calling APIs on different domains. CORS headers tell the browser which cross-origin requests are allowed. Key headers: Access-Control-Allow-Origin (which origins can access the API — specify exact origins, never use * with credentials), Access-Control-Allow-Methods (which HTTP methods are allowed), Access-Control-Allow-Headers (which request headers are allowed), Access-Control-Allow-Credentials (whether cookies are sent). Preflight requests: for non-simple requests (PUT, DELETE, custom headers), the browser sends an OPTIONS request first to check if the server allows the actual request. The server responds with the CORS headers. If allowed, the browser sends the actual request. Common mistake: setting Access-Control-Allow-Origin: * in production. This allows any website to call your API (though without cookies). For APIs with authentication, specify the exact allowed origins: Access-Control-Allow-Origin: https://app.example.com.

Secure API Design Checklist

Production security checklist: (1) Use HTTPS everywhere — HTTP transmits data in plaintext. Redirect HTTP to HTTPS. Set HSTS header (Strict-Transport-Security: max-age=31536000; includeSubDomains). (2) Authenticate every API request — use JWT, API keys, or session cookies. Never expose unauthenticated endpoints for sensitive operations. (3) Authorize at every endpoint — verify the authenticated user has permission to access the requested resource. Check object-level access: can this user view this specific order? Not just “is the user logged in.” (4) Rate limit all endpoints — prevent brute-force attacks and abuse. Stricter limits on authentication endpoints (10 attempts per minute). (5) Validate all input — check types, ranges, formats, and lengths. Reject unexpected fields. Use a validation library, not manual checks. (6) Log security events — log authentication failures, authorization denials, and suspicious patterns. Send alerts on anomalies (100 failed login attempts from one IP). (7) Sanitize error messages — never expose stack traces, database errors, or internal paths to clients. Return generic error messages with error codes for programmatic handling. (8) Keep dependencies updated — CVEs in libraries (Log4j, OpenSSL) are the most common attack vector. Automate dependency scanning with Dependabot or Renovate.

Scroll to Top