Skip to content

How to Protect Your Site from CSRF

Key idea:

CSRF (Cross-Site Request Forgery) — attack where an attacker makes a user perform an action on your site (transfer money, change password) via a cross-site request. Mitigation: 1) **SameSite cookie** (default Strict/Lax in 2026 browsers), 2) **CSRF token** in forms (synchronizer token pattern), 3) **Check Origin/Referer** headers. Modern frameworks automate this.

Below: step-by-step, working examples, common pitfalls, FAQ.

Step-by-Step Setup

  1. Set cookies with SameSite=Lax (default in modern browsers, but explicit is better) + HttpOnly + Secure
  2. For state-changing forms (POST/PUT/DELETE) — add CSRF token field
  3. Server: generate token per session, store in session, validate on every POST
  4. For JSON APIs: require custom header (X-CSRF-Token) — simple requests cannot set it without JS
  5. Check Origin/Referer: reject requests with foreign origin for state-changing endpoints
  6. Test: fetch from another site → should be blocked by browser CORS + your CSRF check

Working Examples

ScenarioConfig
PHP session-based token<?php session_start(); if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } // Form: echo '<input type="hidden" name="_token" value="' . $_SESSION['csrf_token'] . '">'; // Validate: if (!hash_equals($_SESSION['csrf_token'], $_POST['_token'] ?? '')) { http_response_code(403); exit('CSRF'); }
Django (auto)# settings.py MIDDLEWARE = ['django.middleware.csrf.CsrfViewMiddleware', ...] # In template: # <form method="post">{% csrf_token %} ... </form>
Express.js (csurf)const csrf = require('csurf'); app.use(csrf({ cookie: true })); app.get('/form', (req, res) => { res.render('form', { csrfToken: req.csrfToken() }); });
SameSite cookieSet-Cookie: session=abc; HttpOnly; Secure; SameSite=Lax # Lax — allows top-level GET, blocks cross-site POST # Strict — blocks all cross-site (breaks OAuth callbacks)
Origin header check$allowed = ['https://example.com', 'https://www.example.com']; $origin = $_SERVER['HTTP_ORIGIN'] ?? ''; if (!in_array($origin, $allowed)) { http_response_code(403); exit; }

Common Pitfalls

  • Static token — predictable. Always random per-session
  • Equality via === — timing attack. Use hash_equals()
  • SameSite=Strict breaks OAuth/payment redirects. Use Lax for most cases
  • Cookie without Secure flag + HTTPS mixed content → can leak in plain HTTP
  • CSRF doesn't protect from XSS. XSS bypasses CSRF (attacker script reads token). Fix XSS too
HeadersCSP, HSTS, X-Frame-Options, etc.
SSL/TLSEncryption and certificate
ConfigurationServer settings and leaks
Grade A-FOverall security score

Why teams trust us

OWASP
guidelines
15+
security headers
<2s
result
A–F
security grade

How it works

1

Enter site URL

2

Security headers analyzed

3

Get grade A–F

What Does the Security Analysis Check?

The tool checks HTTP security headers, SSL/TLS configuration, server info leaks, and protection against common attacks (XSS, clickjacking, MIME sniffing). A grade fromA to F shows overall security level.

Header Analysis

Checking Content-Security-Policy, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, and more.

SSL Check

TLS version, certificate expiry, chain of trust, HSTS support.

Leak Detection

Finding exposed server versions, debug modes, open configs, and directories.

Report with Recommendations

Detailed report explaining each issue with specific steps to fix it.

Who uses this

Security teams

HTTP header audit

DevOps

config verification

Developers

CSP & HSTS setup

Auditors

compliance checks

Common Mistakes

Missing Content-Security-PolicyCSP is the primary XSS defense. Without it, script injection is much easier.
Missing HSTS headerWithout HSTS, HTTPS-to-HTTP downgrade attacks are possible. Enable Strict-Transport-Security.
Server header exposes versionServer: Apache/2.4.52 helps attackers find exploits. Hide the version.
X-Frame-Options not setSite can be embedded in iframe for clickjacking. Set DENY or SAMEORIGIN.
Missing X-Content-Type-OptionsWithout nosniff, browsers may misinterpret file types (MIME sniffing).

Best Practices

Start with basic headersMinimum: HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy. Takes 5 minutes.
Implement CSP graduallyStart with Content-Security-Policy-Report-Only, monitor violations, then enforce.
Hide server headersRemove Server, X-Powered-By, X-AspNet-Version from responses.
Configure Permissions-PolicyRestrict camera, microphone, geolocation access — only what is actually used.
Check after every deploySecurity headers can be overwritten during server configuration updates.

Get more with a free account

Security check history and HTTP security header monitoring.

Sign up free

Learn more

Frequently Asked Questions

Is SameSite=Lax enough in 2026?

For top-level GET — yes. For POST/PUT/DELETE — Lax blocks cross-site, core CSRF mitigation. But defence-in-depth: token on state-changing forms is mandatory.

API with cookies auth — do I need CSRF?

Yes, if cookies. JWT in header (Bearer) — CSRF ok (attacker cannot set custom header). Cookies — vulnerable without CSRF token.

SPA + JSON API — how?

Double-Submit Cookie: CSRF token in cookie + in request header. Attacker cannot read cookie (SameOrigin), does not know value for header.

How to verify CSRF protection?

Create test page on another domain with <code>fetch('/your-site/action', {method: 'POST', credentials: 'include'})</code>. Should be blocked.