Infitialis Blog

View me on GitHub

Cypress, Google's IAP & Service Accounts

09 Mar 2021

We recently had a requirement to communicate with services that were protected by Google’s Identity Aware Proxy (IAP) inside Cypress integration tests.

After a lot of searching for existing solutions, I was coming up blank, since most people were suggesting interactive authentication using the Cypress plugin for social login.

Since we wante to use service accounts (and by extension, tokens generated by the metadata server, so we don’t have to deal with long lasting secrets ourselves), interactive login wasn’t an option.

I quickly came across the Google Authentication Library for Javascript, but using it with Cypress was hard!

When called directly from the integration test files, it would throw weird errors and state wasn’t saved between it('', () => {}) blocks.

After a lot of trial and error, we settled for configuring it as a plugin….

1) Install the auth library

npm install google-auth-library

2) Edit plugins/index.js

/**
 * @type {Cypress.PluginConfig}
 */
module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config
  on("task", {
    generateOIDC: require("./cypress-google-oidc")
  });
}

3) Create plugins/cypress-google-oidc.js

const {GoogleAuth, auth, Compute} = require('google-auth-library');
const gauth = new Compute();

module.exports = (aud) => {
  return gauth.fetchIdToken(aud);
}

3) In your integration test file, add a beforeEach(() => {}) block to grab the tokens and register the intercepts.

beforeEach(() => {
  cy.task('generateOIDC', '<audience1>').as('frontendToken');
  cy.task('generateOIDC', '<audience2>').as('checkoutToken');

  cy.get('@frontendToken').then( (token) => {
    cy.intercept({
      hostname: 'www.example.com'
    }, (req) => {
      req.headers['Authorization'] = 'Bearer ' + token;
    });
  });

  cy.get('@checkoutToken').then( (token) => {
    cy.intercept({
      hostname: 'checkout.example.com'
    }, (req) => {
      req.headers['Authorization'] = 'Bearer ' + token;
    });
  });
})

As you can see, we are using cy.intercept, so once these are registered, you can use Cypress normally and your authentication tokens will be added transparently to all requests (providing the intercept covers them, see the first paramater, currently set to www.example.com or checkout.example.com above).

This does run at the start of every it('', () => {}) block due to the way state is handled & cleared within Cypress, but the metadata server handles credential & token caching already, so we don’t have to worry about it adding too much extra latency.

The above will work on any service with a GCE compatible metadata server, e.g. GCE, App Engine, Cloud Run, Cloud Functions, GKE with Workload Identity, Cloud Build.