Web Security Fundamentals Every Developer Should Know

Security is not optional, and it is not something you can bolt on after the feature is built. Every developer working on the web should understand the most common vulnerabilities and how to prevent them. This guide covers the attacks you are most likely to encounter and the defenses that stop them.

Cross-Site Scripting (XSS)

XSS attacks inject malicious scripts into web pages viewed by other users. The attacker finds a place where user input is reflected back into a page without sanitization — a comment section, a search result display, a profile field — and submits JavaScript instead of text. When another user views the page, the script executes in their browser, in the context of your application. The attacker can steal session cookies, redirect to phishing pages, or modify the page to capture credentials.

There are three types: stored XSS, where the malicious script is saved to the database and served to every visitor; reflected XSS, where the script is embedded in a URL and triggers when someone clicks a crafted link; and DOM-based XSS, where client-side JavaScript unsafely writes user-controlled data into the DOM.

Prevention: follow one rule and you eliminate the vast majority of XSS risk — never concatenate user-controlled data into HTML. Use your framework’s templating system, which escapes output by default. React’s JSX, for example, escapes any value interpolated with {}. If you must insert raw HTML — which should be extremely rare — use a sanitization library like DOMPurify, and even then, reconsider whether you actually need to.

Set a Content Security Policy (CSP) header. A well-configured CSP restricts which scripts can execute on your pages, making it much harder for injected scripts to run even if an XSS vulnerability exists. A reasonable starting policy:

1
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'

This allows scripts and styles only from your own domain. It is restrictive and may require adjusting for third-party analytics or CDN-hosted libraries, but start strict and loosen only what you need.

SQL Injection

SQL injection occurs when user input is concatenated into SQL queries. The classic example:

1
cursor.execute(f"SELECT * FROM users WHERE email = '{user_input}'")

If user_input is ' OR '1'='1, the query becomes SELECT * FROM users WHERE email = '' OR '1'='1', which returns every row in the table. More destructive inputs can drop tables, extract data, or execute system commands.

SQL injection has been well-understood for over two decades and is still found in production applications regularly. The fix is absolute and non-negotiable: never concatenate user input into SQL strings.

Use parameterized queries, also called prepared statements. In every language and every database library, there is a way to pass user data separately from the SQL:

1
cursor.execute("SELECT * FROM users WHERE email = %s", (user_input,))

The database treats the parameter as a value, never as part of the SQL syntax. There is no way for an attacker to break out of the parameter. This is a solved problem. Use parameterized queries for all SQL. No exceptions.

Cross-Site Request Forgery (CSRF)

CSRF tricks a logged-in user into making an unwanted request to your application. If a user is authenticated to your banking site and visits a malicious page, that page can submit a form that transfers money without the user’s knowledge. The browser automatically includes the user’s session cookies with the request, so the server sees a legitimate authenticated request.

Prevention: generate a unique, random token for each user session and include it as a hidden field in every form. The server rejects any state-changing request (POST, PUT, PATCH, DELETE) that does not include a valid token. The attacker’s malicious form, hosted on a different domain, cannot read the token from your site due to the same-origin policy.

Most web frameworks have CSRF protection built in. Do not disable it.

Additionally, set the SameSite attribute on session cookies to Lax or Strict. SameSite=Lax sends the cookie with same-site requests and top-level navigations using safe HTTP methods, but not with cross-site POST requests. This provides a second layer of protection independent of CSRF tokens:

1
Set-Cookie: session_id=abc123; SameSite=Lax; Secure; HttpOnly

SameSite=Strict blocks the cookie on all cross-site requests, including when a user clicks a link from another site to yours. This is more secure but can break user experience — a user clicking a link from email to your site will not be logged in. Strict is appropriate for high-security applications like banking. Lax is reasonable for most sites.

Authentication and Password Storage

Do not store passwords in plain text. This should not need to be said, but breaches at major companies demonstrate that it does. Hash passwords using bcrypt, scrypt, or argon2. These algorithms are designed to be computationally expensive, making brute-force attacks slow even if the password database is compromised.

Never write your own hashing scheme. Use a library that has been audited and tested by the security community. In Node.js, use bcryptjs. In Python, use bcrypt or passlib. The library handles salt generation, iteration count, and timing-safe comparison.

Rate-limit login endpoints. An attacker should not be able to try thousands of passwords per second against a user account. After five failed attempts, impose an escalating delay. After a threshold, lock the account and require email verification to unlock.

Session cookies should have three attributes: Secure (only sent over HTTPS), HttpOnly (not accessible to JavaScript via document.cookie), and SameSite (restrict cross-site sending). This combination prevents the most common session hijacking vectors.

HTTPS Everywhere

There is no excuse for plain HTTP in 2026. Let’s Encrypt provides free TLS certificates with automated renewal. Cloudflare offers free TLS termination. Setting up HTTPS takes minutes and costs nothing.

Enable HTTP Strict Transport Security (HSTS) by sending this header:

1
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

This tells browsers to never connect to your domain over HTTP, even if a user types http:// or clicks an HTTP link. The preload directive allows you to submit your domain to browser vendors’ HSTS preload lists, hardcoding the HTTPS requirement into browsers themselves.

Redirect all HTTP traffic to HTTPS. This is a single line in your web server or reverse proxy configuration. Do not accept plain HTTP connections, even for static assets.

Keep Dependencies Updated

The most common security vulnerability in modern web applications is not a custom bug. It is an outdated dependency with a known vulnerability. GitHub’s Dependabot, Snyk, and npm audit all scan your dependencies for known CVEs and notify you when updates are available.

Run npm audit or pip audit as part of your CI pipeline. Treat critical and high severity vulnerabilities as blocking issues. The fix is often a single version bump. The cost of not applying it — a breach through a vulnerability the attacker found by scanning public CVE databases — is incomparably higher.

Security is not a feature. It is a property of the entire system, and it degrades over time if not maintained. The principles described here — escape output, parameterize queries, use tokens, hash passwords, enforce HTTPS, update dependencies — will protect against the majority of real-world attacks. Apply them consistently, and you reduce your attack surface from wide open to manageable.


Web Security Fundamentals Every Developer Should Know
https://toongs.org/2026/06/18/17-web-security-basics/
Author
Jain Chen
Posted on
June 18, 2026
Licensed under