awslabs/aws-jwt-verify

`window` not defined in Next.js Edge Runtime

steveharrison opened this issue · 7 comments

I get the following error trying to import this library into the Next.js Edge Runtime, used by their Middleware feature:

wait  - compiling /_error (client and server)...
error - node_modules/aws-jwt-verify/dist/esm/node-web-compat-web.js (55:0) @ <unknown>
error - window is not defined
null

This is my code in middleware.ts:

import { NextRequest, NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { CognitoJwtVerifier } from "aws-jwt-verify";

export const config = {
  matcher: '/api/user/:path*',
}

async function isAuthenticated() {
  // Verifier that expects valid access tokens:
  const verifier = CognitoJwtVerifier.create({
    userPoolId: "[userPoolId]",
    tokenUse: "access",
    clientId: "[clientId]",
  });

  try {
    const payload = await verifier.verify(
      "eyJraWQeyJhdF9oYXNoIjoidk..." // the JWT as string
    );
    console.log("Token is valid. Payload:", payload);
  } catch {
    console.log("Token not valid!");
  }
}

export async function middleware(request: NextRequest) {
  console.log('isAuthenticated: ', await isAuthenticated());
}

This is because the node-web-compat-web compatibility layer is being used, but this isn't a true web environment so window is not available. All of the methods that this library needs, like crypto and setTimeout are available in this environment, just not on window. More info on the Next.js Edge Runtime here: https://nextjs.org/docs/api-reference/edge-runtime.

Any ideas on how to get this library working in this environment?

Versions
aws-jwt-verify: 3.3.0
Next.js: 13.1.2
Node: 16.17.0

Are you using the library in Node.js or in the Web browser?

Neither, using it in Next.js Edge Runtime (https://nextjs.org/docs/api-reference/edge-runtime).

Thanks for the report.

Interesting case, caused by the specifics of the Next.js Edge Runtime. Was able to reproduce, and work around by doing this trick:

// Mock window
globalThis.window = {
  crypto,
  setTimeout,
};

// Must use dynamic import
const verifierPromise = import("aws-jwt-verify").then(({ CognitoJwtVerifier}) => CognitoJwtVerifier.create({
  userPoolId: ".........",
  clientId: ".........",
  tokenUse: "id",
}));

export async function middleware(request: NextRequest) {
  const verifier = await verifierPromise;
  const payload = await verifier.verify(
    "jwt"
  );
  console.log(payload);
  return NextResponse.next();
}

I think we can solve this on our side by not explicitly using the window symbol in our calls, browser don't need that anyway. E.g. we can do crypto.subtle instead of window.crypto.subtle. Then you don't need the above trick.

So we'll treat this as a bug report.

This solution worked for me in dev mode, but when deployed on Vercel, the same error occurred as without the fix

Perhaps instead of:

globalThis.window = {
  crypto,
  setTimeout,
};

Do:

var window = globalThis;

Or maybe:

var window = global;

Just guessing! Not sure about the specifics of Vercel's edge runtime.

Or edit node_modules/aws-jwt-verify/dist/esm/node-web-compat-web.js and remove all references of window. Change window.setTimeout.bind(window) to setTimeout.bind(undefined)

Looking over https://edge-runtime.vercel.sh/features/available-apis and appears that globalThis is defined, but maybe "frozen" and can't be updated

Maybe try something like this that uses Object.setPrototypeOf()

let window = {
  crypto,
  setTimeout,
};
Object.setPrototypeOf(window, globalThis)

Fixed in v3.4.0