Cross Site Request Forgery (CSRF)¶
What does this mean ?¶
CSRF occurs when a browser is induced to send a state-changing request using credentials it attaches automatically, such as session cookies. The server cannot assume that a request was intentionally initiated from its own interface merely because the user is signed in.
What can happen ?¶
An unwanted request can change account settings or perform other actions available to the current user. The attacker does not necessarily need to read the response. Login, recovery and administrative flows also deserve review; do not limit the threat model to a single payment form.
Recommendation¶
Use the framework's current CSRF protection and validate a session-bound token for state-changing requests. Never use GET for operations such as deletion or password changes. Keep tokens out of URLs and logs. Validate origin/fetch metadata where appropriate and set suitable Secure, HttpOnly and SameSite session-cookie attributes.
SameSite cookies and CORS are additional controls, not universal replacements for token validation. An API that exclusively accepts a bearer token explicitly added in the Authorization header has a different CSRF threat model from a cookie-authenticated API. Verify every accepted credential path before exempting it. XSS can defeat many CSRF controls, so safe output handling remains necessary.
Sample Code¶
These fragments assume an existing authentication and session setup. They show the CSRF layer only; resource authorization and request validation still belong to the handler.
Keep django.middleware.csrf.CsrfViewMiddleware in the normal middleware stack. Do not add csrf_exempt to cookie-authenticated state changes.
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.views.decorators.http import require_POST
@login_required
@require_POST
def update_preferences(request):
# Validate fields and authorize the update here.
return HttpResponse(status=204)
Include the token in the same-origin HTML form:
<form method="post" action="/preferences/">
{% csrf_token %}
<button type="submit">Save preferences</button>
</form>
For JavaScript requests, follow Django's documented X-CSRFToken flow. Do not send the token to other origins.
Use the current SecurityFilterChain style, retaining CSRF defaults:
@org.springframework.context.annotation.Bean
org.springframework.security.web.SecurityFilterChain webSecurity(
org.springframework.security.config.annotation.web.builders.HttpSecurity http)
throws Exception {
http.csrf(org.springframework.security.config.Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
return http.build();
}
Integrate the configured login mechanism and template or SPA token flow. WebSecurityConfigurerAdapter is a legacy configuration style. Do not disable CSRF globally to make one request succeed.
MVC controllers can validate unsafe methods globally:
builder.Services.AddControllersWithViews(options =>
options.Filters.Add(new Microsoft.AspNetCore.Mvc.AutoValidateAntiforgeryTokenAttribute()));
An MVC Razor form can explicitly render the token:
<form method="post" action="/preferences">
@Html.AntiForgeryToken()
<button type="submit">Save preferences</button>
</form>
Keep endpoint authorization and authentication middleware. Minimal APIs and JavaScript clients require their documented antiforgery integration; an MVC filter does not cover every endpoint type.
Express with a server-side session and the csrf-sync package. Register secure session middleware and authentication before these routes; use a production session store.
import { csrfSync } from 'csrf-sync';
const { generateToken, csrfSynchronisedProtection } = csrfSync();
app.get('/csrf-token', (req, res) => {
res.set('Cache-Control', 'no-store');
res.json({ token: generateToken(req) });
});
app.post('/preferences', csrfSynchronisedProtection, (req, res) => {
// Validate fields and authorize the update here.
res.sendStatus(204);
});
Send the returned token in the same-origin request's X-CSRF-Token header. Keep the token endpoint private to the intended origin, renew tokens with session changes, and handle token failures without echoing values. The older csurf package is deprecated; do not copy it into a new setup without a maintenance review.
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
Rails form helpers include the authenticity token. Map state changes to POST/PATCH/PUT/DELETE as appropriate, and keep forgery protection enabled. Merely changing a route from GET to PUT is not sufficient on its own. API-only controllers need an explicit review if they accept cookies.
Regression checks¶
In an isolated application, a valid token from the current session should pass. Missing, malformed and another session's tokens should fail before any state change. Assert GET does not mutate data. Verify login/session rotation, expired sessions and cached-form behavior. Test actual browser requests because cookie and SameSite behavior cannot be established by a server unit test alone.