Goodbye Hidden Fields: Modern CSRF Protection Without Tokens

Published: (December 25, 2025 at 11:02 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

If you have ever configured protection against Cross‑Site Request Forgery (CSRF) attacks, you likely remember the routine: generate unique tokens, embed them in hidden form fields, and ensure your scripts send the correct headers. In 2025 this process can be significantly simplified while making your code much cleaner.

Traditional CSRF Tokens

  • State synchronization – Tokens required the server and client to keep shared state, complicating page caching.
  • Markup bloat – Every form needed an extra hidden input.
  • Debugging pain – Expired‑token errors consumed valuable development time.

These drawbacks made token‑based protection feel like an unavoidable technical debt.

Browser‑Based Protection with Fetch Metadata

Modern browsers now transmit a special header known as Fetch Metadata. The most critical element is the Sec-Fetch-Site header, which tells the server the true origin of a request.

Request originHeader value
Same‑origin form submissionSec-Fetch-Site: same-origin
Cross‑site request (e.g., malicious site)Sec-Fetch-Site: cross-site

The browser guarantees that this header cannot be spoofed or modified via JavaScript, allowing the server to rely entirely on it.

Fallback for Older Browsers

If a browser does not send Fetch Metadata, you can fall back to checking the Origin or Referer headers. Requests with same-origin or none (e.g., direct URL entry) are allowed; others are rejected.

Implementation Example

Below are minimal examples for Node.js (Express) and Python (Flask) that reject cross‑site requests for state‑changing methods.

Express (Node.js)

// app.js
const express = require('express');
const app = express();

function csrfProtection(req, res, next) {
  const method = req.method.toUpperCase();
  const unsafeMethods = ['POST', 'PUT', 'PATCH', 'DELETE'];

  if (!unsafeMethods.includes(method)) {
    return next(); // Safe method, no check needed
  }

  const site = req.get('Sec-Fetch-Site');
  if (site && site === 'cross-site') {
    return res.status(403).send('Forbidden: CSRF protection');
  }

  // Fallback for older browsers
  const origin = req.get('Origin') || req.get('Referer');
  if (origin && !origin.includes(req.get('Host'))) {
    return res.status(403).send('Forbidden: CSRF protection');
  }

  next();
}

app.use(express.json());
app.use(csrfProtection);

app.post('/api/data', (req, res) => {
  // Handle state‑changing request
  res.json({ status: 'success' });
});

app.listen(3000, () => console.log('Server running on port 3000'));

Flask (Python)

# app.py
from flask import Flask, request, abort, jsonify

app = Flask(__name__)

def csrf_protect():
    unsafe_methods = {'POST', 'PUT', 'PATCH', 'DELETE'}
    if request.method not in unsafe_methods:
        return  # Safe method

    site = request.headers.get('Sec-Fetch-Site')
    if site == 'cross-site':
        abort(403, description='Forbidden: CSRF protection')

    # Fallback for older browsers
    origin = request.headers.get('Origin') or request.headers.get('Referer')
    if origin and request.host not in origin:
        abort(403, description='Forbidden: CSRF protection')

app.before_request(csrf_protect)

@app.route('/api/data', methods=['POST'])
def handle_data():
    # Process the request
    return jsonify(status='success')

if __name__ == '__main__':
    app.run(port=5000)

Benefits of Header‑Based CSRF Protection

  • No token generation or storage – Eliminates session‑side state and reduces markup.
  • Cache‑friendly – Forms can be cached without worrying about stale tokens.
  • Simpler debugging – Errors are reduced to standard HTTP status codes.
  • Performance gain – Fewer bytes transmitted and less server‑side processing.

Industry Recognition

The OWASP project has recognized Fetch Metadata as a viable alternative to classic anti‑CSRF tokens, moving it from experimental status to a recommended practice.

Conclusion

Browsers are becoming smarter and handling routine security tasks automatically. By shifting CSRF protection from hidden fields and tokens to reliable request metadata, you achieve the same level of security with a cleaner, lighter codebase. This marks a step toward a simpler, more maintainable web.

Back to Blog

Related posts

Read more »

Entendendo o JSON Web Token (JWT)

Em algum momento, ao criar uma aplicação web, precisamos desenvolver uma solução de autenticação para o sistema. Existem várias estratégias para isso, como aute...

CORS - Cross Origin Resource Sharing

Introduction CORS Cross‑Origin Resource Sharing is one of those things you don’t really learn until you run into it. Everything may work on the server side—API...