<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>code-&amp;-chaos</title>
    <subtitle>A blog about backend engineering, auth systems, and terminal things</subtitle>
    <link rel="self" type="application/atom+xml" href="https://codeandchaos.pages.dev/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://codeandchaos.pages.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2025-07-23T18:36:20+05:30</updated>
    <id>https://codeandchaos.pages.dev/atom.xml</id>
    <entry xml:lang="en">
        <title>Surfing the Web With Seasurf</title>
        <published>2025-07-23T18:36:20+05:30</published>
        <updated>2025-07-23T18:36:20+05:30</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://codeandchaos.pages.dev/posts/surfing-the-web-with-seasurf/"/>
        <id>https://codeandchaos.pages.dev/posts/surfing-the-web-with-seasurf/</id>
        
        <content type="html" xml:base="https://codeandchaos.pages.dev/posts/surfing-the-web-with-seasurf/">&lt;blockquote&gt;
&lt;h2 id=&quot;tl-dr&quot;&gt;TL;DR&lt;&#x2F;h2&gt;
&lt;p&gt;Cross-Site Request Forgery exploits the trust between a browser and an authenticated session. A
hidden form on an attacker&#x27;s page can silently transfer money, change account settings, or even
log you into the attacker&#x27;s profile all while you browse completely unaware.&lt;&#x2F;p&gt;
&lt;p&gt;This post breaks down how CSRF works (with code), walks through real incidents that hit YouTube,
Wikipedia, and ING Direct, and covers the lesser-known Login CSRF variant. Then it dives into
every major defense: CSRF tokens, Referer and Origin validation, custom headers, SameSite cookies,
and when each one matters, ending with a cheat sheet for quick reference.&lt;&#x2F;p&gt;
&lt;p&gt;Inspired by the paper
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;seclab.stanford.edu&#x2F;websec&#x2F;csrf&#x2F;csrf.pdf&quot;&gt;&quot;Robust Defenses for Cross-Site Request Forgery&quot;&lt;&#x2F;a&gt;
by Barth, Jackson, and Mitchell (Stanford University).&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;understanding-and-defending-against-csrf&quot;&gt;Understanding and Defending Against CSRF&lt;&#x2F;h2&gt;
&lt;p&gt;You&#x27;ve probably heard of CSRF, that acronym that pops up whenever someone talks about web security
checklists. But have you &lt;em&gt;really&lt;&#x2F;em&gt; looked at what makes it dangerous, sneaky, and, at times, a pain
to mitigate? Let&#x27;s break it down in simple terms, with practical examples, and explore some modern
defenses that make CSRF less terrifying.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;what-s-csrf&quot;&gt;What&#x27;s CSRF?&lt;&#x2F;h2&gt;
&lt;p class=&quot;emph bold&quot;&gt;By the way, it&#x27;s sometimes pronounced as seasurf&lt;&#x2F;p&gt;
&lt;p&gt;Imagine you&#x27;re logged into your bank website in one tab. In another tab, you&#x27;re reading a spicy blog
post that secretly contains a hidden form. That form silently submits a request to your bank&#x27;s
&quot;transfer money&quot; endpoint with your cookies attached transferring ₹10,000 to the attacker&#x27;s account.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s CSRF: &lt;strong&gt;Cross-Site Request Forgery&lt;&#x2F;strong&gt;. It tricks your browser into sending an authenticated
request to a site without your knowledge.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;a-very-basic-example&quot;&gt;A Very Basic Example&lt;&#x2F;h2&gt;
&lt;p&gt;You&#x27;re at a coffee shop, logged into your bank in one tab to pay utility bills. In another tab, you
open what looks like an innocent tech blog. Hidden in its markup is a form like this, set to
auto-submit the moment it loads:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; action&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;https:&#x2F;&#x2F;bank.com&#x2F;transfer&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; method&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;POST&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; type&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;hidden&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;to&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; value&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;attacker_account&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; type&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;hidden&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;amount&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; value&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;10000&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;  document&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;forms&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;].&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;submit&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Your browser attaches your banking cookies to the request automatically. If the bank hasn&#x27;t put CSRF
protection in place, the transfer goes through and you don&#x27;t notice a thing until it&#x27;s too late.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;real-incidents&quot;&gt;Real Incidents&lt;&#x2F;h2&gt;
&lt;p&gt;CSRF isn&#x27;t just a theoretical threat it&#x27;s bitten some of the biggest names on the web.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;YouTube (2008).&lt;&#x2F;strong&gt; A hidden form on any external page could add a video to a logged-in user&#x27;s
&quot;Favorites&quot;, subscribe them to channels, or add friends all without a single click. Victims
unknowingly promoted attacker content using their own session. The attack worked because YouTube&#x27;s
action endpoints accepted &lt;code&gt;POST&lt;&#x2F;code&gt; requests without any token validation.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Wikipedia (2007).&lt;&#x2F;strong&gt; An attacker crafted a page that, when visited by a logged-in Wikipedia admin,
silently submitted a form changing their preferences. This turned into a CSRF worm: the modified
preferences injected malicious scripts into the admin&#x27;s edits, which in turn infected other users.
CSRF became a propagation vector.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;ING Direct (2008).&lt;&#x2F;strong&gt; The online banking portal had no CSRF tokens on its money transfer form.
Visiting a malicious page while simultaneously logged into ING Direct could drain your account. The
only user interaction required? Loading the page.&lt;&#x2F;p&gt;
&lt;p&gt;Each of these was a straightforward CSRF exploit no XSS, no sophisticated phishing. Just a hidden
form and a browser doing exactly what it was told.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;login-csrf-the-lesser-known-evil&quot;&gt;Login CSRF - The Lesser Known Evil&lt;&#x2F;h2&gt;
&lt;p&gt;Not all CSRF attacks aim to steal or transfer something directly. Some are sneakier like Login CSRF,
a lesser known but equally devious variation.&lt;&#x2F;p&gt;
&lt;p&gt;Instead of performing harmful actions, Login CSRF silently logs you into a website using the
attacker&#x27;s credentials. You continue browsing, unaware that you&#x27;re now operating inside their
session. Everything you do browsing, uploading, commenting is linked to them, not you.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-is-this-bad&quot;&gt;Why is this bad?&lt;&#x2F;h3&gt;
&lt;p&gt;Imagine shopping on Amazon while unknowingly logged into the attacker&#x27;s account:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Every product you browse, every review you read gets saved to &lt;em&gt;their&lt;&#x2F;em&gt; browsing history.&lt;&#x2F;li&gt;
&lt;li&gt;Any item you save to a wishlist ends up on &lt;em&gt;their&lt;&#x2F;em&gt; list.&lt;&#x2F;li&gt;
&lt;li&gt;They can log in later and reconstruct exactly what you were looking at, as if they&#x27;d been watching
over your shoulder.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;It&#x27;s subtle, invisible, and invasive. You might not lose money but you definitely lose privacy.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;how-does-this-happen&quot;&gt;How does this happen?&lt;&#x2F;h3&gt;
&lt;p&gt;If a login form accepts credentials via POST without any CSRF protection (tokens, Referer checks,
Origin validation), an attacker can inject a hidden form like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; action&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;https:&#x2F;&#x2F;example.com&#x2F;login&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; method&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;POST&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; type&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;hidden&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;username&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; value&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;attacker@example.com&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; type&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;hidden&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;password&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; value&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;notsosecure&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;  document&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;forms&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;].&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;submit&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Once submitted, your browser obediently sends the request with the attacker&#x27;s credentials and now
you&#x27;re inside their account.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;How to prevent it:&lt;&#x2F;strong&gt; Apply the same CSRF protections to your login form as you would to any
state-changing endpoint. &lt;code&gt;Origin&lt;&#x2F;code&gt; validation is especially well-suited here it works without a
session, costs nothing to implement, and blocks Login CSRF entirely. Many frameworks already include
login CSRF prevention out of the box (e.g, Django&#x27;s &lt;code&gt;@csrf_protect&lt;&#x2F;code&gt; on login views).&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;so-how-do-we-defend-ourselves&quot;&gt;So How Do We Defend Ourselves?&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s look at some standard (and not-so-standard) techniques.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;1. Secret CSRF Tokens (Anti-CSRF Tokens)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The bread and butter. Here&#x27;s how it works in practice:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Server generates a cryptographically random token tied to the user&#x27;s session.&lt;&#x2F;li&gt;
&lt;li&gt;Every form rendered on the server includes the token as a hidden field.&lt;&#x2F;li&gt;
&lt;li&gt;On submission, the server validates the token before processing the request.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;A typical Express middleware:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;app&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;use&lt;&#x2F;span&gt;&lt;span&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;font-style: italic;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;font-style: italic;&quot;&gt; res&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;font-style: italic;&quot;&gt; next&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;session&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;csrfToken&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;    req&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;session&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;csrfToken&lt;&#x2F;span&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; crypto&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;randomBytes&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt;32&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;toString&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;hex&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;  res&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;locals&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;csrfToken&lt;&#x2F;span&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; req&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;session&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;csrfToken&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;  next&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;});&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And the check on form submission:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;app&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;post&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;&#x2F;transfer&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;font-style: italic;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;font-style: italic;&quot;&gt; res&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;_csrf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt; !==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; req&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;session&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;csrfToken&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; res&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt;403&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;send&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;CSRF validation failed&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7F848E;font-style: italic;&quot;&gt;  &#x2F;&#x2F; process transfer...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;});&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;For stateless APIs&lt;&#x2F;strong&gt; (no server-side sessions), use the &lt;strong&gt;Double Submit Cookie&lt;&#x2F;strong&gt; pattern: the
server sets a CSRF token as a cookie, and JavaScript reads it and sends it back as a header. The
server checks that both values match.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7F848E;font-style: italic;&quot;&gt;&#x2F;&#x2F; Client-side&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; token&lt;&#x2F;span&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt; getCookie&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;csrf-token&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;fetch&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;&#x2F;api&#x2F;transfer&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;  method&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt; &amp;quot;POST&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;  headers&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt; &amp;quot;X-CSRF-Token&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; token&lt;&#x2F;span&gt;&lt;span&gt; },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;  credentials&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt; &amp;quot;include&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;});&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The attacker can&#x27;t read the cookie value from a different origin, so they can&#x27;t forge the header.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Common gotchas:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Login and logout forms are easy to forget no session exists yet to bind a token to.&lt;&#x2F;li&gt;
&lt;li&gt;Never expose CSRF tokens via GET endpoints or query parameters.&lt;&#x2F;li&gt;
&lt;li&gt;Bind tokens to session IDs using HMAC to prevent token reuse across users.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;2. Referer Header Checks&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;Referer&lt;&#x2F;code&gt; header tells you where the request came from:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Referer: https:&#x2F;&#x2F;yourbank.com&#x2F;accounts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Two validation strategies:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Same-origin&lt;&#x2F;strong&gt;: Reject unless the origin in &lt;code&gt;Referer&lt;&#x2F;code&gt; matches your domain exactly.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Same-site&lt;&#x2F;strong&gt;: Accept any request whose hostname shares your registered domain.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;app&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;post&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;&#x2F;transfer&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;font-style: italic;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;font-style: italic;&quot;&gt; res&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; referer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; req&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;headers&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;referer&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;referer&lt;&#x2F;span&gt;&lt;span&gt;?.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;startsWith&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;https:&#x2F;&#x2F;yourbank.com&#x2F;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; res&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt;403&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;send&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;Forbidden&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;});&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Caveats:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Proxies and browser extensions often strip the &lt;code&gt;Referer&lt;&#x2F;code&gt; header.&lt;&#x2F;li&gt;
&lt;li&gt;It&#x27;s most reliable over &lt;strong&gt;HTTPS&lt;&#x2F;strong&gt;, HTTP pages lose it entirely.&lt;&#x2F;li&gt;
&lt;li&gt;A missing &lt;code&gt;Referer&lt;&#x2F;code&gt; doesn&#x27;t mean an attack (user typing a URL, bookmark, etc.). Validate
positively: reject wrong origins, but don&#x27;t auto-reject missing headers.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;3. Custom Request Headers (for API requests)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;When using &lt;code&gt;fetch&lt;&#x2F;code&gt; or &lt;code&gt;XMLHttpRequest&lt;&#x2F;code&gt;, add a custom header:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;fetch&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;&#x2F;api&#x2F;transfer&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;  method&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt; &amp;quot;POST&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;  headers&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt; &amp;quot;X-Requested-By&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt; &amp;quot;XMLHttpRequest&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;  credentials&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt; &amp;quot;include&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;});&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Why this works:&lt;&#x2F;strong&gt; Cross-origin requests with custom headers trigger a CORS preflight. The browser
sends an &lt;code&gt;OPTIONS&lt;&#x2F;code&gt; request first, and unless the server explicitly permits the custom header via
&lt;code&gt;Access-Control-Allow-Headers&lt;&#x2F;code&gt;, the actual request is blocked. A malicious &lt;code&gt;&amp;lt;form&amp;gt;&lt;&#x2F;code&gt; or &lt;code&gt;&amp;lt;script&amp;gt;&lt;&#x2F;code&gt;
can&#x27;t set custom headers at all.&lt;&#x2F;p&gt;
&lt;p&gt;Even a static value like &lt;code&gt;X-Requested-By: XMLHttpRequest&lt;&#x2F;code&gt; is effective the attacker can&#x27;t add it
from a simple form submission.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Trade-off:&lt;&#x2F;strong&gt; All state-changing requests must go through JavaScript. Traditional HTML form
submissions won&#x27;t work with this approach.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;origin-validation-the-cleaner-alternative&quot;&gt;Origin Validation: The Cleaner Alternative&lt;&#x2F;h2&gt;
&lt;p&gt;The &lt;code&gt;Referer&lt;&#x2F;code&gt; header has baggage it leaks the full URL, and browsers sometimes strip it. Enter
&lt;code&gt;Origin&lt;&#x2F;code&gt;, which only includes the origin:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Origin: https:&#x2F;&#x2F;yourbank.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;No path. No query string. No privacy leak. Just enough to validate where the request came from.&lt;&#x2F;p&gt;
&lt;p&gt;Browsers send &lt;code&gt;Origin&lt;&#x2F;code&gt; reliably with &lt;code&gt;POST&lt;&#x2F;code&gt; requests. Server-side validation is a few lines:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;app&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;post&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;&#x2F;login&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;font-style: italic;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;font-style: italic;&quot;&gt; res&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; origin&lt;&#x2F;span&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; req&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;headers&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;origin&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;origin&lt;&#x2F;span&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt; !==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt; &amp;quot;https:&#x2F;&#x2F;yourbank.com&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; res&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt;403&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;send&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;Cross-origin login denied&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7F848E;font-style: italic;&quot;&gt;  &#x2F;&#x2F; authenticate...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;});&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Why attackers can&#x27;t spoof it:&lt;&#x2F;strong&gt; The browser controls the &lt;code&gt;Origin&lt;&#x2F;code&gt; header for form submissions.
JavaScript can set arbitrary &lt;code&gt;Origin&lt;&#x2F;code&gt; headers in &lt;code&gt;fetch()&lt;&#x2F;code&gt; calls, but those trigger CORS preflights
and unless your server explicitly whitelists the attacker&#x27;s domain, they won&#x27;t get through.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pro tip:&lt;&#x2F;strong&gt; Use &lt;code&gt;Origin&lt;&#x2F;code&gt; validation for login forms as there&#x27;s no session yet, so tokens aren&#x27;t
available. It&#x27;s a zero-state, header-only defense.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;the-game-changer-samesite-cookies&quot;&gt;The Game-Changer: &lt;code&gt;SameSite&lt;&#x2F;code&gt; Cookies&lt;&#x2F;h2&gt;
&lt;p&gt;The most impactful CSRF defense came from browser vendors. The &lt;code&gt;SameSite&lt;&#x2F;code&gt; attribute tells the
browser: &quot;only send this cookie when the request comes from my site.&quot;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;http&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;Set-Cookie&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt; session=abc123; SameSite=Lax; Secure; HttpOnly&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Attribute&lt;&#x2F;th&gt;&lt;th&gt;Behavior&lt;&#x2F;th&gt;&lt;th&gt;Best for&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;SameSite=Lax&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Cookie sent for top-level navigations (clicks, form GETs) but not cross-origin POSTs. &lt;strong&gt;Default in Chrome, Firefox, Edge since ~2021.&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;General web apps&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;SameSite=Strict&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Cookie never sent cross-origin, not even for navigation.&lt;&#x2F;td&gt;&lt;td&gt;Password change, account deletion&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;SameSite=None; Secure&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Cookie sent cross-origin. Must also set &lt;code&gt;Secure&lt;&#x2F;code&gt;.&lt;&#x2F;td&gt;&lt;td&gt;Embedded widgets, payment iframes&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;With &lt;code&gt;SameSite=Lax&lt;&#x2F;code&gt; as the default browser behavior, most classic CSRF attacks simply stop working
the browser refuses to attach the session cookie to that hidden form submission.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;What you still need to protect:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Routes that accept &lt;code&gt;GET&lt;&#x2F;code&gt; for state changes (Lax allows top-level GET navigations).&lt;&#x2F;li&gt;
&lt;li&gt;Services that intentionally set &lt;code&gt;SameSite=None&lt;&#x2F;code&gt; for cross-origin embedding.&lt;&#x2F;li&gt;
&lt;li&gt;Legacy browsers that don&#x27;t support &lt;code&gt;SameSite&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;best-practices-cheat-sheet&quot;&gt;Best Practices - Cheat Sheet&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Technique&lt;&#x2F;th&gt;&lt;th&gt;Best for&lt;&#x2F;th&gt;&lt;th&gt;Effort&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;CSRF tokens (session-bound)&lt;&#x2F;td&gt;&lt;td&gt;Server-rendered apps&lt;&#x2F;td&gt;&lt;td&gt;Medium&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Double Submit Cookie&lt;&#x2F;td&gt;&lt;td&gt;SPAs &#x2F; stateless APIs&lt;&#x2F;td&gt;&lt;td&gt;Low&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Origin&lt;&#x2F;code&gt; validation&lt;&#x2F;td&gt;&lt;td&gt;Login forms, public endpoints&lt;&#x2F;td&gt;&lt;td&gt;Low&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;SameSite=Lax&lt;&#x2F;code&gt; cookie&lt;&#x2F;td&gt;&lt;td&gt;All apps&lt;&#x2F;td&gt;&lt;td&gt;One line&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Custom request headers&lt;&#x2F;td&gt;&lt;td&gt;API-only apps&lt;&#x2F;td&gt;&lt;td&gt;Low&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;strong&gt;Rules of thumb:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Layer your defenses.&lt;&#x2F;strong&gt; &lt;code&gt;SameSite=Lax&lt;&#x2F;code&gt; + CSRF tokens covers more edge cases than either alone.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Never mutate state on GET.&lt;&#x2F;strong&gt; Browsers prefetch &lt;code&gt;&amp;lt;img&amp;gt;&lt;&#x2F;code&gt;, &lt;code&gt;&amp;lt;link&amp;gt;&lt;&#x2F;code&gt;, and &lt;code&gt;&amp;lt;script&amp;gt;&lt;&#x2F;code&gt; tags, a GET
based action could fire without anyone clicking anything.&lt;&#x2F;li&gt;
&lt;li&gt;Use &lt;code&gt;SameSite=Strict&lt;&#x2F;code&gt; for sensitive actions (password resets, email changes, account deletion).&lt;&#x2F;li&gt;
&lt;li&gt;If your framework has baked-in CSRF protection (Django, Rails, Laravel, Spring), use it. Don&#x27;t
roll your own unless you have a specific reason.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;X-Frame-Options: DENY&lt;&#x2F;code&gt; helps with clickjacking, a related but distinct attack.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Hello World</title>
        <published>2025-07-09T00:56:46+05:30</published>
        <updated>2025-07-09T00:56:46+05:30</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://codeandchaos.pages.dev/posts/hello-world/"/>
        <id>https://codeandchaos.pages.dev/posts/hello-world/</id>
        
        <content type="html" xml:base="https://codeandchaos.pages.dev/posts/hello-world/">&lt;p&gt;Hello, world.&lt;&#x2F;p&gt;
&lt;p&gt;That first program everyone writes. You type it in, run it, feel like you&#x27;ve accomplished something
monumental then spend the next two hours debugging a typo you swear wasn&#x27;t there.&lt;&#x2F;p&gt;
&lt;p&gt;This post is a bit like that. My first blog post. Again. I&#x27;ve started blogs before, wrote a couple
of posts, and then let the domain expire. But here we are.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m Krishna. I work on identity management platforms currently at my workplace, the kind of systems
that handle SSO, LDAP sync, token lifecycles, and all the boring-but-critical stuff that makes
enterprise auth work without anyone noticing.&lt;&#x2F;p&gt;
&lt;p&gt;I also run Arch Linux (yes, &lt;em&gt;I use arch btw&lt;&#x2F;em&gt;), use Neovim with a Lua config I&#x27;ve been tweaking for
way too long, and I&#x27;m that person who gets irrationally excited about terminal tools nobody&#x27;s heard
of.&lt;&#x2F;p&gt;
&lt;p&gt;As for what this blog is going to be; mostly technical deep dives into things I&#x27;m working on or
figuring out. Auth protocols, system design, performance debugging. The kind of posts where I run
into a problem, spend way too long researching it, and then write it down so I don&#x27;t have to figure
it out again.&lt;&#x2F;p&gt;
&lt;p&gt;Probably some posts about side projects that spiral out of control, there&#x27;s already been a fair
share of &quot;why is this not working&quot; moments that might be worth sharing.&lt;&#x2F;p&gt;
&lt;p&gt;Just stuff I find interesting, written down in case someone else finds it useful too.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
