<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ cookies - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ cookies - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 25 Aug 2026 22:03:40 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/cookies/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ CSRF from Scratch: Browser Mechanics, Attacks, and Spring Security Implementation [Full Handbook] ]]>
                </title>
                <description>
                    <![CDATA[ If you've ever built a web application or configured Spring Security, you've almost certainly encountered Cross-Site Request Forgery (CSRF). In my previous guide, How OAuth 2.0 Works: A Practical Guid ]]>
                </description>
                <link>https://www.freecodecamp.org/news/csrf-from-scratch-browser-mechanics-attacks-and-spring-security-implementation-handbook/</link>
                <guid isPermaLink="false">6a74fe284ef5707f2879423d</guid>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ csrf ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spring-boot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spring security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Java ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cookies ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ashutosh Krishna ]]>
                </dc:creator>
                <pubDate>Thu, 06 Aug 2026 21:35:36 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/20e903c5-9011-4f14-b714-974e32d43f3c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you've ever built a web application or configured Spring Security, you've almost certainly encountered Cross-Site Request Forgery (CSRF).</p>
<p>In my previous guide, <a href="https://medium.com/@ashutoshkrris/how-oauth-2-0-works-a-practical-guide-for-backend-developers-630977209476"><strong>How OAuth 2.0 Works: A Practical Guide for Backend Developers</strong></a>, I briefly touched on the mysterious <code>state</code> parameter and noted that its core purpose is protecting authorization flows against CSRF attacks.</p>
<p>At the time, we treated CSRF as a quick prerequisite concept. Today, we're taking a much deeper dive.</p>
<p>Perhaps you were building a REST API in Spring Boot, ran into unexpected HTTP 403 Forbidden errors on every <code>POST</code> request, and "fixed" it by adding <code>.csrf(csrf -&gt; csrf.disable())</code> to your Security Filter Chain.</p>
<p>Most tutorials treat CSRF as a checkbox item or a framework toggle. They immediately jump to code:</p>
<pre><code class="language-java">// What most tutorials show on line 1:
http.csrf(Customizer.withDefaults());
</code></pre>
<p>Starting with framework configuration hides how web security actually operates. Spring Security doesn't invent security rules out of thin air. It responds to the fundamental mechanics of web browsers, HTTP protocols, and cookies.</p>
<p>In this handbook, we'll take a bottom-up, first-principles approach. We won't talk about Spring Security until we've thoroughly explored browsers, HTTP headers, session management, and the underlying mechanics of Cross-Site Request Forgery.</p>
<p>By the end of this guide, you'll understand:</p>
<ul>
<li><p>Why browsers automatically attach credentials to outgoing requests.</p>
</li>
<li><p>Why that automatic behavior creates a fundamental vulnerability.</p>
</li>
<li><p>Why attackers never need to steal or read your cookies to exploit CSRF.</p>
</li>
<li><p>Why Same Origin Policy (SOP) and CORS don't prevent CSRF.</p>
</li>
<li><p>How modern defenses, from CSRF Tokens to <code>SameSite</code> cookies, work under the hood.</p>
</li>
<li><p>How Spring Security implements these defenses internally and how to configure them effectively.</p>
</li>
</ul>
<p>Let’s begin by stripping away frameworks and looking at how the web actually works.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-the-problem-before-csrf">The Problem Before CSRF</a></p>
</li>
<li><p><a href="#heading-why-browsers-automatically-send-cookies">Why Browsers Automatically Send Cookies</a></p>
</li>
<li><p><a href="#heading-when-automatic-cookies-become-dangerous">When Automatic Cookies Become Dangerous</a></p>
</li>
<li><p><a href="#heading-visualize-the-attack">Visualize the Attack</a></p>
</li>
<li><p><a href="#heading-why-the-browser-isnt-broken">Why the Browser Isn't Broken</a></p>
</li>
<li><p><a href="#heading-same-origin-policy-sop">Same Origin Policy (SOP)</a></p>
</li>
<li><p><a href="#heading-why-cors-does-not-prevent-csrf">Why CORS Does NOT Prevent CSRF</a></p>
</li>
<li><p><a href="#heading-safe-methods-and-state-mutation">Safe Methods and State Mutation</a></p>
</li>
<li><p><a href="#heading-csrf-tokens-synchronizer-token-pattern">CSRF Tokens (Synchronizer Token Pattern)</a></p>
</li>
<li><p><a href="#heading-double-submit-cookie-pattern">Double Submit Cookie Pattern</a></p>
</li>
<li><p><a href="#heading-samesite-cookies">SameSite Cookies</a></p>
</li>
<li><p><a href="#heading-origin-and-referer-headers">Origin and Referer Headers</a></p>
</li>
<li><p><a href="#heading-jwt-and-csrf-the-token-storage-dilemma">JWT and CSRF: The Token Storage Dilemma</a></p>
</li>
<li><p><a href="#heading-spring-security-csrf-internals">Spring Security CSRF Internals</a></p>
</li>
<li><p><a href="#heading-implement-csrf-protection-yourself">Implement CSRF Protection Yourself</a></p>
</li>
<li><p><a href="#heading-testing-csrf-protections">Testing CSRF Protections</a></p>
</li>
<li><p><a href="#heading-common-misconceptions">Common Misconceptions</a></p>
</li>
<li><p><a href="#heading-production-best-practices-checklist">Production Best Practices Checklist</a></p>
</li>
<li><p><a href="#heading-final-summary-amp-defense-matrix">Final Summary &amp; Defense Matrix</a></p>
</li>
</ul>
<h2 id="heading-the-problem-before-csrf">The Problem Before CSRF</h2>
<p>To understand security, we must first understand state.</p>
<p>The Hypertext Transfer Protocol (HTTP) is inherently <strong>stateless</strong>. This means that if Alice sends an HTTP request to <code>travelbuddy.com</code> (our example) at 10:00 AM, and sends another HTTP request to <code>travelbuddy.com</code> at 10:01 AM, the server treats those two requests as completely isolated, unrelated events.</p>
<img src="https://cdn.hashnode.com/uploads/covers/61c1acb4a90dea775da8262b/c5c24ee5-450d-4252-8e79-3744f9814fbd.png" alt="Sequence diagram showing Alice’s browser making a successful GET request to the TravelBuddy Server, followed 1 minute later by a second GET request that returns a 401 Unauthorized error." style="display:block;margin:0 auto" width="1071" height="860" loading="lazy">

