awslabs / aws-jwt-verify

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

[BUG] Error: Cannot find module '#node-web-compat' #69

Closed Robstaa closed 2 years ago

Robstaa commented 2 years ago

Describe the bug When starting my typescript application, I immediately get the error

Error: Cannot find module '#node-web-compat'

The stack is the following:

- /Users/robstaa/code/comp/api/node_modules/aws-jwt-verify/dist/cjs/https.js
- /Users/robstaa/code/comp/api/node_modules/aws-jwt-verify/dist/cjs/jwk.js
- /Users/robstaa/code/comp/api/node_modules/aws-jwt-verify/dist/cjs/jwt-rsa.js
- /Users/robstaa/code/comp/api/node_modules/aws-jwt-verify/dist/cjs/index.js
- /Users/robstaa/code/comp/api/src/domains/authentication/verifyToken.ts

This is the verifyToken.ts file:

import { UnauthorizedError } from 'routing-controllers';
import { CognitoJwtVerifier } from 'aws-jwt-verify';
import { JwtExpiredError } from 'aws-jwt-verify/error';
import { TokenExpiredError } from './errors';

export const getToken = (authorization): string => {
    if (!authorization || authorization.split(' ')[0] !== 'Bearer') {
        throw new UnauthorizedError('No token provided');
    }

    const token = authorization.split(' ')[1];

    return token;
};

export const verifyToken = async (authorization): Promise<any> => {
    const verifier = CognitoJwtVerifier.create({
        userPoolId: process.env.COGNITO_USER_POOL_ID as string,
        clientId: process.env.COGNITO_CLIENT_ID as string,
        tokenUse: 'access',
    });
    try {    
        const token = getToken(authorization);
        const payload = await verifier.verify(token);
        return payload;
    } catch (e: any) {
        if (e instanceof JwtExpiredError) {
            throw new TokenExpiredError();
        } else {
            throw new UnauthorizedError(e.message);
        }
    }
};

Versions Which version of aws-jwt-verify are you using? => "^3.0.0" Are you using the library in Node.js or in the Web browser? => Node.js If Node.js, which version of Node.js are you using? (Should be at least 14) => 17.3.0 If using TypeScript, which version of TypeScript are you using? (Should be at least 4) => "^4.5.4" Also, to run the application in dev I am using ts-node-dev ("^1.1.8")

Robstaa commented 2 years ago

Just saw that this is a duplicate of https://github.com/awslabs/aws-jwt-verify/issues/66

hakanson commented 2 years ago

@Robstaa - did #66 solve your issue? Can this bug be closed?

ottokruse commented 2 years ago

Just ran (a slightly modified version of) your code with ts-node-dev and that works fine for me. What exact versions does your [INFO] log line reveal?

npx ts-node-dev index.ts
[INFO] 08:56:57 ts-node-dev ver. 1.1.8 (using ts-node ver. 9.1.1, typescript ver. 4.6.3)

The modified version of your code (index.ts):

import { CognitoJwtVerifier } from 'aws-jwt-verify';
import { JwtExpiredError } from 'aws-jwt-verify/error';

class UnauthorizedError extends Error {}
class TokenExpiredError extends Error {}

export const getToken = (authorization: any): string => {
    if (!authorization || authorization.split(' ')[0] !== 'Bearer') {
        throw new UnauthorizedError('No token provided');
    }

    const token = authorization.split(' ')[1];

    return token;
};

export const verifyToken = async (authorization: any): Promise<any> => {
    const verifier = CognitoJwtVerifier.create({
        userPoolId: process.env.COGNITO_USER_POOL_ID as string,
        clientId: process.env.COGNITO_CLIENT_ID as string,
        tokenUse: 'access',
    });
    try {    
        const token = getToken(authorization);
        const payload = await verifier.verify(token);
        return payload;
    } catch (e: any) {
        if (e instanceof JwtExpiredError) {
            throw new TokenExpiredError();
        } else {
            throw new UnauthorizedError(e.message);
        }
    }
};
// tsconfig.json
{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
  }
}
Robstaa commented 2 years ago

Hi @ottokruse and @hakanson thanks for the quick reply! Unfortunately I could not fix the issue yet, I am currently using https://github.com/ghdna/cognito-express but would like to replace that with aws-jwt-verify.

When I start the development env, this is the output:

[INFO] 09:45:22 ts-node-dev ver. 1.1.8 (using ts-node ver. 9.1.1, typescript ver. 4.5.4)
Error: Cannot find module '#node-web-compat'
Require stack:
...

The tsconfig.json looks like this:

