Web Security

Web Security

Security is not a feature you add at the end. XSS, CSRF, CORS and safe authentication are the baseline every web developer needs to understand.

intermediate15 min readUpdated Sep 15, 2026
comment.js
js
// comment.js
function renderComment(text) {
  const el = document.createElement("p");
  // textContent escapes markup, so
  // <script> is shown as text, not run
  el.textContent = text;
  return el;
}
Golden rule
Never trust the client
Top risk
Cross-site scripting
Transport
HTTPS everywhere
Sessions
HttpOnly, Secure cookies
Defence in depth
Encode, validate, restrict
Reference
OWASP Top 10

Why it matters

Why security is everyone's job

Protect your users

A single XSS bug can expose accounts, data and money. Security failures are the most expensive bugs you can ship.

Protect the session

Cookies, tokens and CSRF protection decide whether an attacker can act as your user.

Protect the data

Hashing passwords, validating input and least privilege keep breaches contained.

The big picture

The three fronts of web security

Trust nothing from the client, protect the session, and configure the browser's defences correctly.

Untrusted input

Assume the worst

Everything from the client can be forged, so validate and encode on the server.

The browser

Enforce

Headers such as CSP, HSTS and cookie flags let the browser enforce your rules.

The server

Decide

Authentication, authorization and data access belong on the server, never the client.

Security at a glance

The threats and defences

XSS

Injected scripts run in your page. Prevent it with output encoding.

CSRF

Forged requests that use the victim's cookies. Prevent it with tokens and SameSite.

CORS

A browser rule about which origins may read responses.

Authentication

Sessions, tokens, hashing and multi-factor authentication.

HTTPS

Encrypt traffic and enable HSTS.

CSP

Restrict what scripts, styles and connections a page may use.

A short history

How the web learned to defend itself

  1. 2005

    XSS goes mainstream

    Cross-site scripting becomes one of the most reported web vulnerabilities.

    05
  2. 2008

    Same-origin policy hardens

    Browsers tighten rules around cross-origin reads and cookie access.

    08
  3. 2012

    Content Security Policy

    CSP gives sites a way to declare which sources the browser should trust.

    12
  4. 2014

    HTTPS everywhere

    Let's Encrypt and HSTS push the web toward universal encryption.

    14
  5. Today

    Secure by default

    Modern frameworks and platforms ship safer defaults, but developers must still configure them.

    Today

The complete guide

Web Security: Everything you need to know

Why security is everyone’s job

Web security is not a specialist concern that someone else handles. Every form, every rendered comment, every cookie and every API endpoint is an opportunity for a mistake that exposes your users. The cost of a security bug is unusually high: lost data, broken trust and legal exposure.

The good news is that most attacks follow a small number of patterns, and most defences are well understood. This guide covers the essentials: cross-site scripting, cross-site request forgery, CORS, secure authentication and the browser headers that enforce your rules.

The golden rule

Never trust the client. Anything that arrives from the browser can be forged: form values, headers, hidden fields, prices, user IDs and JavaScript state. Client-side validation is for user experience; the server must validate and authorize everything again.

This single principle underlies almost every defence. If you assume input is hostile and permissions must be checked server-side, most vulnerabilities disappear before they are written.

Cross-site scripting (XSS)

XSS is the most common serious web vulnerability. It happens when attacker-controlled data is treated as code. There are three broad types: stored (saved on the server and served to others), reflected (bounced back in a response) and DOM-based (introduced by client-side JavaScript).

The primary defence is output encoding: encode data for the context where it appears. In the DOM, use textContent rather than innerHTML.

// safe.js
const el = document.createElement("p");
el.textContent = userComment; // escaped

// If you truly need HTML, sanitise it
import DOMPurify from "dompurify";
el.innerHTML = DOMPurify.sanitize(userHtml);

On the server, use templating that escapes by default and never build SQL or HTML by string concatenation. For databases, always use parameterised queries or an ORM so input can never become SQL.

Content Security Policy

A Content Security Policy is a response header that tells the browser which sources a page may use. A strict policy turns many XSS bugs into harmless console errors.

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';

