DEV Community

Ryan Gard
Ryan Gard

Posted on

Speed Up Cypress Testing of NextAuth Secured Web Apps

One of the biggest headaches around end-to-end testing is dealing with authentication/authorization. With the rapid adoption of Next.js there has also been a corresponding increase in the use of NextAuth which is the preferred method for doing authentication/authorization with Next.js.

The recommended approaches (e.g. NextAuth, Cypress) for authenticating a Cypress test session usually involves going through a full login flow with a given authorization provider. The full login flow approach significantly increases the time to execute a test run and is still subject to failure when the underlying provider changes elements in the login flow interface.

However, if you're using NextAuth with the JWT session strategy you can skip the login flow completely for ANY provider by directly writing the next-auth.session-token cookie directly which will grant the session full authentication! This approach simplifies end-to-end testing with Cypress as well as reduces test time significantly regardless of the chosen authorization provider.

This article will cover how to configure Cypress to perform end-to-end testing on Next.js web app that has all pages and routes secured by NextAuth.

Example Web App

The full source code of the examples shown in this article can be found here. The example web app is based on the react-note-taking-app built on top of the T3 Stack. The Cypress tests and configuration are based on the best practices documented in the cypress-realworld-app.

This article is only going to cover a narrow portion of source code found in the example repo. If you're looking for more detailed information about some of the source code in the example repo then I suggest referring to the links above.

Configure NextAuth

In the [...nextauth].ts file enable the JWT session strategy for your chosen provider:

import NextAuth, { type NextAuthOptions } from "next-auth";
import DiscordProvider from "next-auth/providers/discord";
import { PrismaAdapter } from "@next-auth/prisma-adapter";

import { env } from "../../../env/server.mjs";
import { prisma } from "../../../server/db";

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    DiscordProvider({
      clientId: env.DISCORD_CLIENT_ID,
      clientSecret: env.DISCORD_CLIENT_SECRET,
    }),
  ],
  pages: {
    signIn: "/auth/login",
    signOut: "/",
    error: "/auth/unauthorized",
  },
  session: {
    strategy: "jwt",
  },
};

export default NextAuth(authOptions);
Enter fullscreen mode Exit fullscreen mode

Configure Cypress

In the cypress.config.ts file expose the NEXTAUTH_SECRET to the Cypress.env command and disable chromeWebSecurity which will allow writing insecure cookies:

import { defineConfig } from "cypress";
import { loadEnvConfig } from "@next/env";

loadEnvConfig(process.cwd());

export default defineConfig({
  env: {
    database_url: process.env.DATABASE_URL,
    nextauth_secret: process.env.NEXTAUTH_SECRET,
    mobileViewportWidthBreakpoint: 414,
  },
  e2e: {
    baseUrl: "http://localhost:3000",
    specPattern: "cypress/tests/**/*.spec.{js,jsx,ts,tsx}",
    supportFile: "cypress/support/e2e.ts",
    chromeWebSecurity: false,
    viewportHeight: 1000,
    viewportWidth: 1280,
  },
});
Enter fullscreen mode Exit fullscreen mode

Create a Custom Command for NextAuth Login

Create a custom command that will write a properly encoded next-auth.session-token cookie using the provided NEXTAUTH_SECRET. (nextAuth.ts)

import { v4 as uuidv4 } from "uuid";
import { encode } from "next-auth/jwt";
import type { JWT } from "next-auth/jwt";

// Custom command for automagically authenticating using next-auth cookies.
// Note: this function leaves you on a blank page, so you must call cy.visit()
// afterwards, before continuing with your test.
Cypress.Commands.add("loginNextAuth", ({ userId, name, email, provider }: loginNextAuthParams) => {
  Cypress.log({
    displayName: "NEXT-AUTH LOGIN",
    message: [`🔐 Authenticating | ${name}`],
  });

  const dateTimeNow = Math.floor(Date.now() / 1000);
  const expiry = dateTimeNow + 30 * 24 * 60 * 60; // 30 days
  const cookieName = "next-auth.session-token";
  const cookieValue: JWT = {
    sub: userId,
    name: name,
    email: email,
    provider: provider,
    picture: `https://via.placeholder.com/200/7732bb/c0392b.png?text=${name}`,
    tokenType: "Bearer",
    accessToken: "dummy",
    iat: dateTimeNow,
    exp: expiry,
    jti: uuidv4(),
  };

  // https://docs.cypress.io/api/utilities/promise#Waiting-for-Promises
  cy.wrap(null, { log: false }).then(() => {
    return new Cypress.Promise(async (resolve, reject) => {
      try {
        const encryptedCookieValue = await encode({ token: cookieValue, secret: Cypress.env("nextauth_secret") });

        cy.setCookie(cookieName, encryptedCookieValue, {
          log: false,
          httpOnly: true,
          path: "/",
          expiry: expiry,
        });

        resolve();
      } catch (err) {
        console.error(err);
        reject();
      }
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

Use the NextAuth Login Command in a Test

Now we can use the custom command to login! (auth.spec.ts)

import { testCreds } from "../support/testData";

describe("given an unauthenticated session", () => {
  before(() => {
    cy.resetDatabase();
  });

  beforeEach(() => {
    cy.loginNextAuth(testCreds);

    cy.visit("/");
  });

  describe("when generating a valid JWT and visiting the site", () => {
    it("should show the home page of an authenticated user", () => {
      cy.findByLabelText("logout-button").should("be.visible");
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

Conclusion

This method of login is a great way to speed up Cypress tests that are currently using a full login flow. I don't think this method completely replaces the need for testing a full login flow, but for the vast majority of tests it would be best to save time using the method outlined in the article.

Check out the example repo for this article and run the Cypress test suite yourself to get a feel for how fast this login technique can be for your project(s).

Top comments (0)