Getting Cypress to correctly login to Auth0

Viewed 420

Im setting up a suite of E2E tests using cypress. I have a react typescript app and I am trying to get this working with cypress. My major problem that I am facing is that I am using Auth0 as my authentication provider. This means I need to programmatically log the user in before each test. However I can't seem to get the correct method of doing this. When I try it calls the call back url but it doesn't seem to have authenticated the user because they just go to a blank page? I am using the @auth0/auth0-react package which has a hook for accessing all of the Auth0 data.

Cypress command:

import jwt_decode from 'jwt-decode';

Cypress.Commands.add('login', () => {
    cy.log(`logging in as ${Cypress.env('auth_username')}`);

    cy.request({
        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'),
        },
    }).then(({ body }) => {
        const claims = jwt_decode(body.id_token);
        const { nickname, name, picture, updated_at, email, email_verified, sub, exp } = claims;
        const item = {
            body: {
                ...body,
                decodedToken: {
                    claims,
                    user: {
                        nickname,
                        name,
                        picture,
                        updated_at,
                        email,
                        email_verified,
                        sub,
                    },
                    audience: Cypress.env('auth_audience'),
                    client_id: Cypress.env('auth_client_id'),
                },
            },
            expiresAt: exp,
        };
        window.localStorage.setItem('auth0Cypress', JSON.stringify(item));
    });
});
1 Answers

I just faced the same problem and finally found the right solution:

You have to pass the id_token obtained from auth0 token endpoint to your app's callback endpoint as body of POST request

Important: I assume that you followed auth0 setup described in https://docs.cypress.io/guides/testing-strategies/auth0-authentication#Custom-Command-for-Auth0-Authentication or https://auth0.com/blog/end-to-end-testing-with-cypress-and-auth0/

Sources: https://github.com/auth0/express-openid-connect/blob/65bd19eeac63e00e9a5b76973a0a15ecbe2623af/middleware/auth.js#L90 line 90; https://github.com/auth0/express-openid-connect/blob/65bd19eeac63e00e9a5b76973a0a15ecbe2623af/lib/client.js#L88 line 88; https://github.com/panva/node-openid-client/blob/main/lib/client.js#L333 line 333.

Working solution:

Cypress.Commands.add("login", (overrides = {}) => {
  const tenantURL = Cypress.env("auth0_tenant_url");
  const options = {
    method: "POST",
    url: `${tenantURL}/oauth/token`,
    body: {
      grant_type: "password",
      username: Cypress.env("auth_username"),
      password: Cypress.env("auth_password"),
      audience: `${tenantURL}/api/v2/`,
      scope: "openid profile email",
      client_id: Cypress.env("auth0_client_id"),
      client_secret: Cypress.env("auth0_client_secret"),
    },
  };
  cy.request(options).then((resp) => {
    const { id_token } = resp.body;
    cy.request({ // this is the crucial part. Passing id_token to callback endpoint 
      method: "POST", // callback endpoint sets the session cookie which is picked up by cypress and used to authenticate subsequent requests
      url: "/callback",
      body: { id_token }, // Must pass only the access_token. Passing full response from oauth causes the 'missing at_hash error'
    });
  });
});

Example cypress.env.json:

{
  "auth0_tenant_url": "auth0 tenant url",
  "auth0_client_id": "auth0 client id",
  "auth0_client_secret": "auth0 secret",
  "auth_username": "user@fakeemail",
  "auth_password": "password"
}

Then all you need is:

describe("Logged in route test", () => {
  beforeEach(() => {
    cy.login();
    cy.visit("/");
  });

  it("displays content for logged in users", () => {
    cy.get("button[role=buy]").should("exist");
  });
});

You can see full example in my Clampfit exporter app:

Related