DEV Community

peerhenry
peerhenry

Posted on

Setup Cypress and Auth0 for SPA's

How to setup Cypress for end to end testing an SPA that is locked behind an Auth0 login. Auth0 has a blogpost explaining this setup here. However, if you're building a single page application you are most likely depending on @auth0/auth0-spa-js for managing your login state. This means we need to get the token into our auth0 client. This post will demonstrates how we can achieve that using localstorage.

Setup Auth0 and Cypress

First of all we need to configure Auth0 so that we can retrieve a token with an API request. To do this follow the steps in the paragraph Auth0 Setup & Configuration from the Auth0 blogpost about Cypress.

Add a file cypress.env.json to your cypress project root folder and fill it out with your auth0 application and user data.

{
  "auth_audience": "",
  "auth_url": "",
  "auth_client_id": "",
  "auth_client_secret": "",
  "auth_username": "",
  "auth_password": ""
}
Enter fullscreen mode Exit fullscreen mode

Don't forget to add it to your .gitignore!

Then add a command to /cypress/support/commands.js:

Cypress.Commands.add('login', (overrides = {}) => {
  Cypress.log({
    name: 'loginViaAuth0',
  });

  const options = {
    method: 'POST',
    url: Cypress.env('auth_url'),
    body: {
      grant_type: 'password',
      username: Cypress.env('auth_username'),
      password: Cypress.env('auth_password'),
      audience: Cypress.env('auth_audience'),
      scope: 'openid profile email',
      client_id: Cypress.env('auth_client_id'),
      client_secret: Cypress.env('auth_client_secret'),
    },
  };
  cy.request(options);
});
Enter fullscreen mode Exit fullscreen mode

Enable Auth0's localstorage for caching

Now somewhere in your SPA you will have:

import createAuth0Client from '@auth0/auth0-spa-js'

...

this.auth0Client = await createAuth0Client(authOptions)
Enter fullscreen mode Exit fullscreen mode

This is where you should enable localstorage. The parameter authOptions is an object that can contain cacheLocation, which we just need to set to 'localstorage'. Note that saving tokens in localstorage is a security concern. Therefore we will only use localstorage for non-production environments. For production we will use cacheLocation: 'memory', which is also the default if you don't provide it.

const cacheLocation = process.env.NODE_ENV === 'production' ? 'memory' : 'localstorage'
const optionsWithCache = {
  ...authOptions,
  cacheLocation,
}
this.auth0Client = await createAuth0Client(optionsWithCache)
Enter fullscreen mode Exit fullscreen mode

This also means Cypress login will only work in non-production environments, so I hope you don't need to run Cypress on your production environment.

The login test

describe("login", () => {
  it("should successfully log into our app", () => {
    cy.login()
      .then((resp) => {
        return resp.body;
      })
      .then((body) => {
        const { access_token, expires_in, id_token, token_type } = body;
        cy.visit("/", {
          onBeforeLoad(win) {
            const keyPrefix = "@@auth0spajs@@";
            const defaultScope = "openid profile email";
            const clientId = Cypress.env("auth_client_id");
            const audience = "default";
            const key = `${keyPrefix}::${clientId}::${audience}::${defaultScope}`;
            const decodedToken = {
              user: JSON.parse(
                Buffer.from(body.id_token.split(".")[1], "base64").toString(
                  "ascii"
                )
              ),
            };
            const storageValue = JSON.stringify({
              body: {
                client_id: clientId,
                access_token,
                id_token,
                scope: defaultScope,
                expires_in,
                token_type,
                decodedToken,
                audience,
              },
              expiresAt: Math.floor(Date.now() / 1000) + body.expires_in,
            });
            win.localStorage.setItem(key, storageValue);
          },
        });
      });
  });
});
Enter fullscreen mode Exit fullscreen mode

A few things to note from this snippet:

  • cy.visit("/") assumes you've set baseUrl in cypress.json. For example in my case testing locally:
{
  "baseUrl": "http://localhost:3020"
}
Enter fullscreen mode Exit fullscreen mode
  • The key and storageValue need to be of a specific format in order for the Auth0 client to pick it up. The snippet contains the default values for keyPrefix, defaultScope and audience. Don't worry about their peculiar values. Unless you're tinkering with scope or audience when creating your Auth0 client you don't need to change those.

Happy testing!

Top comments (2)

Collapse
 
jondpenton profile image
Jon Penton

Thanks for this man! I was having some trouble figuring this out on my own and your solution worked 😄

Collapse
 
nicolasmontielf profile image
Nico Montiel

You wrote this almost a year ago, but today, this it was the answer to my problem. Thank you so much for the post, you are amazing!