Code Monkey home page Code Monkey logo

azure-ad-verify-token's Introduction

NPM Version CI Codecov

Azure AD Verify Token

Verify JWT issued by Azure Active Directory B2C.

Table of Contents

Features

  • ๐ŸŽ‰ Verify JWT issued by Azure Active Directory B2C.
  • ๐Ÿš€ Automatically use the rotated public keys from Azure.
  • ๐Ÿ’ช Written in TypeScript.
  • โ™ป๏ธ Configurable cache for public keys.

Installation

npm install azure-ad-verify-token --save

Usage

Verify

import { verify, VerifyOptions } from 'azure-ad-verify-token';

const options: VerifyOptions = {
  jwksUri: 'https://login.microsoftonline.com/common/discovery/keys',
  issuer: 'https://login.microsoftonline.com/<tenant_id>/v2.0',
  audience: '<client_id>',
};

verify(token, options)
  .then((decoded) => {
    // verified and decoded token
    console.log(decoded);
  })
  .catch((error) => {
    // invalid token
    console.error(error);
  });

Verify options:

Property Type Description
jwksUri string jwk_uri value obtained from B2C policy metadata endpoint.
issuer string issuer value obtained from B2C policy metadata endpoint.
audience string Application ID of the application accessing the tenant.

Example metadata endpoints:

Configuration

import { setConfig } from 'azure-ad-verify-token';

setConfig({
  cacheLifetime: 12 * (60 * 60 * 1000), // 12 hours
});

Configuration options:

Property Type Description Default
cacheLifetime number Number of milliseconds to cache public keys. 1 hour

References

Development

npm install
npm run build

azure-ad-verify-token's People

Contributors

github-actions[bot] avatar iamshubhamjangle avatar justinlettau avatar kylefarris avatar leonardobispo avatar renovate-bot avatar renovate[bot] avatar scaccogatto avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

azure-ad-verify-token's Issues

ESM module build failing because 'jsonwebtoken' module changed to CommonJs

Describe the Issue

While updating to the version 3.0.2 ESM module start complaining about 'verify' method not importable. It is working fine in version 3.0.0

[2023-07-11T11:43:35.137Z] Worker was unable to load function testFun: 'Named export 'verify' not found. The requested module 'jsonwebtoken' is a CommonJS module, which may not support all module.exports as named exports.
[2023-07-11T11:43:35.137Z] CommonJS modules can always be imported via the default export, for example using:
[2023-07-11T11:43:35.137Z]
[2023-07-11T11:43:35.137Z] import pkg from 'jsonwebtoken';
[2023-07-11T11:43:35.137Z] const { decode: jwtDecode, verify: jwtVerify } = pkg;
[2023-07-11T11:43:35.137Z] '
[2023-07-11T11:43:35.138Z] Worker was unable to load function testFun: 'Named export 'verify' not found. The requested module 'jsonwebtoken' is a CommonJS module, which may not support all module.exports as named exports.

Expected behavior

No issue in importing verify method and no build issue for 'jsonwebtoken'

Steps to Reproduce

n/a

Other Information

tested in 3.0.2 where the error persist, downgrading to lower version 3.0.0 resolved the issue.

Optimise cache

Describe the Issue

If two simultaneous requests use verify(), the jwksUri will be fetched twice.

Expected behavior

It should only fetch once per jwksUri.

Steps to Reproduce

await verify(a, config)
await verify(a, config)

Other Information

Change the cache to Map<string, Promise> and save the fetch Promise.

Proxy or custom requestClient configuration (aplications running behinden enterprise proxy)

Describe the Feature

Implement a way to informe a proxyAgent instance or proxy configurations to the node-fetch to aplications running behinden enterprise proxy;

Suggested Solution

Somenting like msal-node Configuration.system.networkClient
https://azuread.github.io/microsoft-authentication-library-for-js/ref/types/_azure_msal_node.NodeSystemOptions.html#__type.networkClient

Other Information

INetworkModule example (usede in SAML)

import type { INetworkModule, NetworkRequestOptions, NetworkResponse } from "@azure/msal-node";
import { HttpsProxyAgent } from "https-proxy-agent";
import fetch from 'node-fetch';

const proxyUrl = process.env['HTTPS_PROXY'] || process.env['HTTP_PROXY'];
if (!proxyUrl) {
    throw new Error('Missing HTTP/S_PROXY env');
}

export const proxyAgent = new HttpsProxyAgent(proxyUrl);

