adamjmcgrath / cypress-spa-example

14 stars 3 forks source link

Updated version using underlying SPA SDK with Cypress 6+, TS and Vue #3

Open IlCallo opened 3 years ago

IlCallo commented 3 years ago

For anyone coming here, here's an updated version which uses SPA SDK itself to perform the flow. A static config file is assumed, but can be adapted to use env variables.

Care as it uses deep import to get many types which aren't exposed.

Head here it you want the TS + Vue3 compatible version of Auth0 wrapper for Vue.

// main.ts

import { Auth0Instance, initAuth0 } from 'src/composables/auth0/instance';
import { audience, client_id, domain } from '../../auth0.config.json';

declare module 'vue/types/vue' {
  interface Vue {
    $auth: Auth0Instance;
  }
}

declare global {
  interface Window {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    Cypress?: any;
    Auth0?: Auth0Instance;
  }
}

const auth0Instance = initAuth0({
    client_id,
    domain,
    audience,
    onRedirectCallback: appState =>
      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
      router.push(appState?.targetUrl ?? window.location.pathname),
    cacheLocation: window.Cypress ? 'localstorage' : 'memory'
});

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
Vue.prototype.$auth = auth0Instance;

// When executing into Cypress, expose Auth0 instance
if (window.Cypress) {
  window.Auth0 = auth0Instance;
}
// cypress/support/auth0-helpers.ts

import { TokenEndpointResponse as Auth0TokenEndpointResponse } from '@auth0/auth0-spa-js/dist/typings/api';
import { ICache } from '@auth0/auth0-spa-js/dist/typings/cache';
import type { verify } from '@auth0/auth0-spa-js/dist/typings/jwt';
import { Ref } from '@vue/composition-api';
import { Auth0Instance } from 'src/composables/auth0/instance';

// These methods are private, we are forced to use them to comply to SPA SDK flow
interface Auth0ClientPrivateMethods {
  _verifyIdToken(id_token: string): ReturnType<typeof verify>;
  cache: ICache;
}

export interface TokenEndpointResponse extends Auth0TokenEndpointResponse {
  token_type: string;
}

export const useAuth0 = () =>
  cy.window().its('Auth0') as Cypress.Chainable<
    Auth0Instance & { auth0Client: Ref<Auth0ClientPrivateMethods> }
  >;
// cypress/support/commands.ts

import {
  audience,
  client_id,
  client_secret,
  domain,
  password,
  scope,
  username
} from 'auth0.config.json';
import { TokenEndpointResponse, useAuth0 } from './auth0-helpers';

Cypress.Commands.add('login', () => {
  cy.request({
    method: 'POST',
    url: `https://${domain}/oauth/token`,
    body: {
      grant_type: 'password',
      username,
      password,
      audience,
      scope,
      client_id,
      client_secret
    }
  }).then(({ body }) => {
    const { id_token, ...otherFields } = body as TokenEndpointResponse;

    useAuth0().then(auth0 => {
      const auth0Client = auth0.auth0Client.value;

      const decodedToken = auth0Client._verifyIdToken(id_token);

      const cacheEntry = {
        ...otherFields,
        scope,
        client_id,
        id_token,
        audience,
        decodedToken
      };

      auth0Client.cache.save(cacheEntry);
    });
  });
});
// cypress/support/types.d.ts

// Must be put in its own file because ES6 imports into 'commands.ts' prevents TS from discovering the augmentation
// eslint-disable-next-line @typescript-eslint/no-namespace, @typescript-eslint/no-unused-vars
declare namespace Cypress {
  interface Chainable {
    /**
     * Custom command to login using Auth0 provider.
     * @example cy.login()
     */
    login(): void;
  }
}
adamjmcgrath commented 3 years ago

👍 Thanks @IlCallo