<p>Without a mechanism to remember Alice between requests, Alice would have to send her username and password inside <em>every single HTTP request</em> she makes. That would be horrific for both user experience and performance.</p>
<p>Before session mechanisms were standard, developers tried passing credentials via query parameters or basic authentication headers on every click. This led to credential exposure in server logs, browser histories, and URL shares.</p>
<h3 id="heading-how-do-sessions-and-cookies-solve-this">How Do Sessions and Cookies Solve This?</h3>
<p>To solve this, web engineers introduced the concept of <strong>Server-Side Sessions</strong> and <strong>HTTP Cookies</strong>.</p>
<p>When Alice logs into <code>TravelBuddy</code> by sending her username and password via a POST request to <code>https://travelbuddy.com/login</code>, the server verifies her credentials. Instead of asking Alice to log in again on the next page, the server creates a <strong>Session</strong> in its memory (or in a database/Redis cache) and assigns it a unique, unpredictable identifier: a <strong>Session ID</strong>.</p>
<p>The server then sends this Session ID back to Alice’s browser using a special HTTP response header: <code>Set-Cookie</code>.</p>
<pre><code class="language-plaintext">HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: JSESSIONID=abc123xyz789; Path=/; Secure; HttpOnly
</code></pre>
<p>When Alice’s browser receives this response, it sees the <code>Set-Cookie</code> header. It extracts <code>JSESSIONID=abc123xyz789</code> and stores it inside its internal storage unit: the <strong>Browser Cookie Jar</strong>.</p>
<p>Now, Alice is "logged in". The server remembers her via that session record, and the browser holds the key (<code>JSESSIONID</code>) to that session.</p>
<h2 id="heading-why-browsers-automatically-send-cookies">Why Browsers Automatically Send Cookies</h2>
<p>Now we arrive at the pivotal design choice made in the early days of the web.</p>
<p>Once the browser stores <code>JSESSIONID=abc123xyz789</code> in its cookie jar for the domain <code>travelbuddy.com</code>, how does that cookie get sent back to the server on subsequent requests?</p>
<p>Does the developer have to write custom JavaScript to attach the cookie? <strong>No.</strong></p>
<p>Browsers are explicitly designed to handle cookie management <strong>automatically</strong>.</p>
<h3 id="heading-the-request-lifecycle-and-automatic-cookie-attachment">The Request Lifecycle and Automatic Cookie Attachment</h3>
<p>Every time Alice's browser prepares an HTTP request to <code>https://travelbuddy.com</code> (whether caused by Alice clicking a link, submitting an HTML form, or JavaScript triggering a <code>fetch()</code> call), the browser follows this exact process:</p>
<ol>
<li><p><strong>URL Inspection:</strong> The browser examines the destination URL (for example, <code>https://travelbuddy.com/api/connections</code>).</p>
</li>
<li><p><strong>Cookie Jar Lookup:</strong> The browser scans its cookie jar for any stored cookies whose domain and path match <code>travelbuddy.com</code>.</p>
</li>
<li><p><strong>Validation Check:</strong> It verifies if the cookie has expired, and if flags like <code>Secure</code> (requires HTTPS) are respected.</p>
</li>
<li><p><strong>Header Injection:</strong> If valid cookies match, the browser automatically injects a <code>Cookie</code> header into the outgoing HTTP request payload.</p>
</li>
</ol>
<p>Here's what the outgoing request looks like as it leaves Alice's machine:</p>
<pre><code class="language-shell">POST /api/connections/add HTTP/1.1
Host: travelbuddy.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
Accept: text/html,application/xhtml+xml
Cookie: JSESSIONID=abc123xyz789
Content-Type: application/x-www-form-urlencoded

service=SkyScanner
</code></pre>
<p>Notice something critical: <strong>Neither Alice nor any custom frontend JavaScript explicitly attached</strong> <code>Cookie: JSESSIONID=abc123xyz789</code><strong>.</strong></p>
<p>The browser's internal engine attached it automatically before sending the byte stream across the network. From the server's perspective, receiving <code>Cookie: JSESSIONID=abc123xyz789</code> is proof that the request originated from an authenticated session belonging to Alice.</p>
<p>This automatic behavior is convenient. It makes web browsing seamless across page reloads and link navigation. But as we'll soon see, this convenience leaves a backdoor wide open.</p>
<h2 id="heading-when-automatic-cookies-become-dangerous">When Automatic Cookies Become Dangerous</h2>
<p>Is automatic cookie inclusion a vulnerability by itself?</p>
<p><strong>No.</strong> If Alice only visits <code>travelbuddy.com</code>, automatic cookie inclusion works exactly as intended.</p>
<p>The vulnerability emerges because of a simple web reality: <strong>Alice visits multiple websites in the same browser session.</strong></p>
<h3 id="heading-enter-evilcom">Enter <code>evil.com</code></h3>
<p>Suppose Alice is logged into <code>TravelBuddy</code> in Tab 1. Her session cookie (<code>JSESSIONID=abc123xyz789</code>) sits safely inside her browser's cookie jar for <code>travelbuddy.com</code>.</p>
<p>In Tab 2, Alice visits an unrelated website: <code>https://evil.com</code> (perhaps she clicked a link in a phishing email or a forum post).</p>
<p><code>evil.com</code> is controlled by an attacker. The attacker knows that <code>TravelBuddy</code> has a feature located at <code>POST</code> <code>[https://travelbuddy.com/api/connections/add</code> that connects third-party services. The attacker wants to trick Alice into connecting the attacker's malicious service to her account.</p>
<p>The attacker embeds the following hidden HTML form inside the HTML page served by <code>evil.com</code>:</p>
<pre><code class="language-html">&lt;!-- Hosted on https://evil.com/win-a-car.html --&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;
  &lt;h1&gt;You won a free trip! Click below to claim.&lt;/h1&gt;
  
  &lt;!-- Hidden Form targeting TravelBuddy --&gt;
  &lt;form id="maliciousForm" action="https://travelbuddy.com/api/connections/add" method="POST"&gt;
    &lt;input type="hidden" name="service" value="MaliciousAttackerService" /&gt;
  &lt;/form&gt;

  &lt;script&gt;
    // Automatically submit the form as soon as the page loads
    document.getElementById('maliciousForm').submit();
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h3 id="heading-walkthrough-of-the-attack-execution">Walkthrough of the Attack Execution</h3>
<p>Let's trace step-by-step what happens when Alice opens <code>https://evil.com/win-a-car.html</code>:</p>
<ol>
<li><p>Alice's browser fetches and parses HTML from <code>evil.com</code>.</p>
</li>
<li><p>The browser encounters the <code>&lt;script&gt;</code> tag and executes <code>document.getElementById('maliciousForm').submit()</code>.</p>
</li>
<li><p>The browser prepares an outgoing <code>POST</code> request targeting <code>https://travelbuddy.com/api/connections/add</code>.</p>
</li>
<li><p>The browser looks at the target destination: <code>travelbuddy.com</code>.</p>
</li>
<li><p>The browser checks its Cookie Jar: <em>"Do I have any active cookies for</em> <code>travelbuddy.com</code><em>?"</em></p>
</li>
<li><p><strong>Yes!</strong> It finds <code>JSESSIONID=abc123xyz789</code> (Alice's active session cookie from Tab 1).</p>
</li>
<li><p>The browser automatically injects <code>Cookie: JSESSIONID=abc123xyz789</code> into the outgoing request payload heading to <code>travelbuddy.com</code>.</p>
</li>
<li><p>The request lands on the <code>TravelBuddy</code> Spring Boot backend server.</p>
</li>
</ol>
<h3 id="heading-the-servers-perspective">The Server's Perspective</h3>
<p>Here's what the <code>TravelBuddy</code> backend sees when processing the request:</p>
<pre><code class="language-shell">POST /api/connections/add HTTP/1.1
Host: travelbuddy.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID=abc123xyz789

service=MaliciousAttackerService
</code></pre>
<p>The <code>TravelBuddy</code> server checks the <code>Cookie</code> header. It validates <code>JSESSIONID=abc123xyz789</code> against its session store. The session is valid: it belongs to Alice!</p>
<p>The server assumes: <em>"Alice sent a POST request to add</em> <code>MaliciousAttackerService</code><em>. She is authenticated, so I will grant this request."</em></p>
<p>The server updates Alice's account state. <code>MaliciousAttackerService</code> is now connected to her profile.</p>
<h3 id="heading-the-core-realization-of-csrf">The Core Realization of CSRF</h3>
<p>Take a step back and examine what just happened:</p>
<ol>
<li><p><strong>The attacker NEVER saw or stole Alice’s session cookie.</strong> The attacker on <code>evil.com</code> can't read cookies belonging to <code>travelbuddy.com</code> due to browser isolation rules.</p>
</li>
<li><p><strong>The attacker did NOT break encryption.</strong> HTTPS was active the entire time.</p>
</li>
<li><p><strong>The attacker simply induced Alice's browser to make a request.</strong> The browser, faithfully executing its automatic cookie attachment rules, provided the credentials on behalf of the attacker. You could say the attacker got caught with their hand in Alice's cookie jar!</p>
</li>
</ol>
<p>This is <strong>Cross-Site Request Forgery in action</strong>: An attacker tricks a victim's browser into executing an unwanted, state-changing HTTP request to a trusted site where the victim is currently authenticated.</p>
<h2 id="heading-visualize-the-attack">Visualize the Attack</h2>
<p>Visualizing the interaction between Alice, the browser, <code>evil.com</code>, and <code>TravelBuddy</code> makes the underlying request flow clear.</p>
<h3 id="heading-1-the-complete-csrf-sequence">1. The Complete CSRF Sequence</h3>
<img src="https://cdn.hashnode.com/uploads/covers/61c1acb4a90dea775da8262b/0431c42a-815b-482c-900e-7985c3f5ace1.png" alt="Sequence diagram illustrating a Cross-Site Request Forgery (CSRF) attack where an attacker site (evil.com) uses an auto-submitting form to trick a logged-in user’s browser into sending an authenticated request to travelbuddy.com." style="display:block;margin:0 auto" width="2614" height="2116" loading="lazy">

<p>The attack unfolds across three distinct phases involving four main actors: Alice, her web browser, the TravelBuddy backend server, and the attacker site running on <code>evil.com</code>.</p>
<p>In the first phase, Alice authenticates with TravelBuddy. She submits her login credentials through her browser, which sends a POST request to the TravelBuddy backend. The backend verifies her credentials and responds with an HTTP 200 OK status alongside a <code>Set-Cookie</code> header containing <code>JSESSIONID=abc123xyz</code>.</p>
<p>Upon receiving this response, Alice's browser automatically saves this session identifier inside its cookie jar for the <code>travelbuddy.com</code> domain.</p>
<p>In the second phase, the attacker sets a trap. While keeping her TravelBuddy tab active, Alice opens a second browser tab and visits <code>evil.com</code>. Her browser requests the page <code>win-a-car.html</code> from <code>evil.com</code>. In response, <code>evil.com</code> serves an HTML document containing an invisible form targeting TravelBuddy, paired with an embedded JavaScript script designed to trigger immediately upon loading.</p>
<p>In the final phase, the attack executes automatically. The malicious JavaScript on <code>evil.com</code> calls <code>form.submit()</code>, commanding the browser to send a POST request to <code>https://travelbuddy.com/api/connections/add</code>.</p>
<p>Before sending the request across the network, the browser checks its cookie jar for any cookies matching <code>travelbuddy.com</code>. It finds Alice's active session cookie and automatically attaches <code>Cookie: JSESSIONID=abc123xyz</code> to the outgoing request payload. The TravelBuddy server receives the request, inspects the valid session cookie, assumes Alice intended to perform this action, and attaches the attacker's service to her account.</p>
<h3 id="heading-2-browser-decision-tree-during-outgoing-request">2. Browser Decision Tree during Outgoing Request</h3>
<p>When any request is fired, the browser follows a decision path regarding cookie attachment:</p>
<img src="https://cdn.hashnode.com/uploads/covers/61c1acb4a90dea775da8262b/59876902-2a43-4082-81f8-83e2c198e0c6.png" alt="Flowchart showing how a web browser automatically checks its Cookie Jar and attaches valid cookies to an outgoing HTTP request targeting travelbuddy.com." style="display:block;margin:0 auto" width="1168" height="2635" loading="lazy">

<p>This diagram outlines the automatic evaluation loop executed by a browser whenever an HTTP request is triggered from any tab or script.</p>
<p>The process begins as soon as an outgoing HTTP request is initiated. The browser first inspects the target URL to extract the destination domain, such as <code>travelbuddy.com</code>. Once the domain is identified, the browser queries its internal cookie storage to check whether any cookies are mapped to that target domain. If no matching cookies exist, the browser immediately skips credential attachment and dispatches the raw HTTP request across the network.</p>
<p>If matching cookies are found, the browser evaluates their validity. It checks whether the cookies have expired, whether the request path matches the path defined in the cookie, and whether security constraints like the <code>Secure</code> HTTPS flag are satisfied. If any validation check fails, the cookie is discarded, and the request proceeds without credentials. But if the cookies are valid and active, the browser constructs a <code>Cookie</code> header containing the stored session key and attaches it to the outgoing HTTP request payload before dispatching it across the network to the server.</p>
<h3 id="heading-3-session-and-cookie-lifecycle-state-diagram">3. Session and Cookie Lifecycle State Diagram</h3>
<img src="https://cdn.hashnode.com/uploads/covers/61c1acb4a90dea775da8262b/b244543c-2ad6-455c-949d-eefe219eb4a0.png" alt="State diagram showing a user transitioning from an unauthenticated state to an authenticated state with automatic cookie management, and how maintaining an active session leaves the application vulnerable to CSRF when visiting a malicious site." style="display:block;margin:0 auto" width="902" height="2096" loading="lazy">

<p>This state diagram tracks how a user moves between secure, authenticated, and vulnerable conditions during a web session.</p>
<p>When a user first opens their web browser, they begin in an unauthenticated state with no cookies stored for the target application. Submitting valid credentials via a login form transitions the user into an authenticated state. Inside this authenticated state, the server issues a <code>Set-Cookie</code> header, causing the browser to save the session ID in its cookie storage. For every subsequent request directed to that application, the browser automatically attaches the cookie while keeping the user logged in.</p>
<p>A vulnerability window opens when an authenticated user opens a second tab and navigates to an untrusted website while their application session remains active. This action shifts the browser context into a state vulnerable to Cross-Site Request Forgery. If the untrusted site fires a cross-site request back to the original application, the browser's automatic cookie attachment mechanism triggers, executing an unauthorized state change on the server. The cycle ends only when the user logs out or the server session expires, returning the client to the initial unauthenticated state.</p>
<h2 id="heading-why-the-browser-isnt-broken">Why the Browser Isn't Broken</h2>
<p>When developers first grasp CSRF, their immediate reaction is often: <em>"This is a terrible browser flaw! Why don't browser vendors fix this by disabling automatic cookie sending entirely?"</em></p>
<p>To understand why browsers behave this way, we must look at <strong>Web Compatibility</strong> and a concept known in security engineering as <strong>Ambient Authority</strong>.</p>
<h3 id="heading-the-principle-of-ambient-authority">The Principle of Ambient Authority</h3>
<p>When a system automatically applies a user's identity or credentials to every action without requiring explicit user intent for <em>that specific action</em>, the system is using <strong>ambient authority</strong>.</p>
<p>HTTP cookies are an ambient credential. If you're logged in, every request carrying a destination URL automatically includes your credential.</p>
<h3 id="heading-why-browser-vendors-dont-just-fix-it">Why Browser Vendors Don't Just "Fix" It</h3>
<p>The World Wide Web was created as a web of interconnected hypermedia documents. Cross-site interactions are a fundamental design feature of the web, not an accidental bug:</p>
<ul>
<li><p><strong>Images and assets:</strong> When <code>news.com</code> embeds an image hosted on <code>cdn.com</code>, your browser makes a cross-site request to <code>cdn.com</code>.</p>
</li>
<li><p><strong>Cross-site form submissions:</strong> In the early web (and still today), paying with PayPal meant an HTML form on <code>e-commerce.com</code> submitted data directly to <code>paypal.com</code>.</p>
</li>
<li><p><strong>Hyperlinks:</strong> Clicking a link on <code>google.com</code> takes you to <code>wikipedia.org</code> via a cross-site GET request.</p>
</li>
</ul>
<p>If browsers suddenly stopped attaching cookies to cross-site requests by default, <strong>millions of legacy websites built over three decades would break instantly.</strong> Users would be logged out whenever they clicked a link from an email, a search engine, or a social media site.</p>
<p>Browser vendors prioritize backward compatibility. Rather than removing cross-site capabilities, they introduced configurable security boundaries that developers can opt into.</p>
<p>To understand these boundaries, we must first look at the most fundamental browser security model: the <strong>Same Origin Policy</strong>.</p>
<h2 id="heading-same-origin-policy-sop">Same Origin Policy (SOP)</h2>
<p>Many developers assume: <em>"Doesn't the Same Origin Policy block cross-site requests?"</em></p>
<p>This is one of the most common misunderstandings in web development. Let's clarify what the Same Origin Policy actually is and what it does.</p>
<h3 id="heading-defining-an-origin">Defining an Origin</h3>
<p>An <strong>Origin</strong> in web security is defined by three components:</p>
<ol>
<li><p><strong>Scheme</strong> (Protocol, for example, <code>http</code> vs <code>https</code>)</p>
</li>
<li><p><strong>Host</strong> (Domain, for example, <code>travelbuddy.com</code>)</p>
</li>
<li><p><strong>Port</strong> (for example, <code>:80</code>, <code>:443</code>, <code>:8080</code>)</p>
</li>
</ol>
<p>Two URLs have the <strong>Same Origin</strong> if and only if all three components match exactly.</p>
<table>
<thead>
<tr>
<th>URL 1</th>
<th>URL 2</th>
<th>Same Origin?</th>
<th>Reason</th>
</tr>
</thead>
<tbody><tr>
<td><code>https://travelbuddy.com/page1</code></td>
<td><code>https://travelbuddy.com/page2</code></td>
<td><strong>YES</strong></td>
<td>Scheme, host, and port match.</td>
</tr>
<tr>
<td><code>http://travelbuddy.com/page1</code></td>
<td><code>https://travelbuddy.com/page1</code></td>
<td><strong>NO</strong></td>
<td>Scheme differs (<code>http</code> vs <code>https</code>).</td>
</tr>
<tr>
<td><code>https://travelbuddy.com/page1</code></td>
<td><code>https://api.travelbuddy.com/page1</code></td>
<td><strong>NO</strong></td>
<td>Host differs (<code>travelbuddy.com</code> vs <code>api.travelbuddy.com</code>).</td>
</tr>
<tr>
<td><code>https://travelbuddy.com:8080</code></td>
<td><code>https://travelbuddy.com:9090</code></td>
<td><strong>NO</strong></td>
<td>Port differs (<code>8080</code> vs <code>9090</code>).</td>
</tr>
</tbody></table>
<h3 id="heading-what-sop-protects-vs-what-sop-allows">What SOP Protects vs. What SOP Allows</h3>
<p>The Same Origin Policy governs how scripts running on one origin can interact with resources on another origin.</p>
<p><strong>The SOP Golden Rule:</strong> Same Origin Policy restricts scripts from <strong>READING</strong> responses from another origin. Same Origin Policy generally <strong>DOES NOT PREVENT</strong> scripts or HTML from <strong>SENDING</strong> requests to another origin.</p>
<p>Let's emphasize this distinction:</p>
<p>Sending a request: <code>evil.com</code> can create an HTML form like this: <code>&lt;form action="https://travelbuddy.com/api/delete" method="POST"&gt;</code>. When the form is submitted, the browser will send the request to <code>travelbuddy.com</code>. The backend will process the request and mutate the database state.</p>
<p>Reading the response: JavaScript running on <code>evil.com</code> attempts to inspect the HTTP response body returned by <code>travelbuddy.com</code>. The browser <strong>blocks</strong> JavaScript from reading that data because <code>evil.com</code> and <code>travelbuddy.com</code> are different origins.</p>
<img src="https://cdn.hashnode.com/uploads/covers/61c1acb4a90dea775da8262b/dd549ea3-7d83-494a-90b0-9a7a3c0a91b8.png" alt="Sequence diagram showing how the Browser’s Same-Origin Policy (SOP) blocks malicious JavaScript on evil.com from reading a cross-origin HTTP response from travelbuddy.com, even though the server executed the request." style="display:block;margin:0 auto" width="1508" height="816" loading="lazy">

<p>Notice the flaw relative to CSRF: <strong>CSRF is an attack on state mutation, not data retrieval.</strong></p>
<p>The attacker on <code>evil.com</code> doesn't care to read the response payload returning from <code>travelbuddy.com</code>. Their goal was simply to trigger the action on the server. Because SOP permits request execution and only blocks response reading, <strong>Same Origin Policy alone offers zero protection against CSRF.</strong></p>
<h2 id="heading-why-cors-does-not-prevent-csrf">Why CORS Does NOT Prevent CSRF</h2>
<p>This brings us to another major source of confusion: <strong>Cross-Origin Resource Sharing (CORS)</strong>.</p>
<p>In developer forums, when someone experiences a CSRF issue or a cross-site issue, a common suggestion is: <em>"Just configure CORS properly on your backend!"</em></p>
<p>Let's state this as clearly as possible: CORS does <strong>NOT</strong> prevent CSRF attacks. In fact, CORS is designed to <em>relax</em> Same Origin Policy restrictions, not add new security restrictions.</p>
<h3 id="heading-reading-vs-sending-revisited">Reading vs. Sending Revisited</h3>
<p>Remember: SOP blocks cross-origin reading by default.</p>
<p>CORS (Cross-Origin Resource Sharing) is a mechanism that allows a server (for example, <code>travelbuddy.com</code>) to explicitly tell the browser: <em>"I trust JavaScript running on</em> <code>trusted-partner.com</code><em>. You may allow</em> <code>trusted-partner.com</code> <em>to read my responses."</em></p>
<p>CORS is an opt-in mechanism to <strong>allow cross-origin reading</strong>. Disabling or improperly configuring CORS doesn't stop a browser from sending a forged request.</p>
<h3 id="heading-simple-requests-vs-preflighted-requests">Simple Requests vs. Preflighted Requests</h3>
<p>To understand why CORS fails to stop CSRF, we must examine how browsers handle cross-origin HTTP requests under CORS rules. Browsers divide cross-origin requests into two categories:</p>
<ol>
<li><p>Simple Requests</p>
</li>
<li><p>Preflighted Requests</p>
</li>
</ol>
<h4 id="heading-1-simple-requests">1. Simple Requests</h4>
<p>A request is considered a <strong>Simple Request</strong> if it satisfies all of the following:</p>
<ul>
<li><p>Uses HTTP methods: <code>GET</code>, <code>HEAD</code>, or <code>POST</code>.</p>
</li>
<li><p>Uses standard browser Content-Types: <code>application/x-www-form-urlencoded</code>, <code>multipart/form-data</code>, or <code>text/plain</code>.</p>
</li>
<li><p>Doesn't set custom HTTP headers (like <code>X-Requested-With</code> or <code>Authorization</code>).</p>
</li>
</ul>
<p>When a browser encounters a <strong>Simple Request</strong> (such as a standard HTML form POST), it sends the request immediately to the target server.</p>
<img src="https://cdn.hashnode.com/uploads/covers/61c1acb4a90dea775da8262b/a0f9324c-2d7b-4495-a519-c95f5c959be4.png" alt="Sequence diagram illustrating why CORS does not prevent CSRF attacks on simple requests, showing that travelbuddy.com executes a state-changing POST request before the browser blocks evil.com from reading the response." style="display:block;margin:0 auto" width="1877" height="1184" loading="lazy">

<p>As the diagram shows, the server executes the SQL <code>UPDATE</code> or <code>INSERT</code> statement the moment the request arrives. By the time the browser evaluates CORS headers on the returning response, the state mutation on the server has already happened.</p>
<h4 id="heading-2-preflighted-requests">2. Preflighted Requests</h4>
<p>If a request uses non-standard methods (<code>PUT</code>, <code>DELETE</code>) or non-standard content types (<code>application/json</code>), or custom headers, the browser first sends an <code>OPTIONS</code> request called a <strong>Preflight Request</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/61c1acb4a90dea775da8262b/9ec33169-fa8b-48e6-b59f-7365f435ca33.png" alt="Sequence diagram demonstrating how CORS preflight requests (OPTIONS) prevent CSRF attacks by stopping non-simple requests (like JSON payloads) before the actual POST request is sent to travelbuddy.com." style="display:block;margin:0 auto" width="1509" height="918" loading="lazy">

<p>Because <code>OPTIONS</code> preflight requests don't carry side-effects and are checked before sending the actual request, CORS <em>incidentally</em> stops cross-origin JSON requests from unapproved domains.</p>
<p>But relying on CORS for security is dangerous: an attacker can easily fall back to a Simple Request (<code>application/x-www-form-urlencoded</code>) using a standard HTML form submission, completely bypassing the CORS preflight check.</p>
<h2 id="heading-safe-methods-and-state-mutation">Safe Methods and State Mutation</h2>
<p>Before we dive into effective defenses, we must address an architectural concept defined in HTTP specifications (RFC 9110): <strong>Safe Methods</strong> and <strong>Idempotency</strong>.</p>
<p>HTTP methods are categorized based on their intended impact on server state:</p>
<ul>
<li><p><strong>Safe Methods (</strong><code>GET</code><strong>,</strong> <code>HEAD</code><strong>,</strong> <code>OPTIONS</code><strong>,</strong> <code>TRACE</code><strong>):</strong> These methods are defined as read-only operations. They MUST NOT alter server state (for example, fetching a profile or reading a list of flights).</p>
</li>
<li><p><strong>Unsafe / State-Modifying Methods (</strong><code>POST</code><strong>,</strong> <code>PUT</code><strong>,</strong> <code>DELETE</code><strong>,</strong> <code>PATCH</code><strong>):</strong> These methods are intended to perform actions, modify databases, create resources, or trigger transactions.</p>
</li>
</ul>
<h3 id="heading-the-developer-crime-state-changing-get-requests">The Developer Crime: State-Changing GET Requests</h3>
<p>Consider what happens if a junior developer on the <code>TravelBuddy</code> team writes code like this:</p>
<pre><code class="language-java">// ❌ DANGEROUS CODE: State mutation via GET request
@GetMapping("/api/connections/delete")
public String deleteConnection(@RequestParam String serviceId, HttpSession session) {
    User user = (User) session.getAttribute("user");
    connectionService.deleteForUser(user, serviceId);
    return "redirect:/dashboard";
}
</code></pre>
<p>Why is this an architectural error and a massive security vulnerability?</p>
<p>Because an attacker on <code>evil.com</code> doesn't even need an HTML form or JavaScript to trigger a <code>GET</code> request. They can trigger a <code>GET</code> request using simple HTML element tags:</p>
<pre><code class="language-html">&lt;!-- Hosted on evil.com --&gt;
&lt;img src="https://travelbuddy.com/api/connections/delete?serviceId=SkyScanner" width="0" height="0" /&gt;
</code></pre>
<p>When Alice's browser parses the HTML from <code>evil.com</code>, it encounters the <code>&lt;img&gt;</code> tag. To render the page, the browser automatically sends a <code>GET</code> request to <code>https://travelbuddy.com/api/connections/delete?serviceId=SkyScanner</code>, automatically attaching Alice's session cookie.</p>
<p>The backend receives the <code>GET</code> request, executes <code>connectionService.deleteForUser(...)</code>, and wipes Alice's integration!</p>
<h3 id="heading-rule-1-of-web-security">Rule #1 of Web Security</h3>
<p><code>GET</code> <strong>requests MUST ALWAYS be safe and read-only.</strong> Never perform state mutations (creates, updates, deletes) inside a <code>GET</code> handler.</p>
<p>Enforcing safe <code>GET</code> requests is the foundation of web security. But keeping <code>GET</code> requests read-only only protects against image-tag vectors: it doesn't protect your <code>POST</code>, <code>PUT</code>, or <code>DELETE</code> endpoints from CSRF.</p>
<p>For state-modifying requests, we need specialized defenses.</p>
<h2 id="heading-csrf-tokens-synchronizer-token-pattern">CSRF Tokens (Synchronizer Token Pattern)</h2>
<p>Now that you understand the core vulnerability (that browsers automatically attach ambient credentials/cookies to outgoing cross-site requests) you can bake standard security right into your app.</p>
<h3 id="heading-what-problem-existed-before-csrf-tokens">What Problem Existed Before CSRF Tokens?</h3>
<p>Servers couldn't differentiate between an HTTP request triggered intentionally by the user from inside <code>travelbuddy.com</code>'s real user interface and one forged by <code>evil.com</code> that caused the browser to automatically attach the user's cookies.</p>
<p>From the server's perspective, both requests looked identical: same session cookie, target URL, and payload structure.</p>
<h3 id="heading-how-do-csrf-tokens-solve-this">How Do CSRF Tokens Solve This?</h3>
<p>To distinguish genuine requests from forged requests, we must require a piece of evidence that <strong>only the real application knows</strong>, and that an external attacker site can't forge or read.</p>
<p>This defense is known as the <strong>Synchronizer Token Pattern</strong> (or <strong>CSRF Token</strong>).</p>
<h3 id="heading-how-the-synchronizer-token-pattern-works">How the Synchronizer Token Pattern Works</h3>
<ol>
<li><p><strong>Token generation:</strong> When Alice logs in or requests a page containing a form from <code>travelbuddy.com</code>, the server generates a cryptographically strong, random, unpredictable string (for example, a 128-bit SecureRandom UUID).</p>
</li>
<li><p><strong>Session storage:</strong> The server binds this generated string to Alice's server-side session state.</p>
</li>
<li><p><strong>Token injection into the UI:</strong> The server includes this token inside the HTML response rendered to Alice, typically as a hidden input field inside forms, or as a meta tag for JavaScript to read.</p>
</li>
<li><p><strong>Token submission:</strong> When Alice submits the form, her browser sends the hidden token back in the request body (or as a custom HTTP header).</p>
</li>
<li><p><strong>Server validation:</strong> The server compares the token received in the request against the token saved in Alice's server-side session.</p>
<ul>
<li><p>If the tokens match: Request is <strong>Genuine</strong>. Process it.</p>
</li>
<li><p>If the tokens don't match (or the token is missing): Request is <strong>Forged</strong>. Reject with HTTP 403 Forbidden!</p>
</li>
</ul>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/61c1acb4a90dea775da8262b/5e748f3c-c543-4d70-b772-40af5597af08.png" alt="Sequence diagram demonstrating the Synchronizer Token Pattern (CSRF token), where TravelBuddy Server generates a secret token stored in Alice's session and embeds it in an HTML form to validate subsequent POST requests." style="display:block;margin:0 auto" width="2622" height="1890" loading="lazy">

<h3 id="heading-html-form-example">HTML Form Example</h3>
<p>Here is how <code>TravelBuddy</code> renders a protected form:</p>
<pre><code class="language-html">&lt;!-- Rendered by TravelBuddy at https://travelbuddy.com/connect-service --&gt;
&lt;form action="/api/connections/add" method="POST"&gt;
  &lt;!-- Standard form fields --&gt;
  &lt;label for="service"&gt;Service Name:&lt;/label&gt;
  &lt;input type="text" id="service" name="service" value="SkyScanner" /&gt;

  &lt;!-- Secret CSRF Token injected by Server Template Engine (Thymeleaf/JSP) --&gt;
  &lt;input type="hidden" name="_csrf" value="CSRF-KEY-998877" /&gt;

  &lt;button type="submit"&gt;Submit&lt;/button&gt;
&lt;/form&gt;
</code></pre>
<p>When submitted, the raw HTTP request looks like this:</p>
<pre><code class="language-shell">POST /api/connections/add HTTP/1.1
Host: travelbuddy.com
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID=abc123xyz789

service=SkyScanner&amp;_csrf=CSRF-KEY-998877
</code></pre>
<h3 id="heading-why-attackers-cant-forge-the-csrf-token">Why Attackers Can't Forge the CSRF Token</h3>
<p>Now let's trace what happens when <code>evil.com</code> tries to forge this request:</p>
<ol>
<li><p><code>evil.com</code> builds an auto-submitting form targeting <code>https://travelbuddy.com/api/connections/add</code>.</p>
</li>
<li><p>To succeed, <code>evil.com</code> must include <code>_csrf=CSRF-KEY-998877</code> in its form payload.</p>
</li>
<li><p><strong>How can</strong> <code>evil.com</code> <strong>get</strong> <code>CSRF-KEY-998877</code><strong>?</strong></p>
<ul>
<li><p>Can <code>evil.com</code> guess it? <strong>No.</strong> The token is a cryptographically secure random value (for example, 128 bits of entropy).</p>
</li>
<li><p>Can <code>evil.com</code> make an AJAX <code>GET</code> request to <code>travelbuddy.com</code> to read the HTML form and extract the token? <strong>No!</strong> Because Same Origin Policy (SOP) blocks <code>evil.com</code> JavaScript from reading the response contents of <code>travelbuddy.com</code>.</p>
</li>
</ul>
</li>
</ol>
<p>Because the attacker can't read the page from <code>travelbuddy.com</code>, they can't extract the valid token. When <code>evil.com</code> submits its forged form without a valid <code>_csrf</code> token, the <code>TravelBuddy</code> backend rejects the request immediately:</p>
<pre><code class="language-shell">HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "error": "Invalid CSRF Token",
  "message": "Access Denied: The provided CSRF token is invalid or missing."
}
</code></pre>
<h2 id="heading-double-submit-cookie-pattern">Double Submit Cookie Pattern</h2>
<p>While the Synchronizer Token Pattern is robust, it requires the server to maintain server-side session state to store the token.</p>
<p>What if your backend application is stateless (for example, microservices scaled horizontally across multiple servers without shared session storage)?</p>
<p>Enter the <strong>Double Submit Cookie Pattern</strong>.</p>
<h3 id="heading-how-double-submit-cookie-works">How Double Submit Cookie Works</h3>
<p>In a stateless architecture, the server can't look up a token in a session store. Instead, it relies on cryptographic and domain-isolation properties:</p>
<ol>
<li><p><strong>Cookie generation:</strong> When a user logs in, the server generates a random, cryptographically secure CSRF token.</p>
</li>
<li><p><strong>Setting the cookie:</strong> The server sends this token to the browser as a cookie (for example, <code>XSRF-TOKEN</code>). Crucially, this cookie is <strong>NOT</strong> marked <code>HttpOnly</code>, so client-side JavaScript running on <code>travelbuddy.com</code> can read it.</p>
</li>
<li><p><strong>Frontend header injection:</strong> When the Single Page Application (SPA, such as React, Angular, or Vue) running on <code>travelbuddy.com</code> makes an HTTP request, its custom API client (for example, Axios or <code>fetch</code>) reads the <code>XSRF-TOKEN</code> cookie value and copies that exact value into a custom HTTP request header (for example, <code>X-XSRF-TOKEN</code>).</p>
</li>
<li><p><strong>Server verification:</strong> When the request arrives, the server compares the value in the cookie against the value in the custom header.</p>
</li>
</ol>
<p>If <code>Cookie Value == Header Value</code>, the request is valid.</p>
<img src="https://cdn.hashnode.com/uploads/covers/61c1acb4a90dea775da8262b/09c2f58e-ca6c-4af7-a141-be69c449f58a.png" alt="Sequence diagram illustrating the Double Submit Cookie pattern, where JavaScript reads a non-HttpOnly CSRF token cookie and echoes its value in a custom HTTP header for server validation." style="display:block;margin:0 auto" width="2828" height="1520" loading="lazy">

<h3 id="heading-why-double-submit-cookie-works-against-cross-site-attackers">Why Double Submit Cookie Works against Cross-Site Attackers</h3>
<p>Suppose Alice visits <code>evil.com</code>:</p>
<ol>
<li><p><code>evil.com</code> triggers a cross-site request to <code>travelbuddy.com</code>.</p>
</li>
<li><p>The browser automatically attaches the stored <code>XSRF-TOKEN</code> cookie to the outgoing request.</p>
</li>
<li><p><strong>But</strong> <code>evil.com</code> <strong>must also set the custom header</strong> <code>X-XSRF-TOKEN</code> <strong>with a matching value.</strong></p>
</li>
<li><p>Can <code>evil.com</code> read the <code>XSRF-TOKEN</code> cookie to copy its value into the header? <strong>No!</strong> Browsers strictly prevent <code>evil.com</code> from reading cookies set by <code>travelbuddy.com</code>.</p>
</li>
<li><p>Can <code>evil.com</code> write custom headers on a cross-site request? <strong>No!</strong> Adding custom HTTP headers triggers a CORS preflight (<code>OPTIONS</code>) request, which <code>travelbuddy.com</code> will reject for <code>evil.com</code>.</p>
</li>
</ol>
<p>Since <code>evil.com</code> can't read the cookie value, it can't provide a matching value in the HTTP header. The server compares <code>Header (null)</code> vs <code>Cookie (secret-value-123)</code>, sees a mismatch, and rejects the request.</p>
<h2 id="heading-samesite-cookies">SameSite Cookies</h2>
<p>For over two decades, developers relied entirely on CSRF tokens. Then, in 2016, browser engineers introduced an elegant, browser-native defense mechanism directly into the HTTP cookie specification: the <code>SameSite</code> <strong>attribute</strong>. This defense really takes the biscuit when it comes to simplicity.</p>
<h3 id="heading-what-problem-existed-before-samesite">What Problem Existed Before <code>SameSite</code>?</h3>
<p>Cookies were strictly cross-site by default. If a site set a cookie, the browser attached it to <em>every</em> HTTP request targeting that domain, regardless of where the request originated.</p>
<h3 id="heading-how-samesite-solves-this">How <code>SameSite</code> Solves This</h3>
<p>The <code>SameSite</code> cookie attribute allows developers to instruct the browser whether to attach a cookie during cross-site requests.</p>
<p>Syntax in HTTP response:</p>
<pre><code class="language-shell">Set-Cookie: JSESSIONID=abc123xyz789; Path=/; Secure; HttpOnly; SameSite=Lax
</code></pre>
<p><code>SameSite</code> accepts three values: <code>Strict</code>, <code>Lax</code>, and <code>None</code>.</p>
<table style="min-width:100px"><colgroup><col style="min-width:25px"><col style="min-width:25px"><col style="min-width:25px"><col style="min-width:25px"></colgroup><tbody><tr><td><p><strong>SameSite Mode</strong></p></td><td><p><strong>Same-Site Requests</strong></p></td><td><p><strong>Cross-Site Top-Level Navigation (for example, clicking a link)</strong></p></td><td><p><strong>Cross-Site Subrequests (for example, HTML forms, AJAX, &lt;img&gt;, &lt;iframe&gt;)</strong></p></td></tr><tr><td><p><code>Strict</code></p></td><td><p>Sent</p></td><td><p><strong>Blocked</strong></p></td><td><p><strong>Blocked</strong></p></td></tr><tr><td><p><code>Lax</code> (Modern Default)</p></td><td><p>Sent</p></td><td><p><strong>Sent</strong> (Safe <code>GET</code> methods only)</p></td><td><p><strong>Blocked</strong></p></td></tr><tr><td><p><code>None</code></p></td><td><p>Sent</p></td><td><p>Sent</p></td><td><p>Sent (Requires <code>Secure</code> flag)</p></td></tr></tbody></table>

<h3 id="heading-deep-dive-into-samesite-modes">Deep Dive into SameSite Modes</h3>
<h4 id="heading-1-samesitestrict">1. <code>SameSite=Strict</code></h4>
<p>This is the most secure setting. The browser <strong>never</strong> attaches the cookie on any cross-site request.</p>
<p>Let's say that Alice is logged into <code>TravelBuddy</code> (<code>SameSite=Strict</code>). She clicks a link on <code>twitter.com</code> pointing to <code>https://travelbuddy.com/dashboard</code>.</p>
<p>Because the navigation originated from a cross-site source (<code>twitter.com</code>), the browser <strong>omits</strong> the <code>JSESSIONID</code> cookie. Alice lands on <code>TravelBuddy</code> appearing logged out.</p>
<p>This gives her maximum security, but introduces user friction for standard link navigation.</p>
<h4 id="heading-2-samesitelax-modern-browser-default">2. <code>SameSite=Lax</code> (Modern Browser Default)</h4>
<p><code>Lax</code> provides a pragmatic balance between security and user experience.</p>
<ul>
<li><p><strong>Top-level navigations (</strong><code>GET</code><strong>):</strong> If Alice clicks a link on <code>twitter.com</code> to open <code>https://travelbuddy.com/dashboard</code>, the browser <strong>includes</strong> the cookie. Alice stays logged in!</p>
</li>
<li><p><strong>State-modifying / cross-site requests (</strong><code>POST</code><strong>,</strong> <code>PUT</code><strong>,</strong> <code>DELETE</code> <strong>or</strong> <code>&lt;img&gt;</code> <strong>tags):</strong> If <code>evil.com</code> submits a cross-site <code>POST</code> form to <code>travelbuddy.com</code>, the browser <strong>blocks and strips</strong> the cookie.</p>
</li>
</ul>
<pre><code class="language-shell">/* Cross-site POST request from evil.com targeting travelbuddy.com */
POST /api/connections/add HTTP/1.1
Host: travelbuddy.com
User-Agent: Mozilla/5.0
/* Cookie header is STRIPPED by browser because SameSite=Lax! */

service=MaliciousService
</code></pre>
<p>Because the cookie is missing, <code>TravelBuddy</code> treats the request as unauthenticated and drops it with HTTP 401 Unauthorized.</p>
<h4 id="heading-3-samesitenone">3. <code>SameSite=None</code></h4>
<p>Disables <code>SameSite</code> restrictions entirely. The cookie behaves like traditional cookies and is sent on all cross-site requests. Modern browsers require <code>SameSite=None</code> to be accompanied by the <code>Secure</code> attribute (HTTPS only).</p>
<h3 id="heading-is-samesitelax-a-complete-replacement-for-csrf-tokens">Is <code>SameSite=Lax</code> a Complete Replacement for CSRF Tokens?</h3>
<p>Modern browsers (Chrome, Firefox, Edge, Safari) now set <code>SameSite=Lax</code> as the implicit default if no <code>SameSite</code> attribute is specified.</p>
<p>This doesn't mean CSRF tokens are dead. <code>SameSite=Lax</code> should be viewed as <strong>defense-in-depth</strong>, not a total replacement for CSRF tokens, for several reasons:</p>
<ol>
<li><p><strong>Older browsers:</strong> Legacy browsers or specialized embedded web views don't enforce modern <code>SameSite</code> defaults.</p>
</li>
<li><p><strong>Top-level GET vulnerabilities:</strong> If your application incorrectly mutates state on a <code>GET</code> request, <code>SameSite=Lax</code> will <strong>not</strong> protect you, because <code>Lax</code> permits cookies on top-level cross-site <code>GET</code> navigations.</p>
</li>
<li><p><strong>Client-side refresh windows:</strong> Some browsers apply a 2-minute "Lax-by-default" window exception for top-level POSTs on newly set cookies to handle legacy authentication flows.</p>
</li>
</ol>
<h2 id="heading-origin-and-referer-headers">Origin and Referer Headers</h2>
<p>In addition to CSRF tokens and <code>SameSite</code> cookies, servers can inspect incoming HTTP headers to verify the geographical source of a request: the <code>Origin</code> and <code>Referer</code> headers.</p>
<h3 id="heading-understanding-the-headers">Understanding the Headers</h3>
<p>When a browser makes an HTTP request, it automatically attaches contextual metadata headers:</p>
<ul>
<li><p><code>Origin</code> <strong>Header:</strong> Indicates the origin (scheme + domain + port) of the page that initiated the request. For example: <code>Origin: https://evil.com</code></p>
</li>
<li><p><code>Referer</code> <strong>Header:</strong> Contains the full URL of the exact web page that initiated the request. For example: <code>Referer: https://evil.com/win-a-car.html</code></p>
</li>
</ul>
<h3 id="heading-server-side-validation-logic">Server-Side Validation Logic</h3>
<p>When a state-modifying request (<code>POST</code>, <code>PUT</code>, <code>DELETE</code>) arrives at <code>TravelBuddy</code>, a security filter can inspect these headers:</p>
<pre><code class="language-java">// Conceptual Origin/Referer Checking Logic
public boolean isValidRequest(HttpServletRequest request) {
    String origin = request.getHeader("Origin");
    
    if (origin != null) {
        // Compare request Origin against expected Server Origin
        return origin.equals("https://travelbuddy.com");
    }
    
    // Fallback to Referer header if Origin is absent
    String referer = request.getHeader("Referer");
    if (referer != null) {
        return referer.startsWith("https://travelbuddy.com/");
    }
    
    // If both headers are missing, drop or handle cautiously
    return false;
}
</code></pre>
<h3 id="heading-limitations-of-originreferer-verification">Limitations of Origin/Referer Verification</h3>
<p>While checking <code>Origin</code> and <code>Referer</code> is lightweight and stateless, it has operational limitations:</p>
<ol>
<li><p><strong>Privacy stripping:</strong> Corporate proxies, privacy extensions, VPNs, and browser settings often strip <code>Referer</code> headers to protect user privacy.</p>
</li>
<li><p><strong>Missing</strong> <code>Origin</code> <strong>on certain requests:</strong> The <code>Origin</code> header is generally included on <code>POST</code>/<code>PUT</code>/<code>DELETE</code> requests, but may be omitted on cross-site <code>GET</code> navigations.</p>
</li>
<li><p><strong>Subdomain vulnerabilities:</strong> If an attacker compromises a separate application hosted on <code>blog.travelbuddy.com</code>, an origin check verifying <code>*.travelbuddy.com</code> might accept the forged request.</p>
</li>
</ol>
<h2 id="heading-jwt-and-csrf-the-token-storage-dilemma">JWT and CSRF: The Token Storage Dilemma</h2>
<p>One of the most heavily debated topics in modern architecture is: "Does using JSON Web Tokens (JWT) make my application immune to CSRF?"</p>
<p>The answer depends entirely on where and how the frontend application stores and sends the JWT.</p>
<p>Let's evaluate the two primary JWT storage strategies.</p>
<h3 id="heading-strategy-a-storing-jwt-in-localstorage-or-sessionstorage">Strategy A: Storing JWT in <code>localStorage</code> or <code>sessionStorage</code></h3>
<p>In this architecture, when Alice logs in, the backend returns a JWT in the JSON response body. The frontend JavaScript saves the JWT in Web Storage (<code>localStorage</code> or <code>sessionStorage</code>).</p>
<p>For every API request, JavaScript explicitly attaches the token as a Bearer token inside the <code>Authorization</code> HTTP header:</p>
<pre><code class="language-shell">POST /api/connections/add HTTP/1.1
Host: travelbuddy.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

{"service": "SkyScanner"}
</code></pre>
<h4 id="heading-is-strategy-a-vulnerable-to-csrf">Is Strategy A Vulnerable to CSRF?</h4>
<p>No: strategy A is completely immune to CSRF.</p>
<p>Why? Because the browser <strong>never automatically attaches</strong> <code>localStorage</code> <strong>items or</strong> <code>Authorization: Bearer</code> <strong>headers</strong> to outgoing requests.</p>
<p>If Alice visits <code>evil.com</code>, <code>evil.com</code> can send a request to <code>travelbuddy.com</code>. But because <code>evil.com</code> can't read Alice's <code>localStorage</code> (due to Same Origin Policy), it can't extract the JWT. And because the browser doesn't attach the <code>Authorization</code> header automatically, the forged request arrives at <code>TravelBuddy</code> without credentials and fails.</p>
<h4 id="heading-the-catch-xss-vulnerability">The Catch: XSS Vulnerability</h4>
<p>While Strategy A eliminates CSRF, it introduces a severe risk: <strong>Cross-Site Scripting (XSS)</strong>. Any third-party JavaScript library or injected XSS script running on <code>travelbuddy.com</code> can execute <code>localStorage.getItem('jwt')</code>, steal Alice's token, and send it to an attacker's command-and-control server. Once stolen, the token can be used from anywhere in the world.</p>
<h3 id="heading-strategy-b-storing-jwt-in-an-httponly-cookie">Strategy B: Storing JWT in an <code>HttpOnly</code> Cookie</h3>
<p>To protect JWTs from XSS theft, security engineers often store the JWT inside a <code>Set-Cookie</code> header marked with the <code>HttpOnly</code> flag:</p>
<pre><code class="language-shell">Set-Cookie: jwt_token=eyJhbGciOi...; Path=/; HttpOnly; Secure; SameSite=Lax
</code></pre>
<p>When marked <code>HttpOnly</code>, client-side JavaScript <strong>can't read or steal</strong> the cookie.</p>
<h4 id="heading-is-strategy-b-vulnerable-to-csrf">Is Strategy B Vulnerable to CSRF?</h4>
<p>Yes: strategy B is vulnerable to CSRF unless explicitly defended.</p>
<p>Why? Because the moment you put an authentication credential inside a Cookie, <strong>you re-introduce automatic cookie attachment.</strong> The browser treats a JWT cookie exactly like a session cookie.</p>
<p>If <code>evil.com</code> triggers a cross-site request to <code>travelbuddy.com</code>, the browser automatically attaches <code>Cookie: jwt_token=eyJhbGciOi...</code>.</p>
<h3 id="heading-summary-matrix-jwt-storage-trade-offs">Summary Matrix: JWT Storage Trade-offs</h3>
<table style="min-width:150px"><colgroup><col style="min-width:25px"><col style="min-width:25px"><col style="min-width:25px"><col style="min-width:25px"><col style="min-width:25px"><col style="min-width:25px"></colgroup><tbody><tr><td><p><strong>Storage Location</strong></p></td><td><p><strong>Transmitted Via</strong></p></td><td><p><strong>Automatic Browser Attachment?</strong></p></td><td><p><strong>CSRF Vulnerable?</strong></p></td><td><p><strong>XSS Vulnerable to Token Theft?</strong></p></td><td><p><strong>Primary Defenses Needed</strong></p></td></tr><tr><td><p><code>localStorage</code></p></td><td><p><code>Authorization: Bearer &lt;jwt&gt;</code> Header</p></td><td><p><strong>No</strong></p></td><td><p><strong>No</strong></p></td><td><p><strong>YES</strong></p></td><td><p>Strict Content Security Policy (CSP), Input Sanitization</p></td></tr><tr><td><p><code>HttpOnly</code><strong> Cookie</strong></p></td><td><p><code>Cookie: jwt=&lt;jwt&gt;</code> Header</p></td><td><p><strong>YES</strong></p></td><td><p><strong>YES</strong></p></td><td><p><strong>No</strong></p></td><td><p>CSRF Tokens OR <code>SameSite=Lax/Strict</code></p></td></tr></tbody></table>

<h2 id="heading-oauth-state-parameter-amp-login-csrf">OAuth State Parameter &amp; Login CSRF</h2>
<p>In the introduction, I mentioned that OAuth 2.0 uses a <code>state</code> parameter to protect against CSRF. Let's connect our understanding back to OAuth authentication flows and explore a specialized variant of CSRF called <strong>Login CSRF</strong>.</p>
<h3 id="heading-what-is-login-csrf">What is Login CSRF?</h3>
<p>In standard CSRF, the attacker tries to force a victim to perform an action inside the <em>victim's</em> account (for example, adding an integration to Alice's account).</p>
<p>In <strong>Login CSRF</strong>, the attacker tries to force the victim's browser to log into the <em>attacker's</em> account.</p>
<h4 id="heading-how-login-csrf-works">How Login CSRF Works</h4>
<p>First, the attacker logs into <code>TravelBuddy</code> and initiates an OAuth login flow (for example, "Sign in with Google").</p>
<p>Then Google redirects the attacker's browser back to <code>https://travelbuddy.com/login/oauth2/code/google?code=ATTACKER_AUTHORIZATION_CODE</code>.</p>
<p>The attacker <strong>intercepts and pauses</strong> this request before the code is exchanged, copying the redirect URL containing <code>code=ATTACKER_AUTHORIZATION_CODE</code>.</p>
<p>Next, the attacker crafts a link or malicious page on <code>evil.com</code> that forces Alice's browser to open that exact URL: <code>https://travelbuddy.com/login/oauth2/code/google?code=ATTACKER_AUTHORIZATION_CODE</code>.</p>
<p>Alice's browser executes the request. <code>TravelBuddy</code> takes <code>ATTACKER_AUTHORIZATION_CODE</code>, exchanges it with Google, and logs Alice's browser session into the <strong>Attacker's TravelBuddy account</strong>.</p>
<p>Then Alice, believing she's in her own account, enters sensitive travel data or attaches her credit card. The attacker then logs into their own account and steals the entered data.</p>
<h3 id="heading-how-the-oauth-state-parameter-prevents-login-csrf">How the OAuth <code>state</code> Parameter Prevents Login CSRF</h3>
<p>To prevent Login CSRF, OAuth 2.0 uses the <code>state</code> parameter, which acts as a CSRF token for authorization flows.</p>
<img src="https://cdn.hashnode.com/uploads/covers/61c1acb4a90dea775da8262b/16cc15d1-4ee6-4608-9395-0c7ca5235d81.png" alt="Sequence diagram illustrating OAuth 2.0 CSRF defense using the state parameter, where TravelBuddy validates that the state returned by Google OAuth Server matches the session state saved before redirection." style="display:block;margin:0 auto" width="2657" height="1376" loading="lazy">

<p>If an attacker tries to inject their authorization code into Alice's browser, the attacker's <code>state</code> parameter won't match the random <code>state</code> stored in Alice's session. <code>TravelBuddy</code> rejects the callback, stopping Login CSRF.</p>
<h3 id="heading-comparison-table-csrf-token-vs-oauth-state-vs-pkce">Comparison Table: CSRF Token vs OAuth State vs PKCE</h3>
<table style="min-width:100px"><colgroup><col style="min-width:25px"><col style="min-width:25px"><col style="min-width:25px"><col style="min-width:25px"></colgroup><tbody><tr><td><p><strong>Defense Mechanism</strong></p></td><td><p><strong>Primary Purpose</strong></p></td><td><p><strong>How It Works</strong></p></td><td><p><strong>Target Vulnerability</strong></p></td></tr><tr><td><p><strong>CSRF Token</strong></p></td><td><p>Protects standard web application state mutations.</p></td><td><p>Server issues random token to UI and verifies token on incoming POST requests.</p></td><td><p>CSRF on forms/APIs inside established sessions.</p></td></tr><tr><td><p><strong>OAuth </strong><code>state</code></p></td><td><p>Binds an OAuth authorization request to the user session that initiated it.</p></td><td><p>Client passes random state to Identity Provider (IdP); IdP returns state on callback redirect.</p></td><td><p>Login CSRF/Authorization Code Injection.</p></td></tr><tr><td><p><strong>PKCE</strong> (Proof Key for Code Exchange)</p></td><td><p>Prevents authorization code interception on public clients (mobile/SPA).</p></td><td><p>Client generates <code>code_verifier</code> and sends hashed <code>code_challenge</code> to IdP. Proves ownership during token exchange.</p></td><td><p>Authorization Code Interception on mobile/native apps.</p></td></tr></tbody></table>

<h2 id="heading-spring-security-csrf-internals">Spring Security CSRF Internals</h2>
<p>Now that you've learned these first principles (browser cookies, SOP, CORS, CSRF tokens, <code>SameSite</code>, and OAuth state) you're ready to look at how modern frameworks handle CSRF.</p>
<p>We'll analyze <strong>Spring Security</strong> (Spring Boot 3.x / 4 architecture, using Java 21).</p>
<h3 id="heading-the-mechanics-csrffilter">The Mechanics: <code>CsrfFilter</code></h3>
<p>Spring Security implements CSRF protection through an HTTP Filter inserted into its filter chain: <code>CsrfFilter</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/61c1acb4a90dea775da8262b/dc479386-e392-40f9-a234-869f153596e3.svg" alt="Flowchart showing the internal execution flow of Spring Security's CsrfFilter, validating safe HTTP methods and comparing request tokens against session tokens to either allow request passage or return HTTP 403 Forbidden." style="display:block;margin:0 auto" width="654.984375" height="1179.5625" loading="lazy">

<h3 id="heading-spring-security-csrf-key-architecture-components">Spring Security CSRF Key Architecture Components</h3>
<p>Spring Security decomposes CSRF responsibilities into clear interfaces:</p>
<ol>
<li><p><code>CsrfToken</code><strong>:</strong> An interface representing the token payload (contains <code>getHeaderName()</code>, <code>getParameterName()</code>, and <code>getToken()</code>).</p>
</li>
<li><p><code>CsrfTokenRepository</code><strong>:</strong> Responsible for generating, saving, and loading tokens.</p>
<ul>
<li><p><code>HttpSessionCsrfTokenRepository</code> (Default): Stores the CSRF token in the HTTP Session under a key.</p>
</li>
<li><p><code>CookieCsrfTokenRepository</code>: Stores the CSRF token in a cookie (for stateless/SPA applications).</p>
</li>
</ul>
</li>
<li><p><code>CsrfTokenRequestHandler</code><strong>:</strong> Handles making the token available to the UI template or parsing incoming headers/parameters.</p>
<ul>
<li>In modern Spring Security, <code>XorCsrfTokenRequestAttributeHandler</code> is used by default to protect against side-channel attacks like BREACH by masking tokens with a random XOR mask per request.</li>
</ul>
</li>
<li><p><strong>Deferred CSRF Tokens:</strong> Introduced in Spring Security 6, tokens are loaded <strong>deferred/lazily</strong>. Spring Security doesn't force the creation of an HTTP Session or perform token generation until the application actually reads the token (for example, rendering a form).</p>
</li>
</ol>
<h3 id="heading-modern-spring-security-configuration-spring-boot-3x-4">Modern Spring Security Configuration (Spring Boot 3.x / 4)</h3>
<p>Here's an enterprise-ready Spring Security configuration written in modern Java 21 DSL style:</p>
<pre><code class="language-java">package com.travelbuddy.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -&gt; auth
                .requestMatchers("/public/**", "/login", "/register").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(form -&gt; form
                .loginPage("/login")
                .defaultSuccessUrl("/dashboard", true)
            )
            // Configure CSRF explicitly using modern Lambda DSL
            .csrf(csrf -&gt; csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .csrfTokenRequestHandler(new XorCsrfTokenRequestAttributeHandler())
                .ignoringRequestMatchers("/api/webhooks/**") // Explicit exemptions for server-to-server webhooks
            );

        return http.build();
    }
}
</code></pre>
<p>This configuration uses Spring Security's modern <strong>SecurityFilterChain</strong> instead of the deprecated <code>WebSecurityConfigurerAdapter</code>. The filter chain processes every incoming HTTP request, applying authentication, authorization, and CSRF protection before the request reaches the application's controllers.</p>
<p>The <code>authorizeHttpRequests()</code> method defines the authorization rules. Public endpoints such as <code>/public/**</code>, <code>/login</code>, and <code>/register</code> are accessible without authentication, while all other requests require a logged-in user.</p>
<p>CSRF protection is enabled using <code>CookieCsrfTokenRepository.withHttpOnlyFalse()</code>, which stores the CSRF token in a cookie named <code>XSRF-TOKEN</code>. Because the cookie is readable by JavaScript, frontend frameworks such as React, Angular, or Vue can include the token in the <code>X-XSRF-TOKEN</code> request header. Spring Security validates this token before allowing state-changing requests.</p>
<p>The <code>XorCsrfTokenRequestAttributeHandler</code> further improves security by masking the CSRF token with a random XOR value on each response, helping protect against compression-based attacks such as BREACH. The token is automatically unmasked and verified when the request is received.</p>
<p>Finally, <code>ignoringRequestMatchers("/api/webhooks/**")</code> excludes webhook endpoints from CSRF validation because they receive requests from trusted external services rather than browser sessions. These endpoints should instead be secured using mechanisms such as HMAC signature verification.</p>
<h2 id="heading-implement-csrf-protection-yourself">Implement CSRF Protection Yourself</h2>
<p>To demystify Spring Security entirely, let's build our own lightweight, custom CSRF protection mechanism in raw Java 21 and Spring Boot without using Spring Security's <code>CsrfFilter</code>.</p>
<p>This hands-on exercise proves that security frameworks aren't magical: they're structured applications of web fundamentals.</p>
<h3 id="heading-step-1-create-a-custom-csrf-filter">Step 1: Create a Custom CSRF Filter</h3>
<pre><code class="language-java">package com.travelbuddy.security;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Set;

@Component
public class CustomCsrfFilter extends OncePerRequestFilter {

    private static final String CSRF_SESSION_ATTRIBUTE = "CUSTOM_CSRF_TOKEN";
    private static final String CSRF_PARAM_NAME = "_csrf";
    private static final String CSRF_HEADER_NAME = "X-CSRF-TOKEN";
    
    // Define safe HTTP methods that do not modify state
    private static final Set&lt;String&gt; SAFE_METHODS = Set.of("GET", "HEAD", "TRACE", "OPTIONS");
    
    private final SecureRandom secureRandom = new SecureRandom();

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {

        HttpSession session = request.getSession(true);

        // 1. Ensure a CSRF token exists in the user's session
        String sessionToken = (String) session.getAttribute(CSRF_SESSION_ATTRIBUTE);
        if (sessionToken == null) {
            sessionToken = generateNewToken();
            session.setAttribute(CSRF_SESSION_ATTRIBUTE, sessionToken);
        }

        // Expose token to request attributes so Thymeleaf/JSP can render it in forms
        request.setAttribute("csrfToken", sessionToken);

        // 2. Check if the incoming request method is SAFE
        if (SAFE_METHODS.contains(request.getMethod())) {
            // Safe request: Allow execution to proceed
            filterChain.doFilter(request, response);
            return;
        }

        // 3. Unsafe request (POST, PUT, DELETE): Extract actual token from Header or Parameter
        String actualToken = request.getHeader(CSRF_HEADER_NAME);
        if (actualToken == null || actualToken.isBlank()) {
            actualToken = request.getParameter(CSRF_PARAM_NAME);
        }

        // 4. Validate Token
        if (actualToken != null &amp;&amp; actualToken.equals(sessionToken)) {
            // Token matches! Proceed to controller handler
            filterChain.doFilter(request, response);
        } else {
            // Token missing or mismatched! Reject forged request
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.setContentType("application/json");
            response.getWriter().write("""
                {
                    "error": "Forbidden",
                    "message": "Custom CSRF Filter: Invalid or missing CSRF token."
                }
                """);
        }
    }

    private String generateNewToken() {
        byte[] randomBytes = new byte[32];
        secureRandom.nextBytes(randomBytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes);
    }
}
</code></pre>
<p>The <code>CustomCsrfFilter</code> extends Spring's <code>OncePerRequestFilter</code>, ensuring the filter executes only once for each HTTP request. When a request arrives, it checks the user's session for a CSRF token. If no token exists, a new 256-bit cryptographically secure random token is generated using <code>SecureRandom</code> and stored in the session.</p>
<p>The filter then exposes the token as a request attribute using <code>request.setAttribute("csrfToken", sessionToken)</code>, allowing server-side template engines such as Thymeleaf to include it in hidden form fields. For safe HTTP methods (<code>GET</code>, <code>HEAD</code>, <code>OPTIONS</code>, and <code>TRACE</code>), the filter skips CSRF validation and immediately passes the request to the next filter since these methods shouldn't modify server state.</p>
<p>For state-changing requests such as <code>POST</code>, <code>PUT</code>, and <code>DELETE</code>, the filter retrieves the submitted CSRF token from either the <code>X-CSRF-TOKEN</code> request header (used by JavaScript clients) or the <code>_csrf</code> form parameter (used by HTML forms). It then compares this value with the token stored in the user's session. If the tokens match, the request proceeds normally. If the token is missing or invalid, the filter blocks the request by returning an <strong>HTTP 403 Forbidden</strong> response with a JSON error message.</p>
<h3 id="heading-step-2-register-the-custom-filter">Step 2: Register the Custom Filter</h3>
<pre><code class="language-java">package com.travelbuddy.config;

import com.travelbuddy.security.CustomCsrfFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WebFilterConfig {

    @Bean
    public FilterRegistrationBean&lt;CustomCsrfFilter&gt; loggingFilter(CustomCsrfFilter filter) {
        FilterRegistrationBean&lt;CustomCsrfFilter&gt; registrationBean = new FilterRegistrationBean&lt;&gt;();
        registrationBean.setFilter(filter);
        registrationBean.addUrlPatterns("/api/*"); // Protect API endpoints
        return registrationBean;
    }
}
</code></pre>
<p>The <code>WebFilterConfig</code> class registers the custom <code>CustomCsrfFilter</code> using Spring Boot's <code>FilterRegistrationBean</code>, allowing the filter to be added to the underlying Servlet container without relying on Spring Security's filter chain. The <code>setFilter(filter)</code> method attaches the <code>CustomCsrfFilter</code> instance to the registration, while <code>addUrlPatterns("/api/*")</code> limits its execution to requests targeting <code>/api/*</code> endpoints. As a result, only API requests pass through the custom CSRF validation before reaching the application's <code>@RestController</code> methods.</p>
<h3 id="heading-compare-custom-filter-vs-spring-securitys-csrffilter">Compare Custom Filter vs. Spring Security's <code>CsrfFilter</code></h3>
<table style="min-width:75px"><colgroup><col style="min-width:25px"><col style="min-width:25px"><col style="min-width:25px"></colgroup><tbody><tr><td><p><strong>Feature</strong></p></td><td><p><strong>Our Custom Filter</strong></p></td><td><p><strong>Spring Security CsrfFilter</strong></p></td></tr><tr><td><p><strong>Token Generation</strong></p></td><td><p>Basic <code>SecureRandom</code> Base64 string</p></td><td><p>Cryptographically secure UUID / Custom generators</p></td></tr><tr><td><p><strong>BREACH Defense</strong></p></td><td><p>None (Raw token matching)</p></td><td><p>Masked Tokens (<code>XorCsrfTokenRequestAttributeHandler</code>)</p></td></tr><tr><td><p><strong>Storage Strategy</strong></p></td><td><p>Fixed <code>HttpSession</code></p></td><td><p>Pluggable (<code>HttpSession</code>, Cookie, Custom Repositories)</p></td></tr><tr><td><p><strong>Performance</strong></p></td><td><p>Immediate session creation</p></td><td><p>Lazy / Deferred token generation (Spring Security 6+)</p></td></tr><tr><td><p><strong>SPA Integration</strong></p></td><td><p>Manual header handling</p></td><td><p>Built-in <code>CookieCsrfTokenRepository</code></p></td></tr></tbody></table>

<p>Building this filter manually shows that Spring Security isn't magic. It performs the exact steps we built: checking HTTP methods, extracting tokens, and comparing request attributes against stored session state.</p>
<h2 id="heading-testing-csrf-protections">Testing CSRF Protections</h2>
<p>To verify that CSRF defenses are working correctly, you should know how to inspect, attack, and test your applications using various tools.</p>
<h3 id="heading-1-browser-devtools-inspection">1. Browser DevTools Inspection</h3>
<p>Open Chrome or Firefox DevTools (<code>F12</code>), navigate to the <strong>Application</strong> tab, and select <strong>Cookies</strong>:</p>
<ul>
<li><p>Inspect <code>JSESSIONID</code>: Verify that <code>HttpOnly</code> and <code>Secure</code> flags are set.</p>
</li>
<li><p>Inspect <code>SameSite</code> column: Verify whether <code>Lax</code> or <code>Strict</code> is active.</p>
</li>
</ul>
<p>In the <strong>Network</strong> tab, inspect a submitted <code>POST</code> request payload:</p>
<ul>
<li>Look for <code>_csrf</code> under Form Data, or <code>X-XSRF-TOKEN</code> under Request Headers.</li>
</ul>
<h3 id="heading-2-testing-via-curl">2. Testing via <code>curl</code></h3>
<p>Let's attempt a forged request using command-line <code>curl</code>.</p>
<h4 id="heading-test-attempt-a-submit-post-without-csrf-token-simulating-attacker">Test Attempt A: Submit POST without CSRF Token (Simulating Attacker)</h4>
<pre><code class="language-shell">curl -i -X POST https://travelbuddy.com/api/connections/add \
     -H "Cookie: JSESSIONID=abc123xyz789" \
     -d "service=SkyScanner"
</code></pre>
<p>Expected Response:</p>
<pre><code class="language-shell">HTTP/1.1 403 Forbidden
Content-Type: application/json

{"error":"Forbidden","message":"Invalid CSRF Token"}
</code></pre>
<h4 id="heading-test-attempt-b-fetch-token-and-submit-valid-request-legitimate-client-flow">Test Attempt B: Fetch Token and Submit Valid Request (Legitimate Client Flow)</h4>
<pre><code class="language-shell"># Step 1: Fetch session cookie and CSRF token from page
curl -i -c cookies.txt https://travelbuddy.com/connect-service

# Step 2: Extract token value from HTML, then submit POST request with Cookie + Token
curl -i -b cookies.txt -X POST https://travelbuddy.com/api/connections/add \
     -H "X-CSRF-TOKEN: CSRF-KEY-998877" \
     -d "service=SkyScanner"
</code></pre>
<p>Expected Response:</p>
<pre><code class="language-shell">HTTP/1.1 200 OK
Content-Type: application/json

{"status":"success","message":"Service connected successfully"}
</code></pre>
<h3 id="heading-3-why-postman-can-mislead-developers">3. Why Postman Can Mislead Developers</h3>
<p>Developers frequently report: <em>"I enabled CSRF protection in Spring Boot, but when I test my POST request in Postman, it succeeds without sending a CSRF token! Why?"</em></p>
<p>Postman is an API client, <strong>not a web browser</strong>. When you run a request in Postman, Postman doesn't maintain a cross-site sandbox, nor does it enforce Same Origin Policy or automatic ambient cookie injection unless explicitly configured.</p>
<p>If you don't manually attach a session cookie in Postman, the backend treats the Postman request as unauthenticated. If you use Postman's Interceptor cookie sync, Postman acts like a client explicitly sending parameters. Postman tests API contracts, but it doesn't simulate the browser's ambient authorization rules.</p>
<h3 id="heading-4-automated-integration-testing-with-spring-security-test">4. Automated Integration Testing with Spring Security Test</h3>
<p>In Java unit/integration tests, Spring Security provides test mock builders to simulate CSRF tokens effortlessly:</p>
<pre><code class="language-java">package com.travelbuddy.controller;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
class ConnectionControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithMockUser(username = "alice")
    void addConnection_WithoutCsrf_ShouldReturn403Forbidden() throws Exception {
        mockMvc.perform(post("/api/connections/add")
                .param("service", "SkyScanner"))
                .andExpect(status().isForbidden());
    }

    @Test
    @WithMockUser(username = "alice")
    void addConnection_WithCsrf_ShouldSucceed() throws Exception {
        mockMvc.perform(post("/api/connections/add")
                .param("service", "SkyScanner")
                .with(csrf())) // Injects a valid mock CSRF token into request
                .andExpect(status().isOk());
    }
}
</code></pre>
<h2 id="heading-common-misconceptions">Common Misconceptions</h2>
<p>Let's dispel the seven most persistent myths surrounding CSRF.</p>
<h3 id="heading-myth-1-csrf-and-xss-are-the-same-thing">Myth 1: "CSRF and XSS are the same thing."</h3>
<p><strong>Fact:</strong> CSRF and XSS are completely different vulnerability vectors with opposite mechanisms:</p>
<ul>
<li><p><strong>XSS (Cross-Site Scripting):</strong> Attacker injects malicious JavaScript <em>into</em> your site to execute scripts inside your origin (stealing data, reading DOM, extracting local storage).</p>
</li>
<li><p><strong>CSRF (Cross-Site Request Forgery):</strong> Attacker tricks a victim's browser <em>on a different origin</em> into sending an HTTP request to your site. The attacker cannot read your site's DOM or steal cookies.</p>
</li>
</ul>
<h3 id="heading-myth-2-https-prevents-csrf-attacks">Myth 2: "HTTPS prevents CSRF attacks."</h3>
<p><strong>Fact:</strong> HTTPS encrypts the transport channel between the browser and server. It prevents wiretapping and man-in-the-middle attacks. But in a CSRF attack, the browser itself sends encrypted, valid HTTPS requests. Encrypting the pipe doesn't stop the browser from sending a forged request down that pipe.</p>
<h3 id="heading-myth-3-our-app-requires-authentication-so-were-safe-from-csrf">Myth 3: "Our app requires authentication, so we're safe from CSRF."</h3>
<p><strong>Fact:</strong> Authentication is what <strong>enables</strong> CSRF. CSRF specifically targets authenticated users because the browser automatically attaches their authenticated session cookies.</p>
<h3 id="heading-myth-4-our-api-uses-jwts-so-we-dont-have-to-worry-about-csrf">Myth 4: "Our API uses JWTs, so we don't have to worry about CSRF."</h3>
<p><strong>Fact:</strong> If your JWT is stored in an <code>HttpOnly</code> Cookie, you're fully vulnerable to CSRF because cookies are attached automatically. CSRF is a function of credential transmission mechanism (cookies), not credential payload structure (JWT vs Session ID).</p>
<h3 id="heading-myth-5-cors-blocks-cross-site-attacks">Myth 5: "CORS blocks cross-site attacks."</h3>
<p><strong>Fact:</strong> CORS controls response reading, not request execution. Simple requests (<code>application/x-www-form-urlencoded</code> HTML forms) execute state modifications on the backend long before CORS checks evaluate response headers.</p>
<h3 id="heading-myth-6-samesitelax-makes-csrf-tokens-obsolete">Myth 6: "SameSite=Lax makes CSRF tokens obsolete."</h3>
<p><strong>Fact:</strong> <code>SameSite=Lax</code> is an excellent defense, but top-level GET navigations still carry cookies, legacy browsers don't support it properly, and edge-case refresh windows exist. CSRF tokens remain necessary as defense-in-depth.</p>
<h3 id="heading-myth-7-attackers-can-read-our-csrf-token-from-the-html-form">Myth 7: "Attackers can read our CSRF token from the HTML form."</h3>
<p><strong>Fact:</strong> Same Origin Policy (SOP) strictly prevents JavaScript running on <code>evil.com</code> from fetching and reading HTML DOM nodes rendered from <code>travelbuddy.com</code>.</p>
<h2 id="heading-production-best-practices-checklist">Production Best Practices Checklist</h2>
<p>When deploying Spring Boot applications to production, follow this architectural security checklist:</p>
<h3 id="heading-1-identify-your-architecture-type">1. Identify Your Architecture Type</h3>
<ul>
<li><p><strong>Monolithic HTML Rendering (Thymeleaf, JSP):</strong> Use Synchronizer Token Pattern stored in <code>HttpSession</code>. Ensure all HTML forms include <code>_csrf</code> hidden fields.</p>
</li>
<li><p><strong>Single Page Application (React/Angular + Spring Boot API):</strong> Use Double Submit Cookie pattern (<code>CookieCsrfTokenRepository.withHttpOnlyFalse()</code>) combined with custom frontend request interceptors.</p>
</li>
<li><p><strong>Stateless Pure REST API (Machine-to-Machine / Native Mobile Apps using</strong> <code>Authorization: Bearer</code> <strong>headers):</strong> Disable CSRF (<code>.csrf(csrf -&gt; csrf.disable())</code>), because clients explicitly manage non-cookie tokens.</p>
</li>
</ul>
<h3 id="heading-2-cookie-security-flags">2. Cookie Security Flags</h3>
<p>Ensure every authentication cookie sets these attributes:</p>
<ul>
<li><p><code>Secure</code> = <code>true</code> (HTTPS only)</p>
</li>
<li><p><code>HttpOnly</code> = <code>true</code> (Prevents XSS token theft)</p>
</li>
<li><p><code>SameSite</code> = <code>Lax</code> or <code>Strict</code> (Browser-native cross-site blocking)</p>
</li>
</ul>
<h3 id="heading-3-keep-get-requests-read-only">3. Keep GET Requests Read-Only</h3>
<p>Audit your codebase to ensure no <code>@GetMapping</code> or <code>HttpServletRequest.getMethod().equals("GET")</code> handles database updates, account deletions, or password resets.</p>
<h3 id="heading-4-cross-origin-defense-layers">4. Cross-Origin Defense Layers</h3>
<p>Implement strict <code>Origin</code> and <code>Referer</code> header validation filters on state-modifying endpoints.</p>
<p>Also, deploy a robust Content Security Policy (CSP) header to reduce XSS risk (since XSS can be used to bypass CSRF defenses).</p>
<h3 id="heading-5-webhooks-and-external-callbacks">5. Webhooks and External Callbacks</h3>
<p>For server-to-server endpoints (such as Stripe or GitHub webhooks):</p>
<ul>
<li><p>Explicitly exempt webhook endpoints from standard CSRF filters in Spring Security (<code>ignoringRequestMatchers("/api/webhooks/**")</code>).</p>
</li>
<li><p>Secure webhooks using <strong>HMAC Signature Verification</strong> (<code>X-Hub-Signature-256</code>) instead of session cookies.</p>
</li>
</ul>
<h2 id="heading-final-summary-amp-defense-matrix">Final Summary &amp; Defense Matrix</h2>
<p>Cross-Site Request Forgery (CSRF) isn't a bug in browser design. It's an unintended consequence of web convenience: <strong>browsers automatically attach stored domain cookies to every outgoing request.</strong></p>
<p>When an attacker tricks a user into visiting a malicious origin (<code>evil.com</code>), the attacker relies on the browser's ambient authority to attach authenticated session credentials to a forged, state-changing request targeting your application (<code>travelbuddy.com</code>).</p>
<p>To prevent CSRF, modern web applications employ multi-layered security defenses working in tandem:</p>
<h3 id="heading-comprehensive-defense-matrix">Comprehensive Defense Matrix</h3>
<table style="min-width:125px"><colgroup><col style="min-width:25px"><col style="min-width:25px"><col style="min-width:25px"><col style="min-width:25px"><col style="min-width:25px"></colgroup><tbody><tr><td><p><strong>Defense Mechanism</strong></p></td><td><p><strong>Mechanism Layer</strong></p></td><td><p><strong>Primary Target / Action</strong></p></td><td><p><strong>Advantages</strong></p></td><td><p><strong>Limitations</strong></p></td></tr><tr><td><p><strong>Synchronizer Token Pattern</strong></p></td><td><p>Application Server</p></td><td><p>Binds unpredictable random token to server session. Verifies hidden form parameter.</p></td><td><p>Cryptographically bulletproof. Complete protection against cross-site forged requests.</p></td><td><p>Requires server-side session state (or state management).</p></td></tr><tr><td><p><strong>Double Submit Cookie Pattern</strong></p></td><td><p>Client + Server</p></td><td><p>Cookie value copied into custom HTTP header by JS. Verified server-side.</p></td><td><p>Fully stateless; ideal for SPAs (React/Angular) and microservices.</p></td><td><p>Requires non-HttpOnly cookie readable by JS. Vulnerable if subdomains are compromised.</p></td></tr><tr><td><p><code>SameSite=Lax / Strict</code><strong> Cookies</strong></p></td><td><p>Browser Engine</p></td><td><p>Instructs browser to strip cookies from cross-site requests.</p></td><td><p>Native browser enforcement. Zero server token storage required.</p></td><td><p>Legacy browser gaps. Doesn't protect state-modifying <code>GET</code> operations.</p></td></tr><tr><td><p><code>Origin</code><strong> / </strong><code>Referer</code><strong> Validation</strong></p></td><td><p>Application / Gateway</p></td><td><p>Checks incoming source headers against known server origins.</p></td><td><p>Stateless and extremely fast execution.</p></td><td><p>Headers can be stripped by privacy software/proxies.</p></td></tr><tr><td><p><strong>Bearer Tokens (</strong><code>Authorization</code><strong> Header)</strong></p></td><td><p>API Client</p></td><td><p>Token stored in <code>localStorage</code>. Attached explicitly via JS headers.</p></td><td><p>Completely immune to CSRF (no automatic browser attachment).</p></td><td><p>High risk of XSS token theft if <code>localStorage</code> is accessed by malicious scripts.</p></td></tr></tbody></table>

<p>By mastering these fundamental concepts (how browsers handle cookies, how origins operate, and how frameworks implement token validation) you can build backend architectures that are secure by design.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Cookies to Customize a Web Page's Content ]]>
                </title>
                <description>
                    <![CDATA[ If you develop websites or web apps, someday you’ll have to deal with cookies. That’s why I decided to write this tutorial on how to use cookies to customize a web page according to the previous web page the user comes from.  I wrote this tutorial us... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-cookies-to-customize-web-page-content/</link>
                <guid isPermaLink="false">66bdff670b4523e3b8b99097</guid>
                
                    <category>
                        <![CDATA[ cookies ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Marco Venturi ]]>
                </dc:creator>
                <pubDate>Tue, 30 May 2023 22:04:35 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/05/vyshnavi-bisani-z8kriatLFdA-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you develop websites or web apps, someday you’ll have to deal with cookies. That’s why I decided to write this tutorial on how to use cookies to customize a web page according to the previous web page the user comes from. </p>
<p>I wrote this tutorial using PHP, but you can set cookies also by using other popular programming languages such as Java, Python, and others.</p>
<p>Before going deep with the details, let's go through a brief introduction and some recommendations.</p>
<h2 id="heading-what-are-cookies">What are Cookies?</h2>
<p>Cookies play a vital role in how websites function. They can also enhance a user's browsing experience. </p>
<p>In simple terms, a cookie is a small file that websites store on a user's computer or device while they navigate through various web pages. These files contain data that are utilized by websites to remember certain information and settings, ultimately improving the performance and customization of the website for the user.</p>
<p>When a user visits a website, the site's server sends a cookie to the user's browser, which then stores it on their computer or device. The next time the user visits the same website, the browser sends the stored cookie back to the server. This enables the website to recognize the user, remember their preferences, and provide personalized content.</p>
<p>Cookies serve various functions, such as remembering login information, language preferences, and shopping cart contents. </p>
<p>For example, when you visit an online shopping website and add items to your cart, cookies help in retaining those items even if you navigate to other pages. Cookies can also remember your login details, so you don't have to re-enter them every time you visit a website.</p>
<p>Cookies can also be used for tracking user behavior and gathering information about website usage. This information is often anonymous and helps website owners analyze traffic patterns, identify popular pages, and improve their website's design and functionality. </p>
<p>Advertisers also use cookies to deliver targeted advertisements based on users' browsing habits and interests. This enables them to show relevant ads that are more likely to be of interest to the user.</p>
<h3 id="heading-a-note-about-cookies-and-user-privacy">A Note about Cookies and User Privacy</h3>
<p>Cookies are designed to be a tool for enhancing user experience and improving website functionality. But concerns about privacy and security have led to the development of regulations and guidelines for using cookies. Many websites now provide cookie consent notices, allowing users to choose whether they want to accept or reject cookies.</p>
<p>Over the past few decades, the use of cookies has been subject to extensive discussion by regulatory bodies, emphasizing the significance of ensuring users are fully informed about their implementation. </p>
<p>Progress has been made in this direction, including the introduction of the General Data Protection Regulation (GDPR) by the European Union (EU). For more comprehensive information, you can get detailed insights from the official web portal of the EU. </p>
<p>If you are considering the integration of cookies into your application, I highly recommend discussing the implications with the legal department of your company or consulting with legal professionals who possess expertise in this domain. By doing so, you can ensure compliance with the legal and regulatory frameworks governing the use of cookies, safeguarding user privacy in the process.</p>
<h2 id="heading-lets-get-started">Let’s Get Started</h2>
<p>Let’s assume I’m running a pet lovers e-commerce site, and I’m implementing a content marketing strategy to attract new customers. </p>
<p>I create one informational page for cats lovers and another one for dogs lovers. Both pages point to the same page where I give further details about having pets. </p>
<p>I want this page to show specific (targeted) ads according to the page the user comes from: if they visited the page about cats, I want them to see ads about cat food. If they visited the one about dogs, I want them to see ads about dog food.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/image-124.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-lets-code">Let’s Code</h2>
<p>I’m building three pages:</p>
<ol>
<li>Cat's lovers page: mainPageCat.php (screenshot below)</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/image-125.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="2">
<li>Dog's lover page: mainPageDog.php</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/image-126.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="3">
<li>The target page: cookieTest.php</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/image-127.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>While building pages 1 and 2, I set cookies using the PHP <code>setcookie()</code> function. For the page about cats, I pass the function these parameters:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
$cookie_name = <span class="hljs-string">"cat"</span>;
$cookie_value = <span class="hljs-string">"catFoodAds"</span>;
setcookie($cookie_name, $cookie_value, time() + (<span class="hljs-number">86400</span> * <span class="hljs-number">30</span>), <span class="hljs-string">"/"</span>); <span class="hljs-comment">// 86400 = 1 day</span>
<span class="hljs-meta">?&gt;</span>
</code></pre>
<p>For the page about dogs, I pass these parameters:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
$cookie_name = <span class="hljs-string">"dog"</span>;
$cookie_value = <span class="hljs-string">"dogFoodAds"</span>;
setcookie($cookie_name, $cookie_value, time() + (<span class="hljs-number">86400</span> * <span class="hljs-number">30</span>), <span class="hljs-string">"/"</span>); <span class="hljs-comment">// 86400 = 1 day</span>
<span class="hljs-meta">?&gt;</span>
</code></pre>
<p>For the “further information” page I add some logic. If the cat cookie is stored, I add a CSS class to the dog ads card to hide it. I do the same with the cat ads card if the cookie stored is the one from the dog page.</p>
<pre><code class="lang-php">&lt;div <span class="hljs-class"><span class="hljs-keyword">class</span>="<span class="hljs-title">row</span>"&gt;
            &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">col</span>-<span class="hljs-title">md</span>-3"&gt;
                &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">card</span> &lt;?<span class="hljs-title">php</span> <span class="hljs-title">if</span>(<span class="hljs-title">isset</span>($<span class="hljs-title">_COOKIE</span>['<span class="hljs-title">dog</span>'])) <span class="hljs-title">echo</span> ' <span class="hljs-title">cookieClass</span>'; ?&gt;" <span class="hljs-title">style</span>="<span class="hljs-title">width</span>: 18<span class="hljs-title">rem</span>;"&gt;
                    &lt;<span class="hljs-title">img</span> <span class="hljs-title">class</span>="<span class="hljs-title">card</span>-<span class="hljs-title">img</span>-<span class="hljs-title">top</span>" <span class="hljs-title">src</span>="<span class="hljs-title">https</span>://<span class="hljs-title">images</span>.<span class="hljs-title">unsplash</span>.<span class="hljs-title">com</span>/<span class="hljs-title">photo</span>-1518791841217-8<span class="hljs-title">f162f1e1131</span>?<span class="hljs-title">ixlib</span>=<span class="hljs-title">rb</span>-1.2.1&amp;<span class="hljs-title">ixid</span>=<span class="hljs-title">MnwxMjA3fDB8MHxwaG90by1yZWxhdGVkfDl8fHxlbnwwfHx8fA</span>%3<span class="hljs-title">D</span>%3<span class="hljs-title">D</span>&amp;<span class="hljs-title">w</span>=1000&amp;<span class="hljs-title">q</span>=80" <span class="hljs-title">alt</span>="<span class="hljs-title">Card</span> <span class="hljs-title">image</span> <span class="hljs-title">cap</span>"&gt;
                    &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">card</span>-<span class="hljs-title">body</span>"&gt;
                        &lt;<span class="hljs-title">h5</span> <span class="hljs-title">class</span>="<span class="hljs-title">card</span>-<span class="hljs-title">title</span>"&gt;<span class="hljs-title">Buy</span> <span class="hljs-title">Food</span> <span class="hljs-title">for</span> <span class="hljs-title">Cats</span>&lt;/<span class="hljs-title">h5</span>&gt;
                        &lt;<span class="hljs-title">h6</span> <span class="hljs-title">class</span>="<span class="hljs-title">card</span>-<span class="hljs-title">subtitle</span> <span class="hljs-title">mb</span>-2 <span class="hljs-title">text</span>-<span class="hljs-title">muted</span>"&gt;<span class="hljs-title">Excellent</span> <span class="hljs-title">Food</span>&lt;/<span class="hljs-title">h6</span>&gt;
                        &lt;<span class="hljs-title">p</span> <span class="hljs-title">class</span>="<span class="hljs-title">card</span>-<span class="hljs-title">text</span>"&gt;<span class="hljs-title">Don</span>'<span class="hljs-title">t</span> <span class="hljs-title">know</span> <span class="hljs-title">what</span> <span class="hljs-title">else</span> <span class="hljs-title">I</span> <span class="hljs-title">could</span> <span class="hljs-title">say</span> <span class="hljs-title">about</span> <span class="hljs-title">cat</span> <span class="hljs-title">food</span>.&lt;/<span class="hljs-title">p</span>&gt;
                        &lt;<span class="hljs-title">a</span> <span class="hljs-title">href</span>="#" <span class="hljs-title">class</span>="<span class="hljs-title">card</span>-<span class="hljs-title">link</span>"&gt;<span class="hljs-title">Buy</span>&lt;/<span class="hljs-title">a</span>&gt;
                    &lt;/<span class="hljs-title">div</span>&gt;
                &lt;/<span class="hljs-title">div</span>&gt;
            &lt;/<span class="hljs-title">div</span>&gt;
            &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">col</span>-<span class="hljs-title">md</span>-3"&gt;
                &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">card</span> <span class="hljs-title">ml</span>-5 &lt;?<span class="hljs-title">php</span> <span class="hljs-title">if</span>(<span class="hljs-title">isset</span>($<span class="hljs-title">_COOKIE</span>['<span class="hljs-title">cat</span>'])) <span class="hljs-title">echo</span> ' <span class="hljs-title">cookieClass</span>'; ?&gt;" <span class="hljs-title">style</span>="<span class="hljs-title">width</span>: 18<span class="hljs-title">rem</span>;"&gt;
                    &lt;<span class="hljs-title">img</span> <span class="hljs-title">class</span>="<span class="hljs-title">card</span>-<span class="hljs-title">img</span>-<span class="hljs-title">top</span>" <span class="hljs-title">src</span>="<span class="hljs-title">https</span>://<span class="hljs-title">images</span>.<span class="hljs-title">unsplash</span>.<span class="hljs-title">com</span>/<span class="hljs-title">photo</span>-1561037404-61<span class="hljs-title">cd46aa615b</span>?<span class="hljs-title">ixlib</span>=<span class="hljs-title">rb</span>-1.2.1&amp;<span class="hljs-title">ixid</span>=<span class="hljs-title">MnwxMjA3fDB8MHxjb2xsZWN0aW9uLXBhZ2V8MXwxMTU1Mjc2N3x8ZW58MHx8fHw</span>%3<span class="hljs-title">D</span>&amp;<span class="hljs-title">w</span>=1000&amp;<span class="hljs-title">q</span>=80" <span class="hljs-title">alt</span>="<span class="hljs-title">Card</span> <span class="hljs-title">image</span> <span class="hljs-title">cap</span>"&gt;    
                    &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">card</span>-<span class="hljs-title">body</span>"&gt;
                        &lt;<span class="hljs-title">h5</span> <span class="hljs-title">class</span>="<span class="hljs-title">card</span>-<span class="hljs-title">title</span>"&gt;<span class="hljs-title">Buy</span> <span class="hljs-title">Food</span> <span class="hljs-title">for</span> <span class="hljs-title">Dogs</span>&lt;/<span class="hljs-title">h5</span>&gt;
                        &lt;<span class="hljs-title">h6</span> <span class="hljs-title">class</span>="<span class="hljs-title">card</span>-<span class="hljs-title">subtitle</span> <span class="hljs-title">mb</span>-2 <span class="hljs-title">text</span>-<span class="hljs-title">muted</span>"&gt;<span class="hljs-title">Excellent</span> <span class="hljs-title">Food</span>&lt;/<span class="hljs-title">h6</span>&gt;
                        &lt;<span class="hljs-title">p</span> <span class="hljs-title">class</span>="<span class="hljs-title">card</span>-<span class="hljs-title">text</span>"&gt;<span class="hljs-title">Don</span>'<span class="hljs-title">t</span> <span class="hljs-title">know</span> <span class="hljs-title">what</span> <span class="hljs-title">else</span> <span class="hljs-title">I</span> <span class="hljs-title">could</span> <span class="hljs-title">say</span> <span class="hljs-title">about</span> <span class="hljs-title">dog</span> <span class="hljs-title">food</span>.&lt;/<span class="hljs-title">p</span>&gt;
                        &lt;<span class="hljs-title">a</span> <span class="hljs-title">href</span>="#" <span class="hljs-title">class</span>="<span class="hljs-title">card</span>-<span class="hljs-title">link</span>"&gt;<span class="hljs-title">Buy</span>&lt;/<span class="hljs-title">a</span>&gt;
                    &lt;/<span class="hljs-title">div</span>&gt;
                &lt;/<span class="hljs-title">div</span>&gt;
            &lt;/<span class="hljs-title">div</span>&gt;
        &lt;/<span class="hljs-title">div</span>&gt;</span>
</code></pre>
<h2 id="heading-lets-see-how-it-works">Let’s see how it works</h2>
<p>I test the flow as a user who wants to visit the page about cats. I type in my browser URL bar:</p>
<p>https:///mainPageCat.php</p>
<p>As you can see, I see the page I built and the cookie is stored in my browser</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/image-128.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If I click the call to action (blue button), I see the further details page with cat food ads only:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/image-129.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Let’s now test the flow for dog lovers. First I delete cookies from my browser (or use the incognito mode) and then I visit this URL:</p>
<p>https:///mainPageDog.php</p>
<p>This is what I see:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/image-130.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>As we can see again, I see the page I built and the cookie is stored in my browser</p>
<p>If I click the call to action (blue button), I see the further details page with dog food ads only.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/image-131.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Done! This is a simple and quick example of how you can use cookies to customize the content of your web pages. You can find the Github repo <a target="_blank" href="https://github.com/mventuri/cookiesPhp">here</a> with the full code.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Everything You Need to Know About Cookies for Web Development ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever wondered how you can sign in to a website once and remain signed in, even if you close your browser? Or added an item to your shopping cart without signing in at all? Whether you know it or not, cookies are everywhere, and for better or... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/everything-you-need-to-know-about-cookies-for-web-development/</link>
                <guid isPermaLink="false">66ac87f29c95a40246abe1b4</guid>
                
                    <category>
                        <![CDATA[ cookies ]]>
                    </category>
                
                    <category>
                        <![CDATA[ web ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kristofer Koishigawa ]]>
                </dc:creator>
                <pubDate>Wed, 03 Feb 2021 06:14:00 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/602cb40c0a2838549dcc6af3.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever wondered how you can sign in to a website once and remain signed in, even if you close your browser? Or added an item to your shopping cart without signing in at all?</p>
<p>Whether you know it or not, cookies are everywhere, and for better or worse, they completely changed the way we use the web.</p>
<p>In this article, we'll go over the history of cookies, how they work, how to use them in JavaScript, and some security concerns to keep in mind.</p>
<h2 id="heading-a-brief-history-of-cookies">A brief history of cookies</h2>
<p>HTTP, or the Hypertext Transfer Protocol, is a stateless protocol. According to Wikipedia, its a stateless protocol because it "does not require the HTTP server to retain information or status about each user for the duration of multiple requests."</p>
<p>You can still see this today with simple websites – you type in the URL to the browser, the browser makes a request to a server somewhere, and the server returns the files to render the page and the connection is closed.</p>
<p>Now imagine that you need to sign in to a website to see certain content, like with LinkedIn. The process is largely the same as the one above, but you're presented with a form to enter in your email address and password.</p>
<p>You enter that information in and your browser sends it to the server. The server checks your login information, and if everything looks good, it sends the data needed to render the page back to your browser.</p>
<p>But if LinkedIn was truly stateless, once you navigate to a different page, the server would not remember that you just signed in. It would ask you to enter in your email address and password again, check them, then send over the data to render the new page.</p>
<p>That would be super frustrating, wouldn't it? A lot of developers thought so, too, and found different ways to create stateful sessions on the web.</p>
<h3 id="heading-the-invention-of-the-http-cookie">The invention of the HTTP cookie</h3>
<p>Lou Montoulli, a developer at Netscape in the early 90s, had a problem – he was developing an online store for another company, MCI, which would store the items in each customer's cart on its servers. This meant that people had to create an account first, it was slow, and it took up a lot of storage.</p>
<p>MCI requested for all of this data to be stored on each customer's own computer instead. Also, they wanted everything to work without customers having to sign in first.</p>
<p>To solve this, Lou turned to an idea that was already pretty well known among programmers: the magic cookie.</p>
<p>A magic cookie, or just cookie, is a bit of data that's passed between two computer programs. They're "magic" because the data in the cookie is often a random key or token, and is really just meant for the software using it.</p>
<p>Lou took the magic cookie concept and applied it to the online store, and later to browsers as a whole.</p>
<p>Now that you know about their history, let's take a quick look at how cookies are used to create stateful sessions on the web.</p>
<h2 id="heading-how-cookies-work">How cookies work</h2>
<p>One way to think of cookies is that they're a bit like the wristbands you get when you visit an amusement park.</p>
<p>For example, when you sign in to a website, it's like the process of entering an amusement park. First you pay for a ticket, then when you enter the park, the staff checks your ticket and gives you a wristband.</p>
<p>This is like how you sign in – the server checks your username and password, creates and stores a session, generates a unique session id, and sends back a cookie with the session id.</p>
<p>(Note that the session id is <em>not</em> your password, but is something completely separate and generated on the fly. Proper password handling and authentication is outside the scope of this article, but you can find some in depth guides <a target="_blank" href="https://www.freecodecamp.org/news/search/?query=authentication">here</a>.)</p>
<p>While you're in the amusement park, you can go on any ride by showing your wristband. </p>
<p>Similarly, when you make requests to the website you're signed in to, your browser sends your cookie with your session id back to the server. The server checks for your session using your session id, then returns data for your request.</p>
<p>Finally, once you leave the amusement park, your wristband no longer works – you can't use it to get back into the park or go on more rides. </p>
<p>This is like signing out of a website. Your browser sends your sign out request to the server with your cookie, the server removes your session, and lets your browser know to remove your session id cookie.</p>
<p>If you want to get back into the amusement park, you'd have to buy another ticket and get another wristband. In other words, if you want to continue using the website, you'd have to sign back in.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/fireship-cookies.png" alt="Image" width="600" height="400" loading="lazy">
<em>Source: <a target="_blank" href="https://www.youtube.com/watch?v=UBUNrFtufWo">Session vs Token Authentication in 100 Seconds</a> (YouTube)</em></p>
<p>This is just a simple example of how cookies can be used to keep you signed in to websites. They can be used to remember your setting for dark mode, to track your behavior on a website, and so much more.</p>
<h2 id="heading-how-to-use-cookies">How to use cookies</h2>
<p>Now that you know about the history of cookies and why they're used, let's look at some of the limitations of using cookies, then dive into some simple examples.</p>
<h3 id="heading-cookie-limitations">Cookie limitations</h3>
<p>Cookies are quite limited compared to some modern alternatives to storing data in the browser like <code>localStorage</code> or <code>sessionStorage</code>. Here's a rundown of cookies compared to those other technologies:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td>Cookies</td><td>Local Storage</td><td>Session Storage</td></tr>
</thead>
<tbody>
<tr>
<td>Capacity</td><td>4KB</td><td>10MB</td><td>5MB</td></tr>
<tr>
<td>Accessible from</td><td>Any window</td><td>Any window</td><td>Same tab</td></tr>
<tr>
<td>Expires</td><td>Manually set</td><td>Never</td><td>On tab close</td></tr>
<tr>
<td>Storage location</td><td>Browser and server</td><td>Browser only</td><td>Browser only</td></tr>
<tr>
<td>Sent with requests</td><td>Yes</td><td>No</td><td>No</td></tr>
</tbody>
</table>
</div><p>Based on: <a target="_blank" href="https://www.youtube.com/watch?v=AwicscsvGLg">cookies vs localStorage vs sessionStorage - Beau teaches JavaScript</a> (YouTube)</p>
<p>Cookies are a much older technology, and have a very limited capacity. Still, there's quite a bit you can do with them. And their small size makes it easy for the browser to send cookies with each request to the server.</p>
<p>It's also worth mentioning that browsers only allow cookies to work from one domain for security reasons.</p>
<p>So if you sign in to your bank at, say, ally.com, then cookies will only work within that domain and its subdomains. For example, your <code>ally.com</code> cookie will work on <code>ally.com</code>, <code>ally.com/about</code>, and the subdomain <code>www.ally.com</code>, but not <code>axos.com</code>.</p>
<p>This means that, even if you have accounts and are signed in at both <code>ally.com</code> and <code>axos.com</code>, those sites won't be able to read each other's cookies.</p>
<p>It's important to remember that your cookies are sent with every request you make in the browser. This is very convenient, but has some serious security implications we'll get into later.</p>
<p>Finally, if there's one thing you take away from this article, just remember that cookies are meant to be openly read and sent, so you should never store sensitive information like passwords in them.</p>
<h3 id="heading-how-to-set-a-cookie-in-javascript">How to set a cookie in JavaScript</h3>
<p>Cookies are really just strings with key / value pairs. Though you'll probably work with cookies more on the backend, there may be times you'll want to set a cookie on the client side.</p>
<p>Here's how to set a cookie in vanilla JavaScript:</p>
<pre><code class="lang-js"><span class="hljs-built_in">document</span>.cookie = <span class="hljs-string">'dark_mode=true'</span>
</code></pre>
<p>Then when you open the developer console, click "Application" and then on the site under "Cookies", you'll see the cookie you just added:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/image-101.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If you take a closer look at your cookie, you'll see that its expiration date is set to <code>Session</code>. That means the cookie will be destroyed when you close your tab / browser.</p>
<p>That might be the behavior you want, like for an online store with payment information.</p>
<p>But if you want your cookie to last longer, you'll need to set an expiration date.</p>
<h3 id="heading-how-to-set-an-expiration-date-on-a-cookie-in-javascript">How to set an expiration date on a cookie in JavaScript</h3>
<p>To set an expiration date, just set the value of your cookie, then add an <code>expires</code> attribute with a date set sometime in the future:</p>
<pre><code class="lang-js"><span class="hljs-built_in">document</span>.cookie = <span class="hljs-string">'dark_mode=true; expires=Fri, 26 Feb 2021 00:00:00 GMT'</span> <span class="hljs-comment">// expires 1 week from now</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/image-102.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>JavaScript's <code>Date</code> object should make this much easier and more flexible. You can read more about the <code>Date</code> object <a target="_blank" href="https://www.freecodecamp.org/news/the-ultimate-guide-to-javascript-date-and-moment-js/">here</a>.</p>
<p>Or you could use the <code>max-age</code> attribute with the number of seconds you'd like your cookie to last:</p>
<pre><code class="lang-js"><span class="hljs-built_in">document</span>.cookie = <span class="hljs-string">'dark_mode=true; max-age=604800'</span>; <span class="hljs-comment">// expires 1 week from now</span>
</code></pre>
<p>Then when that date rolls around, the browser will automatically remove your cookie.</p>
<h3 id="heading-how-to-update-a-cookie-in-javascript">How to update a cookie in JavaScript</h3>
<p>Whether or not your cookie has an expiration date, updating it is easy.</p>
<p>Just change the value for your cookie, and the browser will automatically pick it up:</p>
<pre><code class="lang-js"><span class="hljs-built_in">document</span>.cookie = <span class="hljs-string">"dark_mode=false; max-age=604800"</span>; <span class="hljs-comment">// expires 1 week from now</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/image-105.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-set-the-path-for-a-cookie-in-javascript">How to set the path for a cookie in JavaScript</h3>
<p>Sometimes you'll only want your cookie to work with certain parts of your website. Depending on how your website is set up, one way to do this is with the <code>path</code> attribute.</p>
<p>Here's how to make it so a cookie only works on the about page at <code>/about</code>:</p>
<pre><code class="lang-js"><span class="hljs-built_in">document</span>.cookie = <span class="hljs-string">'dark_mode=true; path=/about'</span>;
</code></pre>
<p>Now your cookie will only work on <code>/about</code> and other nested subdirectories like <code>/about/team</code>, but not on <code>/blog</code>.</p>
<p>Then when you visit the about page and check your cookies, you'll see it:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/image-103.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-delete-a-cookie-in-javascript">How to delete a cookie in JavaScript</h3>
<p>To delete a cookie in JavaScript, just set the <code>expires</code> attribute to a date that's already passed:</p>
<pre><code class="lang-js"><span class="hljs-built_in">document</span>.cookie = <span class="hljs-string">'dark_mode=true; expires=Sun, 14 Feb 2021 00:00:00 GMT'</span>; <span class="hljs-comment">// 1 week earlier</span>
</code></pre>
<p>You could also use <code>max-age</code> and pass it a negative value:</p>
<pre><code class="lang-js"><span class="hljs-built_in">document</span>.cookie = <span class="hljs-string">'dark_mode=true; max-age=-60'</span>; <span class="hljs-comment">// 1 minute earlier</span>
</code></pre>
<p>Then when you check for your cookie, it'll be gone:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/image-104.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>And that should be everything you need to know about using cookies in vanilla JS.</p>
<p>Everything we covered will work in a pinch, but if you plan on working with cookies extensively, look into libraries like <a target="_blank" href="https://github.com/js-cookie/js-cookie">JavaScript Cookie</a> or <a target="_blank" href="https://github.com/expressjs/cookie-session">Cookie Parser</a>.</p>
<h2 id="heading-security-concerns-with-cookies">Security concerns with cookies</h2>
<p>In general, cookies are very secure when implemented correctly. Browsers have a lot of built-in limitations that we covered earlier, partly due to the age of the technology, but also to improve security.</p>
<p>Still, there are a few ways that a bad actor can steal your cookie and use it to wreak havoc.</p>
<p>We'll go over some common ways this can happen, and look at different ways to fix it. </p>
<p>Also, note that any code snippets will be in vanilla JavaScript. If you want to implement these fixes on the server, you'll need to look up the exact syntax for your language or framework.</p>
<h3 id="heading-man-in-the-middle-attacks">Man-in-the-middle attacks</h3>
<p>A man-in-the-middle (MitM) attack describes a broad category of attacks where an attacker sits between a client and a server and intercepts the data going between the two.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/man-in-the-middle-attack-how-avoid.png" alt="Image" width="600" height="400" loading="lazy">
<em>Source: <a target="_blank" href="https://www.netsparker.com/blog/web-security/man-in-the-middle-attack-how-avoid/">Man-in-the-Middle Attacks and How To Avoid Them</a></em></p>
<p>This can be done in a lot of ways: by gaining access to or listening in on an insecure website, mimicking a public WiFi router, DNS spoofing, or through malware / adware like <a target="_blank" href="https://en.wikipedia.org/wiki/Superfish">SuperFish</a>.</p>
<p>Here's a high-level overview of MitM attacks, and how websites can protect themselves and their users.</p>
<p>Warning: The beginning of the video talks about Mary, Queen of Scotts, and shows an animated depiction of her beheading. It's not overly gruesome, but if you'd like to avoid it, skip ahead to <a target="_blank" href="https://youtu.be/8OR2dDIaIDw?t=57">this timestamp</a>:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/8OR2dDIaIDw" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>As a developer, you can greatly reduce the chance of a MitM attack by ensuring that you enable HTTPS on your server, use an SSL certificate from a trusted certificate authority, and ensure your code uses HTTPS instead of the insecure HTTP.</p>
<p>In terms of cookies, you should add the <code>Secure</code> attribute to your cookies so they can only be sent over a secure HTTPS connection:</p>
<pre><code class="lang-js"><span class="hljs-built_in">document</span>.cookie = <span class="hljs-string">'dark_mode=false; Secure'</span>;
</code></pre>
<p>Just remember that the <code>Secure</code> attribute doesn't actually encrypt any data in your cookie – it just ensures that the cookie can't be sent over an HTTP connection.</p>
<p>However, a bad actor could still possibly intercept and manipulate the cookie. To prevent this from happening, you can also use the <code>HttpOnly</code> parameter:</p>
<pre><code class="lang-js"><span class="hljs-built_in">document</span>.cookie = <span class="hljs-string">'dark_mode=false; Secure; HttpOnly'</span>;
</code></pre>
<p>Cookies with <code>HttpOnly</code> can only be accessed by the server, and not by the browser's <code>Document.cookie</code> API. This is perfect for things like a login session, where only the server really needs to know if you're signed into a site, and you don't need that information client side.</p>
<h3 id="heading-xss-attacks">XSS attacks</h3>
<p>An XSS (cross-site scripting) attack describes a category of attacks when a bad actor injects unintended, potentially dangerous code into a website.</p>
<p>These attacks are very problematic because they could affect every person that visits the site.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/cross-site-scripting.svg" alt="Image" width="600" height="400" loading="lazy">
<em>Source: <a target="_blank" href="https://portswigger.net/web-security/cross-site-scripting">Cross-site scripting</a></em></p>
<p>For example, if a site has a comments section and someone is able to include malicious code as a comment, it's possible that every person who visits the site and reads that comment will be affected.</p>
<p>In terms of cookies, if a bad actor pulls off a successful XSS attack on a site, they could gain access to session cookies and access the site as another signed in user. From there, they may be able to access the other user's settings, buy things as that user and have it shipped to another address, and so on.</p>
<p>Here's a video that gives a high-level overview of the different types of XSS – Reflected, Stored, DOM-based, and Mutation:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/EoaDgUgS6QA" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>As a developer, you'll want to ensure that your server enforces the Same Origin Policy, and that any input you receive from people is properly sanitized.</p>
<p>And like with preventing MitM attacks, you should set the <code>Secure</code> and <code>HttpOnly</code> parameters with any cookies you use:</p>
<pre><code class="lang-js"><span class="hljs-built_in">document</span>.cookie = <span class="hljs-string">'dark_mode=false; Secure; HttpOnly'</span>;
</code></pre>
<h3 id="heading-csrf-attacks">CSRF attacks</h3>
<p>A CSRF (cross-site request forgery) attack is when a bad actor tricks a person into carrying out an unintended, potentially malicious action.</p>
<p>For example, if you're signed into a site and click on a link in a comment, if that link is part of a CSRF attack, it may lead to you unintentionally changing your sign in details, or even deleting your account.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/cross-site-request-forgery.svg" alt="Image" width="600" height="400" loading="lazy">
<em>Source: <a target="_blank" href="https://portswigger.net/web-security/csrf">Cross-site request forgery</a></em></p>
<p>While CSRF attacks are somewhat related to XSS attacks, specifically reflected XSS attacks where someone inserts malicious code into a site, each preys on a different type of trust.</p>
<p>According to <a target="_blank" href="https://en.wikipedia.org/wiki/Cross-site_request_forgery">Wikipedia</a>, while XSS "exploits the trust a user has for a particular site, CSRF exploits the trust that a site has in a user's browser."</p>
<p>Here's a video that explains the basics of CSRF, and gives some useful examples:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/eWEgUcHPle0" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>As for cookies, one way to prevent possible CSRF attacks is with the <code>SameSite</code> flag:</p>
<pre><code class="lang-js"><span class="hljs-built_in">document</span>.cookie = <span class="hljs-string">'dark_mode=false; Secure; HttpOnly; SameSite=Strict'</span>;
</code></pre>
<p>There are a few values you can set for <code>SameSite</code>: </p>
<ul>
<li><code>Lax</code>: Cookies are not sent for embedded content (images, iframes, etc.) but are sent when you click on a link or send a request to the origin the cookie is set for. For example, if you're on <code>testing.com</code> and you click on a link to go to <code>test.com/about</code>, your browser will send your cookie for <code>test.com</code> with that request</li>
<li><code>Strict</code>: Cookies are only sent when you click on a link or send a request from the origin the cookie is set for. For example, your <code>test.com</code> cookie will only be sent while you're in and around <code>test.com</code>, and not coming from other sites like <code>testing.com</code></li>
<li><code>None</code>: Cookies will be sent with every request, regardless of context. If you set <code>SameSite</code> to <code>None</code>, you must also add the <code>Secure</code> attribute. It's better to avoid this value if possible</li>
</ul>
<p>Major browsers handle <code>SameSite</code> a bit differently. For example, if <code>SameSite</code> isn't set on a cookie, Google Chrome sets it to <code>Lax</code> by default.</p>
<h2 id="heading-alternatives-to-cookies">Alternatives to cookies</h2>
<p>You might be wondering, if there are so many potential security flaws with cookies, why are we still using them? Surely there must be a better alternative.</p>
<p>These days, you can use either <code>sessionStorage</code> or <code>localStorage</code> to store information that originally used cookies. And for stateful sessions, there's token-based authentication with things like JWT (JSON Web Tokens).</p>
<p>While it may seem like you have to choose between cookie-based or token-based authentication, it's possible to use both. For example, you might want use cookie-based authentication when someone signs in through the browser, and token-based authentication when someone signs in through a phone app.</p>
<p>To muddy the waters a bit more, authentication-as-a-service providers like Auth0 allow you to do both types of authentication.</p>
<p>If you'd like to learn more about web tokens and token-based authentication, check out some of our articles <a target="_blank" href="https://www.freecodecamp.org/news/search/?query=web%20tokens">here</a>.</p>
<h2 id="heading-when-you-give-a-developer-a-cookie">When you give a developer a cookie</h2>
<p>That's it! That should be just about everything you need to know to get started with using cookies, and what to watch out for along the way.</p>
<p>Did you find this useful? How do you use cookies? Let me know over on <a target="_blank" href="https://twitter.com/kriskoishigawa">Twitter</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
