Why your auth provider isn't working in Cypress

The dreaded SameSite problem

nextjs, react, cypress

SameSite error in Chrome devtoolsSameSite error in Chrome devtools

The web application that I am currently working on uses several 3rd party auth providers as part of its authentication flow. Manually testing the application I could see that everything was working. However, when running Cypress tests I was getting a strange warning mark on the header of the response from my auth provider. This header was asking my browser to set the session cookie but it was refusing to do so.

Naughty browser.

What is the SameSite attribute?

The SameSite attribute is basically a measure taken to mitigate CSRF attacks. It tells the browser how it should treat cookies across multiple domains. The SameSite attribute can have one of three values:

  1. None
    • The browser will allow all cross-site cookies.
  2. Lax
    • The browser will allow cross-site cookies if the target browsing context is "top-level".
      • What is a "top-level" browsing context?
        A browsing context is the environment in which a browser displays a document. In modern browsers, it usually is a tab, but can be just a part of a page, like an iframe. Top-level simply means there is no parent context. For example, a tab is a top-level browsing context whereas an iframe exists within a tab (as a child browsing-context) and so cannot be considered top-level.
  3. Strict
    • The browser does not allow any cross-site cookies.

Note: Should the SameSite attribute header be omitted, a default of Lax will be applied by most browsers.

Why does it work outside of Cypress?

During the test, redirecting to the auth provider does not change the address in the URL bar of the Cypress test runner. This is because the entire application being tested, including any external redirects, is contained within an iframe. This does not constitute a top-level browsing context and so violates the SameSite=Lax restrictions. Outside of Cypress there is no iframe and so the redirect takes place without breaking the rules.

Solution

Luckily Cypress provides the cy.intercept function that will allow us to intercept and modify all responses including redirects. We can use it to rewrite the headers of any response to: SameSite=None, thus allowing the browser to set our cookies.

We can abstractify this to a Cypress command so we can call it from any test.

1
There might not be a 'set-cookie' header so we could have an undefined value we need to filter out.
2
We must also add 'secure;' otherwise our 'samesite=none' won't be valid.
TS
// commands.ts
Cypress.Commands.add("rewriteHeaders", () => {
cy.intercept("*", (req) =>
req.on("response", (res) => {
const setCookies = res.headers["set-cookie"]
res.headers["set-cookie"] = (
Array.isArray(setCookies) ? setCookies : [setCookies]
)
1.filter((x) => x)
.map((headerContent) =>
headerContent.replace(
/samesite=(lax|strict)/gi,
2"secure; samesite=none"
)
)
})
)
})
1
There might not be a 'set-cookie' header so we could have an undefined value we need to filter out.
2
We must also add 'secure;' otherwise our 'samesite=none' won't be valid.

We can now use our custom command before each test run begins.

TS
// mytest.cy.ts
describe("Logs in", () => {
beforeEach(() => {
cy.rewriteHeaders()
})
it("should log in without errors", () => {
cy.contains("LOGIN").click()
...

And thats all! Hopefully this saves someone a few hours of debugging!

Subscribe

Webmentions

Tom Oliver's profile picture
Tom Oliver

Not as cool as this post right here am I rite m8s??? ๐ŸŒณ๐Ÿ’ป๐ŸŽ‰

Tom Oliver's profile picture
Tom Oliver

Oh, looks like I have to paste the link explicitly for it to work. Anyways, here it is: https://blog.rubenwardy.com/2022/03/17/plant-monitor/