Find the request that changes the account

Cross-site request forgery is a browser trust problem: another site induces a browser to send a request to a business site while the browser may automatically attach the customer’s session cookie. The customer can be authenticated, yet never have intended the change. An attacker does not need to read the response to make some changes happen. Start with actions such as changing a delivery address, adding a recovery method, updating a profile or removing saved information. An email-change flow may have extra verification requirements, but it still needs protection against an unwanted initial request. Draw a line between displaying a page and mutating a record. Authentication answers who the browser is logged in as; CSRF defence asks whether this request came through an intended path.

Inventory the actual routes, methods and clients rather than checking only the visible account page. A legacy form endpoint, an AJAX call, a mobile web view and an administrative shortcut may reach the same business action. Record whether each route uses browser cookies, bearer credentials or a different authentication mechanism, because the browser automatically attaching cookies is central to this threat. Check that GET and other safe-method routes do not change state. A link that removes an address or confirms a preference through GET is especially easy to trigger from outside, and SameSite=Lax can still attach cookies to a top-level safe navigation. If a workflow currently mutates on GET, redesign the operation before treating a cookie attribute as its fix.

Select a primary defence per request path

For server-rendered forms, a framework’s proven CSRF protection is usually a better starting point than a homemade token scheme. OWASP describes a synchronizer token generated for a session or request, delivered within the application page and submitted in a hidden form field or an application request header. The server must compare it with the expected session value and reject missing or mismatched tokens before changing any data. Keep the token out of URLs and logs. A per-request token can narrow reuse but may break a back-button journey if the old form is submitted, so choose lifecycle and recovery behavior deliberately. A session token is easier to use but must still be secret, unpredictable and replaced when the session changes.

For a JavaScript client, a custom request header can be part of the primary defence when the server strictly requires it and cross-origin credentialed requests are limited to trusted origins. OWASP explains that browser requests carrying a custom header require a CORS preflight; allowing a broad set of subdomains or credentialed origins can undo that boundary. Do not assume a JSON endpoint is safe merely because the usual client sends JSON: verify the server rejects simple content types or other routes that could invoke the same action without the expected header. If a stateless double-submit pattern is needed, use the signed, session-bound variant described by OWASP rather than a naive equality check between a cookie and parameter. Select one supported design and verify every write path uses it.

Use cookie and origin checks as supporting controls

Set session cookies with an explicit SameSite policy suited to the real journey, then test external links, sign-in returns and embedded contexts. MDN and OWASP both explain that SameSite reduces some cross-site cookie sending but is not a complete CSRF defence for most deployments. Lax may support a customer arriving through an external link, while Strict can interrupt such navigation; None permits cross-site contexts and needs Secure. Also remember that same-site is broader than same-origin. A sibling subdomain can be a distinct origin while still sharing the site boundary. Keep any less-trusted hosted content and inherited subdomains in the threat review. Cookie choice should reflect actual entry routes, not a slogan that one value is universally best.

A server can also compare the request’s Origin with its configured target origin, falling back to Referer only according to a deliberate policy. OWASP warns that behind a proxy, the host seen by the application may differ from the public host; trust forwarded host information only from configured proxies that replace client-supplied values. A missing header or Origin: null is not proof that a request is safe. Decide whether a valid token is the fallback for legitimate requests without a usable source header, and reject a protected write if no approved check succeeds. Fetch Metadata headers can add another signal for modern browsers, but support and exceptions should be tested rather than used as unexplained blanket permission.

Make rejection safe and understandable

A legitimate customer can encounter an expired session token after leaving an account form open, using Back or signing in again in another tab. Treat this as a rejected action, not a partially applied change. Explain that nothing changed and offer a route to reload the form; preserve non-sensitive entries where safe. Avoid echoing the token, raw security rule or private account values in an error page. Log enough server-side context to diagnose rejected writes without placing token material in logs. Keep authorization checks after CSRF validation: a valid token from one account never grants permission to modify another account’s record. High-impact actions may also need step-up confirmation or out-of-band notice, but those controls answer other risks and do not erase the CSRF requirement.

Consider an illustrative address-book change. A signed-in customer opens the address page, receives a server-approved form and submits a new delivery address. The write succeeds only after session, CSRF and record-ownership checks. In a second test, an outside page tries to trigger the same endpoint through a browser form while the customer remains signed in. The request either lacks the valid token or fails the approved cross-origin conditions, so no address changes. In a third test, the customer returns to a stale form after login renewal. The system rejects it, leaves the old address intact and points to a fresh form. These three outcomes can be verified against the database and customer confirmation; none should be inferred merely from an HTTP response label.

Verify every entry point after a change

Build a test matrix from the endpoint inventory: valid token, missing token, altered token, stale token, foreign Origin, absent source header, a safe navigation and each allowed cross-origin integration. For each protected write, assert both the response and the absence or presence of the intended database change. Recheck after a new subdomain, proxy, embedded checkout or framework upgrade, because these can change origin handling or expose a previously overlooked route. Monitor rejection rates with enough context to find broken legitimate journeys, but do not loosen the rule simply because a browser lacks one header. The result should be a documented decision for every route, including the accepted fallback and the owner who can approve an exception.

Use customer support reports as evidence of friction, not as proof of an attack or of a secure system. A spike in token failures after a release may indicate stale cached forms, an incorrectly scoped cookie, a proxy change or a malicious campaign; investigation must distinguish them. Keep cache behavior for account forms aligned with token lifecycle, and make the recovery path reachable without asking a customer to repeat a sensitive change blindly. Review analytics only when real measurements exist. The business decision is whether every account mutation has a reliable authorization and intent boundary that works for its actual browser journey, including the unhappy path.

Document the exact point at which the application commits a change. If the browser sends a request that starts an asynchronous job, protect that initiating request before the job is queued; a later worker cannot reconstruct whether the browser action was intended. Likewise, if one button triggers several service calls, each externally reachable state-changing entry point needs a policy. In review, ask developers to show the middleware order and a test where an invalid token leaves the database and job queue untouched. This is more meaningful than seeing a token field in the rendered form. Pair the security check with an ordinary customer confirmation that names the action completed, so a rejected request and a successful one cannot look identical.

Before you proceed

  • Does every cookie-authenticated write have a documented CSRF defence?
  • Do cross-site, stale and missing-token requests leave records unchanged?
  • Can a legitimate customer recover from rejection without uncertain partial changes?

Sources & further reading