CSRF defenses: reason from browser credential attachment, not CORS
Separate cross-site request forgery from cross-origin response access and validate state changes.
On this page
Key takeaways
- CSRF is possible when a browser automatically attaches credentials to an attacker-induced request.
- CORS controls script access to responses; it is not a replacement for CSRF defenses.
First principles
Cross-site request forgery (CSRF) starts with a browser behavior: a browser may attach ambient credentials, such as a session cookie, to a request initiated by a page the user did not intend to trust. If the target application treats that request as an authenticated state change, an attacker can sometimes cause the victim's browser to perform an action even though the attacker cannot read the response. The attacker's page does not need to steal the session cookie; the browser supplies it.
This explains why CORS is not a CSRF defense. Cross-Origin Resource Sharing primarily controls whether browser JavaScript at one origin can read a response from another origin, subject to preflight and response headers. A simple cross-site form submission can reach a server without granting the initiating page access to the response. If the server performs a transfer before the browser enforces response readability, CORS has not protected that state change. CORS policy still matters for API exposure, but it answers a different question.
First classify the credential. Cookie-based sessions are ambient because the browser may attach them automatically. A bearer token stored and explicitly added by application code is not normally attached to a cross-site form request, though token storage and script injection introduce other risks. Do not conclude that an endpoint is safe simply because it expects JSON: content type, preflight behavior, redirects, and alternate request paths can change the picture. Define accepted methods and content types and reject unexpected forms at the server boundary.
Use layered defenses for cookie-authenticated state changes. A synchronizer token ties the action to state available only to the legitimate application flow. A signed double-submit token can bind a token cookie to a request value, but must be designed against cookie injection and scoped correctly. SameSite cookie attributes reduce some cross-site cookie sending, but browser behavior, cross-site navigation requirements, and embedded use affect which setting is compatible. Origin or Referer checks can provide another signal when the application has a precise allowlist. Fetch Metadata headers can add useful context, but older clients and non-browser callers need an explicit policy.
Runnable example
This Node.js function illustrates an exact origin allowlist check for a browser state-changing route. In a real application, derive the expected origin from trusted server configuration, not from an unvalidated Host or forwarded-host header. The function is a layer in a CSRF strategy, not a universal authentication mechanism.
const allowedOrigins = new Set([
'https://app.example.test',
]);
function requireTrustedOrigin(request) {
const origin = request.headers.origin;
if (typeof origin !== 'string' || !allowedOrigins.has(origin)) {
const error = new Error('untrusted request origin');
error.statusCode = 403;
throw error;
}
}
function transferFunds(request) {
requireTrustedOrigin(request);
// Authentication, authorization, CSRF token validation, and the transfer follow.
}
This snippet intentionally does not parse suffixes, wildcards, or substring matches. A comparison such as origin.endsWith('example.test') could accept an attacker-controlled hostname with a misleading suffix or port. Production code must account for reverse proxies, canonical origin configuration, requests without an Origin header, and non-browser clients. Missing headers need an explicit route policy rather than an accidental allow.
Contextual failure case
A team adds Access-Control-Allow-Origin for its own site and assumes transfers are now protected. An attacker embeds a form that submits to the transfer endpoint. The browser sends the victim's session cookie, the server processes the transfer, and the attacker cannot read the result. The team tested whether a script could read the API response; it did not test whether an authenticated state-changing request could be induced.
Another failure comes from placing the CSRF token in a cookie and accepting the same cookie value as the request token without binding or integrity protection. If an attacker can inject a cookie through a sibling subdomain, response splitting, or an insecure transport path, they may set both values. A double-submit design needs a robust token format and cookie scope; a synchronizer token avoids some of these assumptions by keeping server-side session association.
A legitimate cross-site workflow can also be broken by a blanket SameSite=Strict or Origin rejection. Authentication callbacks, embedded applications, and external payment returns may have distinct navigation semantics. Create endpoint-specific contracts, test the full browser flow, and avoid adding a broad exception that disables protection for unrelated mutations.
Verification
Test a browser-like request matrix: trusted same-origin form, untrusted cross-origin form, missing Origin, invalid CSRF token, valid token with a mismatched session, and a legitimate authentication callback. Assert that rejected requests produce no database mutation, external side effect, or queued job. Test the server behind its production proxy chain so scheme and host normalization match reality. Include cookie attributes and domain scope in the review.
Review all state-changing endpoints, not just routes named POST. GET should not mutate state, but legacy handlers and redirects can violate that convention. Confirm CORS origins are exact and credentialed response policies are narrow, while CSRF checks are independently enforced. Session trust boundaries covers the cookie context that makes ambient credentials important.
Exercise
Choose a cookie-authenticated settings endpoint and list every way a browser can reach it: HTML forms, fetch, redirects, mobile clients, and legacy routes. Decide which require CSRF tokens, which can validate Origin, and how missing headers are treated. Build an integration test that sends an attack-shaped form request and proves the database state remains unchanged. Explain why a failed CORS response alone does not establish that the server rejected the action.
Safety and caveats
CSRF defenses do not prevent cross-site scripting, session theft, clickjacking, or compromised same-origin code. Origin, Referer, Fetch Metadata, cookie, and CORS behavior varies across clients and deployment proxies. Use a framework's maintained CSRF facility when it matches the application, and verify its configuration against actual state-changing routes. Do not weaken validation globally to accommodate one special callback; define and test that exception narrowly.