SameSite cookie recipes

Rowan Merewood
Rowan Merewood

Chrome, Firefox, Edge, and others are changing their default behavior in line with the IETF proposal, Incrementally Better Cookies so that:

  • Cookies without a SameSite attribute are treated as SameSite=Lax, meaning the default behavior is to restrict cookies to first party contexts only.
  • Cookies for cross-site usage must specify SameSite=None; Secure to enable inclusion in third party context.

If you haven't already done so, you should update the attributes for your third-party cookies so they won't be blocked in the future.

Browser Support

  • Chrome: 51.
  • Edge: 16.
  • Firefox: 60.
  • Safari: 13.

Source

Use cases for cross-site or third-party cookies

There are a number of common use cases and patterns where cookies need to be sent in a third-party context. If you provide or depend on one of these use cases, make sure that either you or the provider are updating their cookies to keep the service functioning correctly.

Content within an <iframe>

Content from a different site displayed in an <iframe> is in a third-party context. Standard use cases include:

  • Embedded content shared from other sites, such as videos, maps, code samples, and social posts.
  • Widgets from external services such as payments, calendars, booking, and reservation features.
  • Widgets such as social buttons or anti-fraud services that create less obvious <iframes>.

Cookies may be used here to, among other things, maintain session state, store general preferences, enable statistics, or personalize content for users with existing accounts.

Diagram of a browser window where the URL of embedded content doesn't match the URL of the page.
If the embedded content doesn't come from the same site as the top-level browsing context, it's third-party content.

Because the web is inherently composable, <iframes> are also used to embed content viewed in a top-level or first-party context. Any cookies the site displayed in the iframe uses are considered third-party cookies. If you're creating sites that you want other sites to embed, and need cookies to make them work, you also need to ensure those are marked for cross-site usage or that you can fall back gracefully without them.

"Unsafe" requests across sites

"Unsafe" might sound concerning here, but it refers to any request that might be intended to change state. On the web, that's primarily POST requests. Cookies marked as SameSite=Lax are sent on safe top-level navigations, like clicking a link to go to a different site. However, something like a <form> submission to a different site using POST doesn't include cookies.

Diagram of a request moving from one page to another.
If the incoming request uses a "safe" method, the page sends cookies.

This pattern is used for sites that can redirect the user out to a remote service to perform some operation before returning, for example, redirecting to a third-party identity provider. Before the user leaves the site, a cookie is set containing a single use token with the expectation that this token can be checked on the returning request to mitigate Cross Site Request Forgery (CSRF) attacks. If that returning request comes through POST, you'll need to mark the cookies as SameSite=None; Secure.

Remote resources

Any remote resource on a page, such as from <img> tags or <script> tags, might rely on cookies being sent with a request. Common use cases include tracking pixels and personalizing content.

This also applies to requests sent from your JavaScript using fetch or XMLHttpRequest. If fetch() is called with the credentials: 'include' option, those requests are likely to include cookies. For XMLHttpRequest, expected cookies are usually indicated by a withCredentials value fo true. Those cookies must be appropriately marked to be included in cross-site requests.

Content within a WebView

A WebView in a platform-specific app is powered by a browser. Developers need to test whether the restrictions or issues that affect their apps also apply to their app's WebViews.

Android also lets its platform-specific apps set cookies directly using the CookieManager API. As with cookies set using headers or JavaScript, consider including SameSite=None; Secure if they're intended for cross-site use.

How to implement SameSite today

Mark any cookies that are only needed in a first-party context as SameSite=Lax or SameSite=Strict depending on your needs. If you don't mark these cookies and instead rely on default browser behavior to handle them, they can behave inconsistently across browsers and potentially trigger console warnings for each cookie.

Set-Cookie: first_party_var=value; SameSite=Lax

Make sure to mark any cookies needed in a third-party context as SameSite=None; Secure. Both attributes are required. If you just specify None without Secure, the cookie will be rejected. To account for differences in browser implementations, you might need to use some of the mitigating strategies described in Handle incompatible clients.

Set-Cookie: third_party_var=value; SameSite=None; Secure

Handle incompatible clients

Because these changes to include None and update default behavior are still relatively new, different browsers handle them in different ways. You can refer to the updates page on chromium.org for a list of known issues, but this list might not be exhaustive.

One possible workaround is to set each cookie in both the new and the old style:

Set-cookie: 3pcookie=value; SameSite=None; Secure
Set-cookie: 3pcookie-legacy=value; Secure

Browsers implementing the newer behavior set the cookie with the SameSite value. Browsers that don't implement the new behavior ignore that value and set the 3pcookie-legacy cookie. When processing included cookies, your site should first check for the presence of the new style of cookie and, then fall back to the legacy cookie if it can't find a new one.

The following example shows how to do this in Node.js, using the Express framework and its cookie-parser middleware:

const express = require('express');
const cp = require('cookie-parser');
const app = express();
app.use(cp());

app.get('/set', (req, res) => {
  // Set the new style cookie
  res.cookie('3pcookie', 'value', { sameSite: 'none', secure: true });
  // And set the same value in the legacy cookie
  res.cookie('3pcookie-legacy', 'value', { secure: true });
  res.end();
});

app.get('/', (req, res) => {
  let cookieVal = null;

  if (req.cookies['3pcookie']) {
    // check the new style cookie first
    cookieVal = req.cookies['3pcookie'];
  } else if (req.cookies['3pcookie-legacy']) {
    // otherwise fall back to the legacy cookie
    cookieVal = req.cookies['3pcookie-legacy'];
  }

  res.end();
});

app.listen(process.env.PORT);

This approach requires you to do extra work setting redundant cookies and making changes at the point of both setting and reading the cookie. However, it should cover all browsers regardless of their behavior, and keep third-party cookies functioning.

As an alternative, you can detect the client using the user agent string when a Set-Cookie header is sent. Refer to the list of incompatible clients, and use an appropriate user agent detection library for your platform, for example, the ua-parser-js library on Node.js. This approach only requires you to make one change, but user agent sniffing might not catch all affected users.

Support for SameSite=None in languages, libraries, and frameworks

The majority of languages and libraries support the SameSite attribute for cookies. However, because the addition of SameSite=None is still relatively recent, you might need to work around some standard behavior for now. These behaviors are documented in the SameSite examples repository on GitHub.

Getting help

Cookies are used everywhere on the web, and it's rare for any development team to have complete knowledge of where their site sets and uses them, especially in cross-site use cases. When you encounter an issue, it might be the first time anyone has encountered it, so don't hesitate to reach out: