awslabs / aws-jwt-verify

JS library for verifying JWTs signed by Amazon Cognito, and any OIDC-compatible IDP that signs JWTs with RS256, RS384, and RS512
Apache License 2.0
598 stars 43 forks source link

`window` not defined in Next.js Edge Runtime #108

Closed steveharrison closed 1 year ago

steveharrison commented 1 year ago

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).

ottokruse commented 1 year ago

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.

Emilcrafter commented 1 year ago

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

ottokruse commented 1 year ago

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.

ottokruse commented 1 year ago

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)

hakanson commented 1 year ago

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)
Emilcrafter commented 1 year ago

110

ottokruse commented 1 year ago

Fixed in v3.4.0