How to check SameSite cookie attribute

Read the Set-Cookie lines and pull the attribute out: curl -s -o /dev/null -D - https://example.com/ | grep -o -i -E 'samesite=[a-z]+'. Expect Strict, Lax or None on every cookie you own. A cookie with no SameSite at all is treated as Lax by current browsers, which is a default, not a decision.

Why check this

SameSite decides whether a cookie rides along on a request that a different site started. It is the cheapest defence against cross-site request forgery and the most common cause of a payment callback or an embedded widget losing its session. Run this check whenever a flow crosses an origin boundary: a hosted checkout returning to your domain, single sign-on, an iframe embed, or an OAuth redirect back from an identity provider.

Prerequisites

import http from 'node:http';
http.createServer((req, res) => {
  res.setHeader('set-cookie', [
    'sid=a1; Path=/; Secure; HttpOnly; SameSite=Strict',
    'csrf=b2; Path=/; Secure; SameSite=Lax',
    'embed=c3; Path=/; Secure; SameSite=None',
    'broken=d4; Path=/; SameSite=None',
    'legacy=e5; Path=/',
  ]);
  res.writeHead(200, { 'content-type': 'text/plain' });
  res.end('ok\n');
}).listen(8792, '127.0.0.1');

Steps

  1. Step 1.

    Count the values a real response uses.

    curl -s -o /dev/null -D - https://www.cloudflare.com/ | grep -i '^set-cookie:' | grep -o -i -E 'samesite=[a-z]+' | sort | uniq -c
    
          3 SameSite=Lax
        1 SameSite=None

    Four values across five cookies. The count that is missing from this output is the one step 2 finds.

  2. Step 2.

    List the cookies that carry no SameSite attribute at all.

    curl -s -o /dev/null -D - https://www.cloudflare.com/ | grep -i '^set-cookie:' | sed -E 's/^[Ss]et-[Cc]ookie: ([^=]+)=[^;]*/\1/' | sed -E 's/ ?(Expires|Path|Domain|Max-Age)=[^;]*;?//gI' | grep -vi 'SameSite'
    
    _ga;

    One cookie with no attribute. Browsers apply Lax to it, so its behaviour depends on the browser version rather than on the server.

  3. Step 3.

    Start the local server from the prerequisites and read every shape in one response.

    curl -s -o /dev/null -D - http://127.0.0.1:8792/ | grep -i '^set-cookie:'
    
    set-cookie: sid=a1; Path=/; Secure; HttpOnly; SameSite=Strict
    set-cookie: csrf=b2; Path=/; Secure; SameSite=Lax
    set-cookie: embed=c3; Path=/; Secure; SameSite=None
    set-cookie: broken=d4; Path=/; SameSite=None
    set-cookie: legacy=e5; Path=/
  4. Step 4.

    Find the combination a browser rejects outright.

    curl -s -o /dev/null -D - http://127.0.0.1:8792/ | grep -i '^set-cookie:' | grep -i 'samesite=none' | grep -vi 'secure'
    
    set-cookie: broken=d4; Path=/; SameSite=None

    SameSite=None without Secure is not a weaker cookie, it is no cookie. Browsers discard the whole Set-Cookie line, so the symptom is a session that never starts.

  5. Step 5.

    Open the site in Chrome, press F12, then the Application tab, Storage, Cookies, and select the origin.

    The grid has a SameSite column with the value per cookie, and blank where the attribute is absent. That blank is the one thing curl cannot tell you apart from the browser default, because the default is applied inside the browser and never appears on the wire. Stop the local server with Ctrl+C when you are done.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | SameSite=Strict on a session cookie | Never sent on a request another site started, top-level navigation included | Check that arriving from an external link still shows the user as logged in. | | SameSite=Lax | Sent on top-level GET navigation, withheld on cross-site POST and subresources | The right default for a session cookie. | | SameSite=None; Secure | Sent on every cross-site request | Only for cookies an embed or a third-party callback needs. Pair it with a CSRF token. | | SameSite=None with no Secure | The browser drops the cookie entirely | Release blocker. The session silently fails to start. | | No SameSite attribute | The browser applies Lax | Write the value down explicitly so behaviour does not depend on the client. | | SameSite=Strict on a cookie used by an iframe | The embed never receives it | Split the embed onto its own cookie with None; Secure. |

Common mistakes

Sign: The cookie has no SameSite attribute and the flow works, so the attribute is recorded as unnecessary.Cause: Current browsers apply Lax when the attribute is absent, so an unset cookie and an explicit Lax behave the same today. Older clients send the cookie on every cross-site request, and the difference only appears in the field.
Sign: SameSite=None is added to fix an embed and the session stops working everywhere.Cause: None without Secure is discarded by the browser, header line and all. On a staging host served over plain HTTP the cookie is never stored, so the login loop looks like an authentication bug.
Sign: Strict is chosen for the session cookie and users report being logged out when they arrive from email.Cause: Strict withholds the cookie even on a top-level navigation from another site, so the first page after any external link renders logged out. Lax allows that one case and blocks the dangerous ones.
Sign: curl shows SameSite=Strict and the cookie is still sent on a cross-site request in a test.Cause: curl never enforces SameSite. It has no concept of the site that initiated a request, so it sends whatever matches the host. Only a browser can prove this attribute works.

What to check next

FAQ

How to check SameSite cookie in Chrome?

Press F12, open the Application tab, then Storage, Cookies, and pick the origin. The SameSite column shows the value per cookie and stays blank when the server sent no attribute.

What does the browser do when SameSite is absent?

It treats the cookie as Lax. The default is applied client side, so the header on the wire still shows nothing, and two browser versions can disagree about the same response.

Why does SameSite=None need Secure?

A cookie that travels on every cross-site request is the one that most needs TLS. Browsers enforce the pairing by rejecting the Set-Cookie line when Secure is missing, as step 4 shows.

Does SameSite replace a CSRF token?

No. Lax still allows top-level GET navigation, and any flow that needs None has the attribute switched off by design. Keep the token.

Can curl prove SameSite is working?

No. curl reads what the server sent. Enforcement happens in the browser, which is where the check ends.

Verified

Verified by Maks Vernycurl 8.21.0node 22.23.2

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

intermediate6 minpublished updated Maks Verny