express-oauth2-jwt-bearer
Advanced tools
Comparing version 1.3.0 to 1.4.0
@@ -98,6 +98,14 @@ /// <reference types="node" /> | ||
interface JWTPayload { | ||
/** JWT Issuer - [RFC7519#section-4.1.1](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.1). */ | ||
/** | ||
* JWT Issuer | ||
* | ||
* @see [RFC7519#section-4.1.1](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.1) | ||
*/ | ||
iss?: string | ||
/** JWT Subject - [RFC7519#section-4.1.2](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.2). */ | ||
/** | ||
* JWT Subject | ||
* | ||
* @see [RFC7519#section-4.1.2](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.2) | ||
*/ | ||
sub?: string | ||
@@ -108,12 +116,28 @@ | ||
/** JWT ID - [RFC7519#section-4.1.7](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.7). */ | ||
/** | ||
* JWT ID | ||
* | ||
* @see [RFC7519#section-4.1.7](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.7) | ||
*/ | ||
jti?: string | ||
/** JWT Not Before - [RFC7519#section-4.1.5](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.5). */ | ||
/** | ||
* JWT Not Before | ||
* | ||
* @see [RFC7519#section-4.1.5](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.5) | ||
*/ | ||
nbf?: number | ||
/** JWT Expiration Time - [RFC7519#section-4.1.4](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.4). */ | ||
/** | ||
* JWT Expiration Time | ||
* | ||
* @see [RFC7519#section-4.1.4](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.4) | ||
*/ | ||
exp?: number | ||
/** JWT Issued At - [RFC7519#section-4.1.6](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6). */ | ||
/** | ||
* JWT Issued At | ||
* | ||
* @see [RFC7519#section-4.1.6](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6) | ||
*/ | ||
iat?: number | ||
@@ -148,2 +172,3 @@ | ||
timeoutDuration?: number; | ||
cacheMaxAge?: number; | ||
validators?: Partial<Validators>; | ||
@@ -196,2 +221,5 @@ clockTolerance?: number; | ||
interface AuthOptions extends JwtVerifierOptions { | ||
authRequired?: boolean; | ||
} | ||
declare global { | ||
@@ -204,3 +232,3 @@ namespace Express { | ||
} | ||
declare const auth: (opts?: JwtVerifierOptions) => Handler; | ||
declare const auth: (opts?: AuthOptions) => Handler; | ||
declare const claimCheck: ClaimCheck<Handler>; | ||
@@ -212,2 +240,2 @@ declare const claimEquals: ClaimEquals<Handler>; | ||
export { JwtVerifierOptions as AuthOptions, VerifyJwtResult as AuthResult, FunctionValidator, InsufficientScopeError, InvalidRequestError, InvalidTokenError, JSONPrimitive, JWSHeaderParameters as JWTHeader, JWTPayload, UnauthorizedError, Validator, Validators, auth, claimCheck, claimEquals, claimIncludes, requiredScopes, scopeIncludesAny }; | ||
export { AuthOptions, VerifyJwtResult as AuthResult, FunctionValidator, InsufficientScopeError, InvalidRequestError, InvalidTokenError, JSONPrimitive, JWSHeaderParameters as JWTHeader, JWTPayload, UnauthorizedError, Validator, Validators, auth, claimCheck, claimEquals, claimIncludes, requiredScopes, scopeIncludesAny }; |
@@ -6,6 +6,4 @@ 'use strict'; | ||
var assert = require('assert'); | ||
var buffer = require('buffer'); | ||
var crypto = require('crypto'); | ||
var jose = require('jose'); | ||
var url = require('url'); | ||
var jose = require('jose'); | ||
var http = require('http'); | ||
@@ -15,2 +13,3 @@ var https = require('https'); | ||
var util = require('util'); | ||
var crypto = require('crypto'); | ||
@@ -132,3 +131,3 @@ class UnauthorizedError extends Error { | ||
const assertIssuer = (data) => assert.strict(data.issuer, `'issuer' not found in authorization server metadata`); | ||
const discover = async (uri, { agent, timeoutDuration } = {}) => { | ||
const discover = async ({ issuerBaseURL: uri, agent, timeoutDuration, }) => { | ||
const url$1 = new url.URL(uri); | ||
@@ -168,3 +167,37 @@ if (url$1.pathname.includes('/.well-known/')) { | ||
}; | ||
var discovery = (opts) => { | ||
let discovery; | ||
let timestamp = 0; | ||
return () => { | ||
const now = Date.now(); | ||
if (!discovery || now > timestamp + opts.cacheMaxAge) { | ||
timestamp = now; | ||
discovery = discover(opts).catch((e) => { | ||
discovery = undefined; | ||
throw e; | ||
}); | ||
} | ||
return discovery; | ||
}; | ||
}; | ||
var getKeyFn = ({ secret, cooldownDuration, timeoutDuration, cacheMaxAge, }) => { | ||
let getKeyFn; | ||
let prevjwksUri; | ||
const secretKey = secret && crypto.createSecretKey(Buffer.from(secret)); | ||
return (jwksUri) => { | ||
if (secretKey) | ||
return () => secretKey; | ||
if (!getKeyFn || prevjwksUri !== jwksUri) { | ||
prevjwksUri = jwksUri; | ||
getKeyFn = jose.createRemoteJWKSet(new URL(jwksUri), { | ||
cooldownDuration, | ||
timeoutDuration, | ||
cacheMaxAge, | ||
}); | ||
} | ||
return getKeyFn; | ||
}; | ||
}; | ||
var validate = (payload, header, validators) => Promise.all(Object.entries(validators).map(async ([key, validator]) => { | ||
@@ -231,5 +264,3 @@ const value = key === 'alg' || key === 'typ' ? header[key] : payload[key]; | ||
const SYMMETRIC_ALGS = ['HS256', 'HS384', 'HS512']; | ||
const jwtVerifier = ({ issuerBaseURL = process.env.ISSUER_BASE_URL, jwksUri = process.env.JWKS_URI, issuer = process.env.ISSUER, audience = process.env.AUDIENCE, secret = process.env.SECRET, tokenSigningAlg = process.env.TOKEN_SIGNING_ALG, agent, cooldownDuration = 30000, timeoutDuration = 5000, clockTolerance = 5, maxTokenAge, strict = false, validators: customValidators, }) => { | ||
let origJWKS; | ||
let discovery; | ||
const jwtVerifier = ({ issuerBaseURL = process.env.ISSUER_BASE_URL, jwksUri = process.env.JWKS_URI, issuer = process.env.ISSUER, audience = process.env.AUDIENCE, secret = process.env.SECRET, tokenSigningAlg = process.env.TOKEN_SIGNING_ALG, agent, cooldownDuration = 30000, timeoutDuration = 5000, cacheMaxAge = 600000, clockTolerance = 5, maxTokenAge, strict = false, validators: customValidators, }) => { | ||
let validators; | ||
@@ -243,25 +274,22 @@ let allowedSigningAlgs; | ||
assert.strict(!secret || (tokenSigningAlg && SYMMETRIC_ALGS.includes(tokenSigningAlg)), `You must supply one of ${SYMMETRIC_ALGS.join(', ')} for 'tokenSigningAlg' to validate symmetrically signed tokens`); | ||
const secretKey = secret && crypto.createSecretKey(buffer.Buffer.from(secret)); | ||
const JWKS = async (...args) => { | ||
if (secretKey) | ||
return secretKey; | ||
if (!origJWKS) { | ||
origJWKS = jose.createRemoteJWKSet(new url.URL(jwksUri), { | ||
agent, | ||
cooldownDuration, | ||
timeoutDuration, | ||
}); | ||
} | ||
return origJWKS(...args); | ||
}; | ||
const getDiscovery = discovery({ | ||
issuerBaseURL, | ||
agent, | ||
timeoutDuration, | ||
cacheMaxAge, | ||
}); | ||
const getKeyFnGetter = getKeyFn({ | ||
agent, | ||
cooldownDuration, | ||
timeoutDuration, | ||
cacheMaxAge, | ||
secret, | ||
}); | ||
return async (jwt) => { | ||
try { | ||
if (issuerBaseURL) { | ||
discovery = | ||
discovery || discover(issuerBaseURL, { agent, timeoutDuration }); | ||
({ | ||
jwks_uri: jwksUri, | ||
issuer, | ||
id_token_signing_alg_values_supported: allowedSigningAlgs, | ||
} = await discovery); | ||
const { jwks_uri: discoveredJwksUri, issuer: discoveredIssuer, id_token_signing_alg_values_supported: idTokenSigningAlgValuesSupported, } = await getDiscovery(); | ||
jwksUri = jwksUri || discoveredJwksUri; | ||
issuer = issuer || discoveredIssuer; | ||
allowedSigningAlgs = idTokenSigningAlgValuesSupported; | ||
} | ||
@@ -272,3 +300,3 @@ validators || (validators = { | ||
}); | ||
const { payload, protectedHeader: header } = await jose.jwtVerify(jwt, JWKS); | ||
const { payload, protectedHeader: header } = await jose.jwtVerify(jwt, getKeyFnGetter(jwksUri)); | ||
await validate(payload, header, validators); | ||
@@ -385,3 +413,8 @@ return { payload, header, token: jwt }; | ||
catch (e) { | ||
next(e); | ||
if (opts.authRequired === false) { | ||
next(); | ||
} | ||
else { | ||
next(e); | ||
} | ||
} | ||
@@ -388,0 +421,0 @@ }; |
{ | ||
"name": "express-oauth2-jwt-bearer", | ||
"description": "Authentication middleware for Express.js that validates JWT bearer access tokens.", | ||
"version": "1.3.0", | ||
"version": "1.4.0", | ||
"main": "dist/index.js", | ||
@@ -20,26 +20,26 @@ "types": "dist/index.d.ts", | ||
"devDependencies": { | ||
"@rollup/plugin-node-resolve": "^13.1.1", | ||
"@tsconfig/node12": "^1.0.9", | ||
"@types/express": "^4.17.13", | ||
"@types/jest": "^27.0.3", | ||
"@types/node": "^14.18.0", | ||
"@typescript-eslint/eslint-plugin": "^5.7.0", | ||
"@typescript-eslint/parser": "^5.7.0", | ||
"eslint": "^8.4.1", | ||
"express": "^4.17.1", | ||
"got": "^11.8.5", | ||
"jest": "^27.4.5", | ||
"jest-junit": "^13.0.0", | ||
"nock": "^13.2.1", | ||
"@rollup/plugin-node-resolve": "^13.3.0", | ||
"@tsconfig/node12": "^1.0.11", | ||
"@types/express": "^4.17.17", | ||
"@types/jest": "^27.5.2", | ||
"@types/node": "^14.18.42", | ||
"@typescript-eslint/eslint-plugin": "^5.57.0", | ||
"@typescript-eslint/parser": "^5.57.0", | ||
"eslint": "^8.37.0", | ||
"express": "^4.18.2", | ||
"got": "^11.8.6", | ||
"jest": "^29.5.0", | ||
"jest-junit": "^13.2.0", | ||
"nock": "^13.3.0", | ||
"prettier": "~2.5.1", | ||
"rimraf": "^3.0.2", | ||
"rollup": "^2.61.1", | ||
"rollup-plugin-dts": "^4.0.1", | ||
"rollup-plugin-typescript2": "^0.31.1", | ||
"ts-jest": "^27.1.1", | ||
"tslib": "^2.3.1", | ||
"typescript": "^4.5.4" | ||
"rollup": "^2.79.1", | ||
"rollup-plugin-dts": "^4.2.3", | ||
"rollup-plugin-typescript2": "^0.31.2", | ||
"ts-jest": "^29.0.5", | ||
"tslib": "^2.5.0", | ||
"typescript": "^5.0.2" | ||
}, | ||
"dependencies": { | ||
"jose": "^4.9.2" | ||
"jose": "^4.13.1" | ||
}, | ||
@@ -46,0 +46,0 @@ "engines": { |
@@ -16,2 +16,3 @@ ![Authentication middleware for Express.js that validates JWT Bearer Access Tokens](https://cdn.auth0.com/website/sdks/banners/express-oauth2-jwt-bearer-banner.png) | ||
## Getting started | ||
### Requirements | ||
@@ -21,3 +22,3 @@ | ||
- Node.js: `^12.19.0 || ^14.15.0 || ^16.13.0` | ||
- Node.js: `^12.19.0 || ^14.15.0 || ^16.13.0` || ^18.12.0 | ||
@@ -36,3 +37,3 @@ ### Installation | ||
The library requires [issuerBaseURL](https://auth0.github.io/node-oauth2-jwt-bearer/interfaces/authoptions.html#issuerbaseurl) and [audience](https://auth0.github.io/node-oauth2-jwt-bearer/interfaces/authoptions.html#audience). | ||
The library requires [issuerBaseURL](https://auth0.github.io/node-oauth2-jwt-bearer/interfaces/AuthOptions.html#issuerBaseURL) and [audience](https://auth0.github.io/node-oauth2-jwt-bearer/interfaces/AuthOptions.html#audience). | ||
@@ -56,6 +57,6 @@ #### Environment Variables | ||
app.use( | ||
auth({ | ||
issuerBaseURL: 'https://YOUR_ISSUER_DOMAIN', | ||
audience: 'https://my-api.com' | ||
}) | ||
auth({ | ||
issuerBaseURL: 'https://YOUR_ISSUER_DOMAIN', | ||
audience: 'https://my-api.com', | ||
}) | ||
); | ||
@@ -69,8 +70,8 @@ ``` | ||
app.use( | ||
auth({ | ||
issuer: 'https://YOUR_ISSUER_DOMAIN', | ||
audience: 'https://my-api.com', | ||
secret: 'YOUR SECRET', | ||
tokenSigningAlg: 'HS256' | ||
}) | ||
auth({ | ||
issuer: 'https://YOUR_ISSUER_DOMAIN', | ||
audience: 'https://my-api.com', | ||
secret: 'YOUR SECRET', | ||
tokenSigningAlg: 'HS256', | ||
}) | ||
); | ||
@@ -84,10 +85,8 @@ ``` | ||
```js | ||
app.get('/api/messages', | ||
(req, res, next) => { | ||
const auth = req.auth; | ||
auth.header; // The decoded JWT header. | ||
auth.payload; // The decoded JWT payload. | ||
auth.token; // The raw JWT token. | ||
} | ||
); | ||
app.get('/api/messages', (req, res, next) => { | ||
const auth = req.auth; | ||
auth.header; // The decoded JWT header. | ||
auth.payload; // The decoded JWT payload. | ||
auth.token; // The raw JWT token. | ||
}); | ||
``` | ||
@@ -114,10 +113,11 @@ | ||
- [auth](https://auth0.github.io/node-oauth2-jwt-bearer#auth) - Middleware that will return a 401 if a valid Access token JWT bearer token is not provided in the request. | ||
- [AuthResult](https://auth0.github.io/node-oauth2-jwt-bearer/interfaces/authresult.html) - The properties added to `req.auth` upon successful authorization. | ||
- [requiredScopes](https://auth0.github.io/node-oauth2-jwt-bearer#requiredscopes) - Check a token's scope claim to include a number of given scopes, raises a 403 `insufficient_scope` error if the value of the scope claim does not include all the given scopes. | ||
- [claimEquals](https://auth0.github.io/node-oauth2-jwt-bearer#claimequals) - Check a token's claim to be equal a given JSONPrimitive (string, number, boolean or null) raises a 401 `invalid_token` error if the value of the claim does not match. | ||
- [claimIncludes](https://auth0.github.io/node-oauth2-jwt-bearer#claimincludes) - Check a token's claim to include a number of given JSONPrimitives (string, number, boolean or null) raises a 401 `invalid_token` error if the value of the claim does not include all the given values. | ||
- [claimCheck](https://auth0.github.io/node-oauth2-jwt-bearer#claimcheck) - Check the token's claims using a custom method that receives the JWT Payload and should return `true` if the token is valid. Raises a 401 `invalid_token` error if the function returns `false`. | ||
- [auth](https://auth0.github.io/node-oauth2-jwt-bearer/functions/auth.html) - Middleware that will return a 401 if a valid Access token JWT bearer token is not provided in the request. | ||
- [AuthResult](https://auth0.github.io/node-oauth2-jwt-bearer/interfaces/AuthResult.html) - The properties added to `req.auth` upon successful authorization. | ||
- [requiredScopes](https://auth0.github.io/node-oauth2-jwt-bearer/functions/requiredScopes.html) - Check a token's scope claim to include a number of given scopes, raises a 403 `insufficient_scope` error if the value of the scope claim does not include all the given scopes. | ||
- [claimEquals](https://auth0.github.io/node-oauth2-jwt-bearer/functions/claimEquals.html) - Check a token's claim to be equal a given JSONPrimitive (string, number, boolean or null) raises a 401 `invalid_token` error if the value of the claim does not match. | ||
- [claimIncludes](https://auth0.github.io/node-oauth2-jwt-bearer/functions/claimIncludes.html) - Check a token's claim to include a number of given JSONPrimitives (string, number, boolean or null) raises a 401 `invalid_token` error if the value of the claim does not include all the given values. | ||
- [claimCheck](https://auth0.github.io/node-oauth2-jwt-bearer/functions/claimCheck.html) - Check the token's claims using a custom method that receives the JWT Payload and should return `true` if the token is valid. Raises a 401 `invalid_token` error if the function returns `false`. | ||
## Feedback | ||
### Contributing | ||
@@ -153,2 +153,2 @@ | ||
This project is licensed under the MIT license. See the <a href="https://github.com/auth0/node-oauth2-jwt-bearer/blob/main/packages/express-oauth2-jwt-bearer/LICENSE"> LICENSE</a> file for more info. | ||
</p> | ||
</p> |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
31638
637
147
Updatedjose@^4.13.1