auth0-spa-js: Cannot automate login in cypress since 1.12 using cookie injection

Describe the problem

In order to automate test to a frontend application i’m currently working on, I’m using cypress on a react app.

As it’s not possible to navigate to the auth0 login page, I’ve been using a command that generates the user token using the API POST /oauth/token, and then inject the token in the cookies and redirect the front end as it would occur after a successful login using the state param.

But since version 1.12, the behavior has changed and the auth0 sdk does not seem to accept this way to inject authentication.

Here is the code I’m using to authenticate the browser automation (This code still works using 1.11):

Cypress.Commands.add("login", ({ username, password }, appState = { targetUrl: "/" }) => {
  cy.log(`Log in as ${username}`)
  cy.request({
    method: "POST",
    url: Cypress.env("AUTH0_GET_TOKEN_URL"),
    body: {
      grant_type: "password",
      username: username,
      password,
      audience: Cypress.env("AUTH0_AUDIENCE"),
      scope: "openid profile email",
      client_id: Cypress.env("AUTH0_CLIENTID"),
      client_secret: Cypress.env("AUTH0_CLIENT_SECRET"),
    },
  }).then(({ body }) => {
    const { access_token, expires_in, id_token } = body

    cy.server()
    cy.route({
      url: Cypress.env("AUTH0_GET_TOKEN_URL"),
      method: "POST",
      response: {
        access_token,
        id_token,
        scope: "openid profile email",
        expires_in,
        token_type: "Bearer",
      },
    })

    const stateId = "test"
    cy.setCookie(
      `a0.spajs.txs.${stateId}`,
      encodeURIComponent(
        JSON.stringify({
          appState,
          scope: "openid profile email",
          audience: Cypress.env("AUTH0_AUDIENCE"),
          redirect_uri: "http://localhost:3000",
        })
      )
    )
    cy.visit(`/?code=test-code&state=${stateId}`)
  })
})

Maybe it’s a bad thing to do, but it seemed fine to me to do it that way, if you have any other option available I would be glad to hear about how I should do it differently.

What was the expected behavior?

I was expecting that the state handling would be the same after the version upgrade.

Reproduction

  • Allow password authentication on your tenant
  • Generate a token using auth0 api (POST https://{your-tenant}.auth0.com/oauth/token)
  • insert a cookie at a0.spajs.txs.${stateId} as the login page would do
  • mock the auth0 api with the generated token
  • redirect your app to the login callback with the parameters /?code=any-code&state=${stateId}

Can the behavior be reproduced using the SPA SDK Playground?

  • I’m not sure about how to test it with the playground

Environment

Please provide the following:

  • Version of auth0-spa-js used: 1.12.1
  • Which browsers have you tested in? any
  • Which framework are you using, if applicable (Angular, React, etc): react
  • Other modules/plugins/libraries that might be involved: cypress

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 17 (7 by maintainers)

Most upvoted comments

Hey @eliasplatekdkt

If I’m understanding correctly the solution using the localStorage is way more complex, because before using the localStorage some cookies checks are performed

apologies, I just used that as an example of setting localstorage - you don’t need to do the cookie stuff

I’ve put a working example here https://github.com/adamjmcgrath/cypress-spa-example

I think it’s simpler than your solution because it doesn’t rely on cy.request/cy.server It has the disadvantage of having to change cacheLocation in cypress mode But it works with the latest version of spa js and IMO relies less on our internals

@adamjmcgrath this is a very elegant solution and it works seamlessly. I tried the same implementation but some fields were missing which is why it didn’t work. Thanks a lot for your time !

I think that would be interesting to update the documentation at https://auth0.com/blog/end-to-end-testing-with-cypress-and-auth0/ which explains quite a different approach, and can be misleading for people searching for cypress + auth0, the localStorage approach requires less setup in cypress. I’m going with this solution in my project.

@eliasplatekdkt @adamjmcgrath Thank you so much guys ! It helps me a lot to auto login users on my SPA ! 💪

Thanks for sharing that @StephenCleary

If anyone else is having trouble setting up https://github.com/auth0/auth0-spa-js/issues/581#issuecomment-697323567 - It’s usually because the localStorage key for the credentials you’re putting in aren’t the same as the key SPA JS is looking for (eg you unexpectedly had offline_access in your scopes)

I added a suggestion and snippet of code that might help you debug situations like this here https://github.com/adamjmcgrath/cypress-spa-example/issues/2#issuecomment-765583180

One thing that tripped me up was I had useRefreshTokens set to true, so the auth0 library was adding offline_access to my scopes, which changes the localstorage key.

Glad you got it working @vinzcelavi!

@stevehobbsdev I’ve been playing a bit with the library, and it can be fixed by removing the pre-check on the nonce in handleRedirectCalback:

    // Transaction should have a `code_verifier` to do PKCE and a `nonce` for CSRF protection
    if (!transaction || !transaction.code_verifier || !transaction.nonce) {
      throw new Error('Invalid state');
    }

This check can be removed safely I think because the nonce is revalidated after getting the token in verifyIdToken:

  if (options.nonce) {
    if (!decoded.claims.nonce) {
      throw new Error(
        'Nonce (nonce) claim must be a string present in the ID token'
      );
    }
    if (decoded.claims.nonce !== options.nonce) {
      throw new Error(
        `Nonce (nonce) claim mismatch in the ID token; expected "${options.nonce}", found "${decoded.claims.nonce}"`
      );
    }
  }

If the transaction test only checks for the code_verifier before calling the POST /oauth/token, then you ensure that the code verifier is provided, and when you verify the id_token the nonce is validated as before (it is even declared as an optional parameter).

From my point of view it seems enough to fix it like this, I can provide a PR if it’s okay for you

@eliasplatekdkt thanks for raising. In v1.12 we moved from using cookies for the transaction store to session storage, so if you’re relying on cookie behaviour your tests will need to be updated.

It does get slightly easier though, in that we no longer include the state value in the key name, so that can be dropped. Otherwise, the data stored inside session storage should be exactly the same as it was for cookies.

Let me know if you’re able to get this working as before.