Start with Content-Security-Policy-Report-Only to collect violations without breaking the site, then enforce. Avoid unsafe-inline and unsafe-eval where you can, and use nonces or hashes for the inline scripts you cannot remove.

Cross-site request forgery (CSRF)

CSRF tricks the browser into sending an authenticated request using the victim’s cookies. If your site uses cookie-based sessions and a state-changing endpoint accepts a simple request, an attacker’s page can trigger it.

Defences, layered:

  • SameSite cookies. SameSite=Lax or Strict stops cookies from being sent on cross-site requests in modern browsers.
  • Anti-CSRF tokens. A per-session token included in forms and verified on the server.
  • Check the origin. Reject state-changing requests whose Origin header is not your site.
  • Never mutate on GET. Use POST, PUT, PATCH or DELETE for changes.

CORS

CORS is a browser rule about which origins may read responses from your API. By default, a page cannot read a cross-origin response unless the server allows it with Access-Control-Allow-Origin.

Two things are commonly misunderstood:

  • CORS protects the user’s browser, not your server. Other servers can call your API freely; only server-side authorization stops them.
  • A permissive Access-Control-Allow-Origin: * combined with credentials is not allowed, and reflecting arbitrary origins is dangerous.

Configure CORS explicitly for the origins you trust, and keep authentication and authorization on the server.

Secure authentication

Authentication is where mistakes are most costly.

  • Hash passwords with bcrypt, scrypt or Argon2, never plaintext or reversible encryption.
  • Use HttpOnly, Secure, SameSite cookies for sessions so JavaScript cannot read the token.
  • Prefer short-lived tokens with refresh, and rotate them.
  • Add multi-factor authentication for sensitive accounts.
  • Rate-limit login and lock out after repeated failures.
  • Never put secrets in client code — anything shipped to the browser is public.

The comparison above shows the cookie versus localStorage trade-off. localStorage tokens are convenient but readable by any script, so a single XSS steals the session. HttpOnly cookies remove that path.

Other essentials

  • HTTPS everywhere, with HSTS to prevent downgrade attacks.
  • Validate and encode input on the server for every field.
  • Apply least privilege to database users, API keys and cloud roles.
  • Set security headers: CSP, HSTS, X-Content-Type-Options: nosniff, Referrer-Policy and frame-ancestors.
  • Keep dependencies patched and audit them regularly.
  • Do not leak details in error messages or stack traces in production.
  • Log and monitor authentication events and permission failures.

Best practices

  • Treat all client input as untrusted and validate on the server.
  • Encode output for its context; use textContent and sanitise any HTML.
  • Use parameterised queries for all database access.
  • Store sessions in HttpOnly, Secure, SameSite cookies.
  • Add a Content Security Policy and start with report-only.
  • Use SameSite plus anti-CSRF tokens for state-changing requests.
  • Keep secrets on the server and rotate them.

Common mistakes

  • Using innerHTML with user data.
  • Putting session tokens in localStorage.
  • Trusting client-side validation or hidden form fields.
  • Building SQL with string concatenation.
  • Assuming CORS is an access control mechanism.
  • Shipping API keys in front-end code.

Where to go next

Security is a practice, not a checklist you finish. Ground it in the HTTP protocol, enforce it on the Node.js server, and handle secrets safely when you ship with Docker. Read the OWASP Top 10, then audit one form in your own app end to end — you will almost always find something worth fixing.

Rendering user content

textContent treats input as text. innerHTML parses it as markup, which turns any user input into a potential script injection.

Prefer
const el = document.createElement("p");
el.textContent = comment;
// <b>hi</b> renders literally
Avoid
el.innerHTML = comment;
// <img src=x onerror=alert(1)>
// executes

Storing session tokens

An HttpOnly, Secure, SameSite cookie is not readable by JavaScript, so an XSS bug cannot steal the session.

Prefer
res.cookie("session", token, {
  httpOnly: true,
  secure: true,
  sameSite: "lax",
  maxAge: 1000 * 60 * 60,
});
Avoid
localStorage.setItem("token", token);
// readable by any script,
// stolen by any XSS

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning Web Security?

Our interactive tutorial walks you through Web Security step by step — with quizzes and real code you can run in the browser.