Access and accounts Required
Block cross-site form posts
Make sure a change of state only comes from your own pages, with SameSite cookies plus a token or an origin check.
How to do it
Never change anything on a GET request. For cookie logins, check Sec-Fetch-Site on every POST, PUT, PATCH and DELETE, and fall back to the Origin header. Refuse when neither proves the request came from your own origin. Or add a token to each form. SameSite alone does not stop attacks from your own subdomains. APIs that take a bearer token in a header are not exposed.
Example
const UNSAFE = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
app.use((req, res, next) => {
if (!UNSAFE.has(req.method)) return next();
const site = req.get('sec-fetch-site');
const sameOrigin = site ? site === 'same-origin' : req.get('origin') === 'https://example.com';
return sameOrigin ? next() : res.sendStatus(403);
});
# Python with Flask
UNSAFE = {"POST", "PUT", "PATCH", "DELETE"}
@app.before_request
def refuse_cross_site():
if request.method not in UNSAFE:
return
site = request.headers.get("Sec-Fetch-Site")
same_origin = site == "same-origin" if site else request.headers.get("Origin") == "https://example.com"
if not same_origin:
abort(403)
How to check
Send a POST to a change-of-state route with the test user's cookie and Origin: https://evil.example.net, then with Sec-Fetch-Site: cross-site. The server refuses both with 403. The same POST with your own origin still works.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers security testing playbook: https://agentwranglers.se/playbook/adversarial-testing/access-and-accounts/cross-site-requests.