Developer

Web Security Complete Guide: JWT Authentication, Password Hashing, XSS & CSRF

A practical web security guide for developers. Covers JWT authentication, password hashing, XSS prevention, CSRF protection, HTTPS, security headers, and common vulnerability patterns.

March 24, 202610 min read

Why Web Security Is Every Developer's Responsibility

Security is not a feature to bolt on at the end — it must be integrated into every stage of development. According to the 2024 Verizon Data Breach Investigations Report, web application attacks remain one of the top breach vectors, with the majority exploiting well-known, preventable vulnerabilities. The OWASP Top 10, updated regularly, catalogues the most critical risks, and most of them come down to developers making predictable mistakes.

Understanding the fundamentals of web security enables you to make secure-by-default decisions and recognize when a library, pattern, or shortcut introduces risk.

JWT Authentication: How It Works and What Can Go Wrong

JSON Web Tokens (JWTs) are the most widely used mechanism for stateless authentication in modern web applications. A JWT consists of three Base64URL-encoded parts separated by dots: the header (algorithm and token type), the payload (claims: user ID, role, expiry), and the signature (cryptographic proof of integrity).

The critical security property of a JWT is its signature. When your server issues a JWT signed with a secret key (HMAC-SHA256) or a private key (RS256), any tampering with the header or payload invalidates the signature, which the server detects on every request. Never trust the payload without verifying the signature first.

Common JWT security mistakes include: storing JWTs in localStorage (accessible to JavaScript — use HttpOnly cookies instead for sensitive apps), accepting the "none" algorithm (which disables signature verification — always whitelist only your intended algorithm), ignoring token expiry (always check the exp claim), and not rotating signing keys (implement key rotation with kid headers for long-lived systems).

Password Security: Hashing, Salting, and Storage

Passwords must never be stored in plain text or with reversible encryption. Use a purpose-built password hashing algorithm — bcrypt, scrypt, or Argon2id — rather than cryptographic hash functions like SHA-256. The difference is critical: cryptographic hashes are designed to be fast (billions per second with GPUs), making brute-force attacks trivial. Password hashing algorithms are deliberately slow, making exhaustive attacks computationally infeasible.

Argon2id is the current best practice recommended by NIST and OWASP. It requires memory and CPU proportional to configurable parameters, making GPU-based attacks expensive. For bcrypt, use a work factor of 12 or higher. For scrypt, use N=32768 or higher.

Salting — adding a unique random string to each password before hashing — prevents rainbow table attacks (precomputed hash lookup tables). All modern password hashing libraries include automatic salting; never implement salting manually.

XSS: Cross-Site Scripting Prevention

XSS attacks occur when attacker-controlled HTML or JavaScript is injected into your page and executed in victims' browsers. This allows stealing session cookies, logging keystrokes, redirecting users, and defacing content.

The primary defense is output encoding: escape all user-supplied data before inserting it into HTML. Modern frameworks (React, Vue, Angular) escape output by default in their templating systems — the danger is bypassing this with dangerouslySetInnerHTML, v-html, or innerHTML. Use these only with sanitized content.

Implement a Content Security Policy (CSP) header to define which sources of scripts, styles, and other resources are allowed. A strict CSP like Content-Security-Policy: default-src 'self'; script-src 'nonce-<random>' prevents inline scripts and limits script sources, dramatically reducing XSS attack surface even if injection occurs.

CSRF: Cross-Site Request Forgery Protection

CSRF attacks trick authenticated users into unknowingly submitting requests to your application. If a user is logged into your banking site and visits an attacker's page containing a hidden form that submits to your /transfer endpoint, the request carries the user's real session cookie and may be processed.

The standard defense is CSRF tokens: a secret, unique, unpredictable value included in every state-changing form and AJAX request. The server validates the token on every request. The Same-Origin Policy prevents attacker pages from reading your CSRF token, breaking the attack.

The SameSite cookie attribute (SameSite=Strict or SameSite=Lax) is a powerful modern defense that prevents cookies from being sent with cross-origin requests. Set this attribute on all session cookies. Combined with CSRF tokens, it provides defense-in-depth.

Security Headers

HTTP security headers are free, low-effort protections that significantly raise the bar for attackers. Essential headers include: Strict-Transport-Security (forces HTTPS for a defined period), X-Content-Type-Options: nosniff (prevents MIME sniffing attacks), X-Frame-Options: DENY (prevents clickjacking), Referrer-Policy: strict-origin-when-cross-origin (limits referrer information leakage), and Permissions-Policy (restricts access to browser features like camera, microphone, geolocation).

Use securityheaders.com to audit your current header configuration and get a graded report with specific recommendations.

SQL Injection and Parameterized Queries

SQL injection remains in the OWASP Top 10 despite being entirely preventable. It occurs when user input is concatenated directly into SQL queries, allowing attackers to modify query logic.

The complete solution is parameterized queries (also called prepared statements). Never use string concatenation to build queries. Every modern database library supports parameterized queries; they are as easy to use as concatenation and completely eliminate injection risk from parameters.

Try It Now — Free Online JWT Decoder

When debugging authentication issues, inspecting JWT contents is essential. UtiliZest's JWT Decoder instantly decodes any JWT token to reveal its header, payload claims, and signature status — directly in your browser without sending tokens to any server.

Try jwt decoder Now

Frequently Asked Questions

Should I store JWT tokens in localStorage or cookies?
Use HttpOnly, Secure, SameSite=Strict cookies for sensitive applications (banking, e-commerce). localStorage is accessible to JavaScript, making stored tokens vulnerable to XSS attacks. An attacker who injects a script can steal all localStorage values. HttpOnly cookies cannot be accessed by JavaScript at all — only sent automatically by the browser on requests. The trade-off is that cookies require CSRF protection; localStorage does not.
What hashing algorithm should I use for passwords?
Argon2id is the current best practice. If Argon2 is not available in your language or framework, use bcrypt with a work factor of 12 or higher. Never use MD5, SHA-1, SHA-256, or other fast cryptographic hashes for passwords — they can be brute-forced billions of times per second with GPU clusters. The bcrypt and Argon2 algorithms are specifically designed to be computationally expensive, which is exactly what password storage requires.
What is the OWASP Top 10 and why does it matter?
The OWASP Top 10 is a regularly updated consensus list of the most critical web application security risks, published by the Open Web Application Security Project. It is the de facto industry standard for web security awareness. The categories — including injection, broken authentication, security misconfiguration, and XSS — represent the vulnerabilities that attackers actually exploit in the wild. Reviewing the list annually and auditing your application against each category is a practical security baseline.
How do Content Security Policies (CSP) protect against XSS?
CSP is an HTTP response header that whitelists the sources from which browsers are permitted to load resources. A properly configured CSP that disallows inline scripts (no unsafe-inline) and restricts script sources to your own domain eliminates the most common XSS execution vectors, because even if an attacker injects a script tag, the browser refuses to execute it. Use report-uri or report-to to collect CSP violation reports without blocking in testing mode.
What are the most important security headers to add to every website?
The five most impactful headers are: Strict-Transport-Security: max-age=31536000; includeSubDomains (forces HTTPS), Content-Security-Policy (restricts resource sources), X-Content-Type-Options: nosniff (prevents MIME sniffing), X-Frame-Options: DENY (prevents clickjacking), and Referrer-Policy: strict-origin-when-cross-origin (limits referrer leakage). Add these to all responses via your server or reverse proxy configuration, not just specific routes.

Related Posts