export class CustomHttpClient implements INetworkModule {
    sendGetRequestAsync<T>(url: string, options?: NetworkRequestOptions): Promise<NetworkResponse<T>> {
        return this.sendRequestAsync(url, 'GET', options);
    }
    sendPostRequestAsync<T>(url: string, options?: NetworkRequestOptions): Promise<NetworkResponse<T>> {
        return this.sendRequestAsync(url, 'POST', options);
    }

    private async sendRequestAsync<T>(
        url: string,
        method: 'GET' | 'POST',
        options: NetworkRequestOptions = {},
    ): Promise<NetworkResponse<T>> {
        try {
            const requestOptions = {
                method: method,
                headers: options.headers,
                body: method === 'POST' ? options.body : undefined,
                agent: proxyAgent,
            };

            console.log('>>> url', url, requestOptions);

            const response = await fetch(url, requestOptions);
            const data = await response.json() as any;

            const headersObj: Record<string, string> = {};
            response.headers.forEach((value, key) => {
                headersObj[key] = value;
            });

            return {
                headers: headersObj,
                body: data,
                status: response.status,
            };
        } catch (err) {
            console.error('CustomRequest', err);
            throw new Error('Custom request error');
        }
    }
}

Handle v2.0 nonce

Describe the Issue

When verifying a token provided by AzureAD that is from the v2.0 endpoint, a nonce is included. That nonce is actually SHA-256'd before the signature is calculated, but the non-SHA'd version is sent along in the token. Therefore, the signature will fail to verify without fixing the nonce prior to verification.

Expected behavior

The signature would verify

Steps to Reproduce

  1. Get a token from the v2.0 endpoint.
  2. Attempt to verify it.

Other Information

As a quick fix, I'm using the following code to fix the nonce prior to calling the verify function:

const SHA2 = require("sha2");
const base64url = require('base64url');
const { verify } = require('azure-ad-verify-token');

const doVerify = (token, options) => {

  const [header_raw, body_raw, sig] = token.split('.');

  const header_decoded = JSON.parse(base64url.decode(header_raw));
  header_decoded.nonce = base64url.encode(SHA2["SHA-256"](header_decoded.nonce));

  const new_header_raw = base64url.encode(JSON.stringify(header_decoded));

  const new_token = `${new_header_raw}.${body_raw}.${sig}`;

  return verify(new_token, options);
};

Validation of a foreign token makes the lib get stuck on a never resolved promise

Describe the Issue

When I try to validate a token which is not signed with any of the key-id's which are available in the jwksUri, then the library gets completly stuck, because the cached promise will never resolve nor reject.

In my case we have support for multiple IDPs and the token to validate was from another IDP as the jwksUri points to.

To solve this, the logic for fetching from the jwksUri and caching public keys by key-id need to be changed.

Expected behavior

The library should return that the token is invalid or should return that for the key-id of the token, there was no PublicKey found.
The library should not return an ever-pending promise.

Steps to Reproduce

Try to validate a token which is from another IDP than the jwksUri.

Other Information

In theory, the current implementation of verify.ts should also have the issue, that, if there is a network problem and fetching the jwksUri fails, there will be an ever-pending promise in the cache which then leads to a similar issue that the lib gets completly stuck. But this case I have not explicitly tested.

Import failure

Import of verify function fails

I'm facing a similar problem that was described in issue#45. In my nestjs project, I can import verify function from the package without any typescript warnings, but the compilation fails with following message:

Error [ERR_REQUIRE_ESM]: require() of ES Module ...\node_modules\azure-ad-verify-token\dist\index.js from ...\my-project-file.js not supported.
Instead change the require of index.js in ...\my-project-file.js to a dynamic import() which is available in all CommonJS modules.

Using a dynamic import would be a bit annoying as I cannot use top-level await due to standard nestjs project configuration, which I'm not allowed to alter easily.

When I try dynamic import in a function (with regards to the advice from issue#47), the code compiles. However, strange enough, when the function with dynamic import runs, the app crashes with the very same error message - including the "change to a dynamic import()" hint.

Expected behavior

I can import verify function from the package and use it without any compilation errors or application crashes.

Steps to Reproduce

I'm trying to use the package (version 3.0.0, but downgrading to 2.0.1 doesn't help) in a standard nestjs project generated from Nest CLI.

From project's package.json:

...
"dependencies": {
    "@nestjs/common": "^9.0.0",
    "@nestjs/config": "^2.2.0",
    "@nestjs/core": "^9.0.0",
    "@nestjs/mongoose": "^9.2.1",
    "@nestjs/platform-express": "^9.0.0",
    "azure-ad-verify-token": "^3.0.0",
    "class-transformer": "^0.5.1",
    "class-validator": "^0.14.0",
    "cookie-session": "^2.0.0",
    "mongoose": "^6.7.3",
    "reflect-metadata": "^0.1.13",
    "rimraf": "^3.0.2",
    "rxjs": "^7.2.0"
  },
