Nonce (nonce) claim must be a string present in the ID token

Hello,

since updating the auth0-spa-js SDK to 1.12.0 in my project, I am unable to login into my app using password grant in my cypress e2e tests. As a result, I now get the error message Nonce (nonce) claim must be a string present in the ID token when calling handleRedirectCallback(). But using password grant doesn’t return a nonce in the id token. Below is the code I use in cypress to login. It’s mostly copied from the blog article one of you did back in 2019. This works in 1.11.0:

Cypress.Commands.add('login', () => {
  Cypress.log({ name: 'log in with Auth0' })

  const options = {
    method: 'POST',
    url: Cypress.env('AUTH0_LOGIN_URL'),
    body: {
      grant_type: 'password',
      username: Cypress.env('AUTH0_USERNAME'),
      password: Cypress.env('AUTH0_PASSWORD'),
      audience: Cypress.env('AUTH0_AUDIENCE'),
      scope: 'openid profile email',
      client_id: Cypress.env('AUTH0_CLIENT_ID'),
      client_secret: Cypress.env('AUTH0_CLIENT_SECRET'),
    },
  }

  cy.request(options).then(({ body }) => {
    // eslint-disable-next-line camelcase
    const { access_token, expires_in, id_token } = body
    const scope = 'openid profile email'

    cy.server()
    // intercept Auth0 request for token and return what we have
    cy.route({
      url: 'oauth/token',
      method: 'POST',
      response: {
        access_token,
        id_token,
        scope,
        expires_in,
        token_type: 'Bearer',
      },
    })

    // Auth0 SPA SDK will check for value in cookie to get appState
    const auth0State = JSON.stringify({
      nonce: '',
      state: Math.random().toString(36).substring(7),
    })
    cy.setCookie(
      `a0.spajs.txs.${auth0State}`,
      encodeURIComponent(JSON.stringify({
        appState: { targetUrl: '/' },
        scope,
        audience: Cypress.env('AUTH0_AUDIENCE'),
        redirect_uri: 'http://localhost:8080',
      }))).then(() => {
      cy.visit(`/?code=test-code&state=${auth0State}`)
    })
  })
})

So what changed? What do I have to do, to get it working again? Should I create a github issue?

Thanks!

Hi sebastian.richter and welcome to the community! :tada:

I think this might be the relevant changer: [SDK-1885] Add some additional state validation by adamjmcgrath · Pull Request #560 · auth0/auth0-spa-js · GitHub

I noticed your nonce string is empty. Could you try filling it and seeing if that fixes this issue?

1 Like

@thomas.osborn Filling with what kind of content? I tried random strings, random number, arbitary strings, nanoid() and still get the error. I mean the error says use the nonce in the id token. But as I said there is none?!

    const auth0State = JSON.stringify({
      nonce: nanoid(),
      state: Math.random().toString(36).substring(7),
    })

Hi sebastian.richter. Thanks for clarifying that. I think since you have already tried including a nonce, you are right you should go ahead and make a Github issue.

However, please keep in mind the SPA JS is probably not designed to do password grant, as that is a flow not suited for front end applications. I understand you’re doing this in a test environment where the client secret can be stored securely, but I suspect it was not intended for this flow to work in AUTH) SPA JS.

1 Like

I have the same issue with my Cypress tests. It looks like this change makes it impossible to use Cypress.
@sebastian.richter, did you create a Github issue? Or did you get it working in another way?

@HWouters No github issue. If the auth0 flow oauth/token doesn’t allow nonce because reasons (I don’t know all details of OAuth), then it’s supposed to be like that.
So the following is my new way, unfortunately you need another dependency (jwt-decode) to decode jwt, or you do it by yourself.

  1. I use a env variable to control where the token gets stored. My e2e tests use cookies and localstorage for the auth0 config:
VUE_APP_AUTH0_CACHE_LOCATION=localstorage
VUE_APP_AUTH0_USE_COOKIES=true
this.auth0Client = await createAuth0Client({
    ...
    // use cookies and store the token in the local storage in e2e tests
    // this makes login for e2e test much easier
    cacheLocation: process.env.VUE_APP_AUTH0_CACHE_LOCATION,
    useRefreshTokens: !process.env.VUE_APP_AUTH0_USE_COOKIES,
})
  1. I created a cypress command for login, which mimics the functionality of the sdk setting a decoded token in the localstorage. Because we told the SDK it will find its stuff in the localstorage, the authentication will work now.
import jwt_decode from 'jwt-decode'

Cypress.Commands.add('login', () => {
  Cypress.log({ name: 'log in with Auth0' })

  const username = Cypress.env('AUTH0_USERNAME')
  const password = Cypress.env('AUTH0_PASSWORD')
  const audience = Cypress.env('AUTH0_AUDIENCE')
  const scope = 'openid profile email'
  const client_id = Cypress.env('AUTH0_CLIENT_ID')
  const client_secret = Cypress.env('AUTH0_CLIENT_SECRET')

  cy.request({
    method: 'POST',
    url: Cypress.env('AUTH0_LOGIN_URL'),
    body: {
      grant_type: 'password',
      username,
      password,
      audience,
      scope,
      client_id,
      client_secret,
    },
  }).then(({ body: { access_token, expires_in, id_token } }) => {
    localStorage.setItem(`@@auth0spajs@@::${client_id}::${audience}::${scope}`, JSON.stringify({
      body: {
        client_id,
        access_token,
        id_token,
        scope,
        expires_in,
        decodedToken: {
          user: jwt_decode(id_token),
        },
      },
      expiresAt: Math.floor(Date.now() / 1000) + expires_in,
    }))
    cy.visit('/')
  })
})
  1. Use the command before routing to your homepage.
  beforeEach(() => {
    cy.login()
  })
  1. Hope that the structure of the stored information will never change :').

That’s it, I hope it helps! Please don’t ask me about details, I forgot almost everything again :'). I’m happy that it works. Maybe I just copied it from the e2e tests in the Auth0 SPA SDK repo e2e tests. Can’t remember.

Thank you for the response!

Storing the token in local storage will definitely make it easier to test, but you do a concession to security. Auth0 is advocating to store the token in memory and I’m not willing to give this up to be able to test my application. On the other hand, I’m not keen on dropping my Cypress test suite… :frowning:

I think I’m going to make a GitHub issue, because I don’t see any possibility to use Cypress and Auth0 (using recommended practices) together after the changes they made in 1.12.0.

@HWouters I don’t care to be honest, that stuff runs on a test environment with a test tenant and on a local server in the CI. I don’t see a problem here. But hey, I don’t know your seyup. And maybe the Auth0 Teams have different opinions on this, but why do they offer this option then to store it in the localStorage if it is such a security issue!?