{
  "compilerOptions": {
    "baseUrl": ".",
    "declaration": true,
    "declarationMap": true,
    "emitDecoratorMetadata": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "forceConsistentCasingInFileNames": true,
    "importHelpers": true,
    "incremental": true,
    "lib": ["es7"],
    "module": "commonjs",
    "moduleResolution": "node",
    "noFallthroughCasesInSwitch": true,
    "noImplicitAny": false,
    "noImplicitReturns": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "outDir": "dist",
    "resolveJsonModule": true,
    "sourceMap": true,
    "strict": true,
    "strictPropertyInitialization": false,
    "target": "es2017",
    "typeRoots": ["./node_modules/@types"],
    "types": ["reflect-metadata", "node"],
    "useUnknownInCatchVariables": true,
  },
  "include": ["src/**/*.*"],
  "exclude": ["node_modules", "src/**/*.test.ts", "**/__mocks__/*", "**/__mockData__/"]
}
hakanson commented 2 years ago

Any chance you can create a minimal express app to demonstrate and host in a GitHub repo? That would be easier for us to git clone and find the problem, then we can PR back the fix.

ottokruse commented 2 years ago

Any update @Robstaa ?

lemiesz commented 2 years ago

I have a similar issue. Trying to bundle my function to be deployed as a edge lambda with

      const secureInterceptorNodejsFunction = new NodeJSLambda.NodejsFunction(
            this,
            'SecureInterceptorNodejsFunction',
            {
                bundling: {
                    define: {
                        'process.env.USER_POOL_ID': JSON.stringify('us-east-1_gEQ7Fw07X'),
                        'process.env.CLIENT_ID': JSON.stringify('5c5j92ba0b8nkrgulve36vfhfs'),
                    },
                    externalModules: ['#node-web-compact'],
                    minify: true,
                },
                entry: path.join(__dirname, '../authenticate-cognito-lambda/index.js'),
                awsSdkConnectionReuse: false,
                functionName: props?.functionName,
                role: this.role,
                runtime: MoteLambda.SecureRuntime.NODEJS_14_X,
                timeout: Duration.seconds(5),
                tracing: Lambda.Tracing.ACTIVE,
            },
        );

Getting the following error

 > node_modules/aws-jwt-verify/dist/cjs/https.js:9:35: error: Could not resolve "#node-web-compat" (mark it as external to exclude it from the bundle, or surround it with try/catch to handle the failure at run-time)

Edit:

Downgrading to 2.1.3 seems to have resolved the issue. Although issue is present in 3.0

ottokruse commented 2 years ago

@lemiesz don't mark #node-web-compat as external module cause it isn't an external module.

When do you get that error?

Just tested and this CDK synths flawlessly for me:

import { Stack, StackProps, aws_lambda_nodejs } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as path from "path";

export class JwttestStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const secureInterceptorNodejsFunction =
      new aws_lambda_nodejs.NodejsFunction(
        this,
        "SecureInterceptorNodejsFunction",
        {
          bundling: {
            define: {
              "process.env.USER_POOL_ID": JSON.stringify("<some_id>"),
              "process.env.CLIENT_ID": JSON.stringify("<some_id>"),
            },
            minify: true,
          },
          entry: path.join(
            __dirname,
            "../authenticate-cognito-lambda/index.js"
          ),
        }
      );
  }
}
ottokruse commented 2 years ago

Closing for now -- will re-open if you can provide a reproducible sample.

lemiesz commented 2 years ago

Error happens during bundling. However I see you are using CDK2.

I'm still on an old version (until I can upgrade the rest of our stack) "monocdk": "1.145.0",. Downgrading to "aws-jwt-verify": "^2.1.3" allows me to use this without issue. However with version 3.0.0 It fails.

I've tried both with it marked as external (as per the error message recommendation), and without. Fails same way

Seems its just not compatible with monocdk

ottokruse commented 2 years ago

CDK2 uses esbuild for bundling, and esbuild has supported subpath imports since Oct 2021 (https://github.com/evanw/esbuild/issues/1691). Not sure what monocdk uses for bundling, maybe an older version of esbuild––you might try updating that. Or don't bundle ...

lemiesz commented 2 years ago

monocdk does indeed use esbuild.

Looks like I had installed 0.12.28, but it wasnt until 0.13.13 that the subpath imports change was merged.

Upgrading to the a newer version of esbuild has fixed my issues... thanks!

SethO commented 2 years ago

Currently produces this error if you run this library in Jest tests with either esbuild-jest (v0.5.0) or @swc/jest (v0.2.22). I imagine both are tied to versions of esbuild that are incompatible.

The workaround from https://github.com/ottokruse/jest-subpath-import/blob/main/jest.config.fix.js works for me.