"devDependencies": {
    ...
    "ts-loader": "^9.2.3",
    "ts-node": "^10.0.0",
    "tsconfig-paths": "4.1.0",
    "typescript": "^4.7.4"
  }
...

Here's my tsconfig.json:

"compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false
  }

The code looks like so:

import {verify, VerifyOptions} from 'azure-ad-verify-token';
// other imports

const options: VerifyOptions = {
  jwksUri: 'https://my-jwksUri',
  issuer: 'https://my-issuer',
  audience: 'my-audience'
};

@Injectable()
export class CurrentUserMiddleware implements NestMiddleware {
  constructor(private usersService: UsersService) {}

  async use(req: Request, res: Response, next: NextFunction) {
    const token = req.headers['authorization'];

    try {
      const resolved = await verify(token, options);
      console.log(resolved);
    } catch (err) {
      console.log('azure-ad-verify-token/verify FAILED');
      console.log(err);
    }

    // some more code
  }
}

Error JsonWebTokenError: invalid signature

I am facing issue in verifying my oauth ID token.

When i use the id token obtain from the aad_oauth flutter , i am facing a error "JWTVerification:: Error JsonWebTokenError: invalid signature". Do you know what is the id token failed the check?

When i use jwt.ms, everything look likes it is in order.

getPublicKey never resolves

Describe the Issue

If the kid is not found then getPublicKey never resolves. The current check introduced in #29 is not enough since the call to setDeferredItem before implies that even if the kid is not found the cache will contain an empty promise that will never resolve.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

github-actions
.github/workflows/ci.yml
  • actions/checkout v3
npm
package.json
  • jsonwebtoken ^8.5.1
  • node-fetch ^2.6.5
  • rsa-pem-from-mod-exp ^0.8.4
  • @types/jest ^28.1.6
  • @types/jsonwebtoken ^8.5.5
  • @types/node-fetch ^2.5.12
  • @typescript-eslint/eslint-plugin ^5.30.6
  • @typescript-eslint/parser ^5.30.6
  • eslint ^8.19.0
  • eslint-config-prettier ^8.5.0
  • husky ^8.0.1
  • jest ^28.1.3
  • nock ^13.2.8
  • prettier ^2.7.1
  • pretty-quick ^3.1.3
  • standard-version ^9.5.0
  • ts-jest ^28.0.6
  • ts-node ^10.9.1
  • typescript ^4.7.4
  • node ^12.20.0 || ^14.13.1 || >=16.0.0

  • Check this box to trigger a request for Renovate to run again on this repository

Broken imports on NextJS app

Describe the Issue

While trying to use this package in NextJS on server-side, error is thrown about jwt imports inside the package.

error file:///Users/tomas/projects/foo/node_modules/azure-ad-verify-token/dist/verify.js:1
import { decode as jwtDecode, verify as jwtVerify } from 'jsonwebtoken';
         ^^^^^^
SyntaxError: Named export 'decode' not found. The requested module 'jsonwebtoken' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'jsonwebtoken';
const { decode: jwtDecode, verify: jwtVerify } = pkg;

    at ModuleJob._instantiate (node:internal/modules/esm/module_job:123:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:189:5)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:530:24)
    at async importModuleDynamicallyWrapper (node:internal/vm/module:438:15) {
  digest: undefined
}

Changes to imports have been introduced in #56
I have installed 3.0.1 and it works fine.

dynamic import() error

Hello,
I am getting this error:
Worker was unable to load function etdoUploadFunc: 'require() of ES Module C:\projects\node_modules\azure-ad-verify-token\dist\index.js from C:\projects\api\dist\api\authMiddleware.js not supported. Instead change the require of index.js in C:\projects\api\dist\api\authMiddleware.js to a dynamic import() which is available in all CommonJS modules.'

but I am using import { verify, VerifyOptions } from 'azure-ad-verify-token';
version: "azure-ad-verify-token": "^3.0.1"

I also tried const { verify } = await import('azure-ad-verify-token'); but I am getting this error Result: Failure Exception: require() of ES Module

"Fixed bug for foreign token submission" still not able to handle jwt kid not in public keys

Type

What kind of issue is this?

[ V] Bug report.
[ ] Feature request.

Description the Issue

the solution of latest PR(#48) not working, as 'done' was set as resolve function, so !item.done won't help to validate if the item is empty. Maybe need to rethink if do you really need to set done as promise in setDeferredItem

Todo

Steps to Reproduce

pass a jwt which kid not in public key page of Azure AD
n/a

Other Information

n/a

Cannot read property 'header' of null

Invalid tokens might be null:
https://github.com/auth0/node-jsonwebtoken/blob/5f10bf9957a2541828501cfecab0310908b2f62f/decode.js#L6

That is why this code throws Cannot read property 'header' of null

decoded = jwt.decode(token, { complete: true, json: true });
kid = decoded.header.kid;

Instead, it would make sense to throw an 'invalid token' error like here:
https://github.com/auth0/node-jsonwebtoken/blob/5f10bf9957a2541828501cfecab0310908b2f62f/verify.js#L75

dynamic import() does not work

Describe the Issue

As I currently cannot import the package in our nx based project because of Error [ERR_REQUIRE_ESM]: require() of ES Module not supported. I wanted to use a dynamic import to load the package but it looks like it does not work neither.
const { verify } = import('azure-ad-verify-token');
console.log(verify) -> { default: {} }

Todo

Expected behavior

dynamic import loads the package correctly

Todo

Steps to Reproduce

npm install azure-ad-verify-token
const { verify } = import('azure-ad-verify-token');
verify() -> verify is not a function

n/a

Other Information

n/a

cannot seem to get the import to work correctly

Type

What kind of issue is this?

[x ] Bug report.
[ ] Feature request.

Description the Issue

No matter how I try this script (vanilla node.js or typescript) I cannot run verify b/c of module issues.

When running as ts-node, I get:

require() of ES Module ...node_modules/azure-ad-verify-token/dist/index.js from index.ts not supported.
to a dynamic import() which is available in all CommonJS modules.

when running in javascript

Cannot find module azure-ad-verify-token/dist/config' imported from ../node_modules/azure-ad-verify-token/dist/index.js

Todo

Try to use the library as illustrated

n/a

Other Information

n/a

Add expiry to the public key cache

Describe the Feature

Currently the public key cache value is set when the value is fetched and subsequent calls that need the public keys will get the cached version. Unless I missed something in the code, the cached value does not expire.
For short running applications this is not a problem.
But for long running applications like a rest-api this could be a problem once the keys in Azure are rotated and the application has not restarted.

Suggested Solution

Change the current Map<string, string> to a Map<string, object> where the object contains both an expiry value which should be a timestamp as well as the string.

When the value is initially populated set the expiry value to a timestamp in the future - e.g. expiry = new Date().getTime() + cacheLifeTimeMs where cacheLifeTimeMs is the allowed life time of the cached value in ms. The cacheLifeTimeMs could be a configurable value - and maybe default to a 1 hour life time if not configured.

On subsequent get calls to the cache, the cached value is ok if expiry > new Date().getTime() - otherwise it should be deleted, and a new fresh value should be fetched and cached.

Other Information

n/a
I will be happy to make a PR if such are accepted?

security vulnerabilities (npm audit)

Describe the Issue

npm audit and dependabots report two high-severity issues:

# npm audit report

json5  2.0.0 - 2.2.1
Severity: high
Prototype Pollution in JSON5 via Parse Method - https://github.com/advisories/GHSA-9c47-m6qq-7p4h
fix available via `npm audit fix`
node_modules/json5

jsonwebtoken  <=8.5.1
Severity: high
jsonwebtoken vulnerable to signature validation bypass due to insecure default algorithm in jwt.verify() - https://github.com/advisories/GHSA-qwph-4952-7xr6
jsonwebtoken has insecure input validation in jwt.verify function - https://github.com/advisories/GHSA-27h2-hvpr-p74q
fix available via `npm audit fix --force`
Will install [email protected], which is a breaking change
node_modules/jsonwebtoken

2 high severity vulnerabilities

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Todo

  • Bump json5 to >= 2.2.1
  • Bump jsonwebtoken to >= 9.0.0

by applying the --fixes

Consumers would not be polluted by audit reports, this package would be more secure and npm audit would be fixed.

Steps to Reproduce

  • Clone this repo
  • npm i && npm audit

Allow array of strings for issuer option

Describe the Feature

When verifying tokens the JWT library which is used in this project support issuer as a string or an array of strings. To align this project with that I suggest allowing issuer as a string or an array of strings. This functionality comes in handy in cases where we setup azure b2c custom domains in this scenario both the default azure domain and the custom domain should be regarded as valid issuers.

Suggested Solution

After taking a look at the code it looks like the change should be simple enough and I'm happy to push a pull request. Essentially we just need to change VerifyOptions interface to look like below.

export interface VerifyOptions {
  ...

  issuer: string | string[];

  ...
}

Other Information

n/a

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.