Setup Cypress and Auth0 for SPA's
Tags: cypress, auth0, spa
Published: 2021-03-22 11:07:41

How to setup [Cypress](https://www.cypress.io/) for end to end testing an SPA that is locked behind an [Auth0](https://www.auth0.com) login. Auth0 has a blogpost explaining this setup [here](https://auth0.com/blog/end-to-end-testing-with-cypress-and-auth0/). However, if you're building a single page application you are most likely depending on [@auth0/auth0-spa-js](https://www.npmjs.com/package/@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](https://auth0.com/blog/end-to-end-testing-with-cypress-and-auth0/#Auth0-Setup--amp--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.
```json
{
  "auth_audience": "",
  "auth_url": "",
  "auth_client_id": "",
  "auth_client_secret": "",
  "auth_username": "",
  "auth_password": ""
}
```
Don't forget to add it to your `.gitignore`!

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

```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);
});
```

## Enable Auth0's localstorage for caching

Now somewhere in your SPA you will have:

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

...

this.auth0Client = await createAuth0Client(authOptions)
```

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.

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

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

```js
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);
          },
        });
      });
  });
});
```

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:
```json
{
  "baseUrl": "http://localhost:3020"
}
```

- 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!