openid-client
Advanced tools
Comparing version 4.0.2 to 4.1.0
@@ -5,2 +5,9 @@ # Changelog | ||
## [4.1.0](https://github.com/panva/node-openid-client/compare/v4.0.2...v4.1.0) (2020-09-11) | ||
### Features | ||
* OAuth 2.0 DPoP in various relevant API interfaces ([44a0de7](https://github.com/panva/node-openid-client/commit/44a0de7ceb62cabacd62798ac136f1c394907028)) | ||
## [4.0.2](https://github.com/panva/node-openid-client/compare/v4.0.1...v4.0.2) (2020-09-11) | ||
@@ -7,0 +14,0 @@ |
@@ -376,3 +376,3 @@ /* eslint-disable max-classes-per-file */ | ||
checks = {}, | ||
{ exchangeBody, clientAssertionPayload } = {}, | ||
{ exchangeBody, clientAssertionPayload, DPoP } = {}, | ||
) { | ||
@@ -467,3 +467,3 @@ let params = pickCb(parameters); | ||
code_verifier: checks.code_verifier, | ||
}, { clientAssertionPayload }); | ||
}, { clientAssertionPayload, DPoP }); | ||
@@ -491,3 +491,3 @@ await this.decryptIdToken(tokenset); | ||
checks = {}, | ||
{ exchangeBody, clientAssertionPayload } = {}, | ||
{ exchangeBody, clientAssertionPayload, DPoP } = {}, | ||
) { | ||
@@ -569,3 +569,3 @@ let params = pickCb(parameters); | ||
code_verifier: checks.code_verifier, | ||
}, { clientAssertionPayload }); | ||
}, { clientAssertionPayload, DPoP }); | ||
} | ||
@@ -978,3 +978,3 @@ | ||
*/ | ||
async refresh(refreshToken, { exchangeBody, clientAssertionPayload } = {}) { | ||
async refresh(refreshToken, { exchangeBody, clientAssertionPayload, DPoP } = {}) { | ||
let token = refreshToken; | ||
@@ -993,3 +993,3 @@ | ||
refresh_token: String(token), | ||
}, { clientAssertionPayload }); | ||
}, { clientAssertionPayload, DPoP }); | ||
@@ -1023,2 +1023,3 @@ if (tokenset.id_token) { | ||
tokenType = accessToken instanceof TokenSet ? accessToken.token_type : 'Bearer', | ||
DPoP, | ||
} = {}, | ||
@@ -1048,3 +1049,3 @@ ) { | ||
url: resourceUrl, | ||
}, { mTLS }); | ||
}, { mTLS, DPoP }); | ||
} | ||
@@ -1057,3 +1058,3 @@ | ||
async userinfo(accessToken, { | ||
method = 'GET', via = 'header', tokenType, params, | ||
method = 'GET', via = 'header', tokenType, params, DPoP, | ||
} = {}) { | ||
@@ -1064,2 +1065,3 @@ assertIssuerConfiguration(this.issuer, 'userinfo_endpoint'); | ||
method: String(method).toUpperCase(), | ||
DPoP, | ||
}; | ||
@@ -1234,3 +1236,3 @@ | ||
*/ | ||
async grant(body, { clientAssertionPayload } = {}) { | ||
async grant(body, { clientAssertionPayload, DPoP } = {}) { | ||
assertIssuerConfiguration(this.issuer, 'token_endpoint'); | ||
@@ -1244,3 +1246,3 @@ const response = await authenticatedPost.call( | ||
}, | ||
{ clientAssertionPayload }, | ||
{ clientAssertionPayload, DPoP }, | ||
); | ||
@@ -1256,3 +1258,3 @@ const responseBody = processResponse(response); | ||
*/ | ||
async deviceAuthorization(params = {}, { exchangeBody, clientAssertionPayload } = {}) { | ||
async deviceAuthorization(params = {}, { exchangeBody, clientAssertionPayload, DPoP } = {}) { | ||
assertIssuerConfiguration(this.issuer, 'device_authorization_endpoint'); | ||
@@ -1285,2 +1287,3 @@ assertIssuerConfiguration(this.issuer, 'token_endpoint'); | ||
maxAge: params.max_age, | ||
DPoP, | ||
}); | ||
@@ -1615,2 +1618,58 @@ } | ||
/** | ||
* @name dpopProof | ||
* @api private | ||
*/ | ||
function dpopProof(payload, jwk) { | ||
if (!isPlainObject(payload)) { | ||
throw new TypeError('payload must be a plain object'); | ||
} | ||
let key; | ||
try { | ||
key = jose.JWK.asKey(jwk); | ||
assert(key.type === 'private'); | ||
} catch (err) { | ||
throw new TypeError('"DPoP" option must be an asymmetric private key to sign the DPoP Proof JWT with'); | ||
} | ||
let { alg } = key; | ||
if (!alg && this.issuer.dpop_signing_alg_values_supported) { | ||
const algs = key.algorithms('sign'); | ||
alg = this.issuer.dpop_signing_alg_values_supported.find((a) => algs.has(a)); | ||
} | ||
if (!alg) { | ||
[alg] = key.algorithms('sign'); | ||
} | ||
return jose.JWS.sign({ | ||
iat: now(), | ||
jti: random(), | ||
...payload, | ||
}, jwk, { | ||
alg, | ||
typ: 'dpop+jwt', | ||
jwk: pick(key, 'kty', 'crv', 'x', 'y', 'e', 'n'), | ||
}); | ||
} | ||
Object.defineProperty(BaseClient.prototype, 'dpopProof', { | ||
enumerable: true, | ||
configurable: true, | ||
value(...args) { | ||
process.emitWarning( | ||
'The DPoP APIs implements an IETF draft. Breaking draft implementations are included as minor versions of the openid-client library, therefore, the ~ semver operator should be used and close attention be payed to library changelog as well as the drafts themselves.', | ||
'DraftWarning', | ||
); | ||
Object.defineProperty(BaseClient.prototype, 'dpopProof', { | ||
enumerable: true, | ||
configurable: true, | ||
value: dpopProof, | ||
}); | ||
return this.dpopProof(...args); | ||
}, | ||
}); | ||
module.exports.BaseClient = BaseClient; |
@@ -13,3 +13,3 @@ /* eslint-disable camelcase */ | ||
constructor({ | ||
client, exchangeBody, clientAssertionPayload, response, maxAge, | ||
client, exchangeBody, clientAssertionPayload, response, maxAge, DPoP, | ||
}) { | ||
@@ -28,2 +28,3 @@ ['verification_uri', 'user_code', 'device_code'].forEach((prop) => { | ||
instance(this).client = client; | ||
instance(this).DPoP = DPoP; | ||
instance(this).maxAge = maxAge; | ||
@@ -54,3 +55,3 @@ instance(this).exchangeBody = exchangeBody; | ||
}, | ||
{ clientAssertionPayload: instance(this).clientAssertionPayload }, | ||
{ clientAssertionPayload: instance(this).clientAssertionPayload, DPoP: instance(this).DPoP }, | ||
); | ||
@@ -57,0 +58,0 @@ |
@@ -125,3 +125,3 @@ const jose = require('jose'); | ||
async function authenticatedPost(endpoint, opts, { | ||
clientAssertionPayload, endpointAuthMethod = endpoint, | ||
clientAssertionPayload, endpointAuthMethod = endpoint, DPoP, | ||
} = {}) { | ||
@@ -153,3 +153,3 @@ const auth = await authFor.call(this, endpointAuthMethod, { clientAssertionPayload }); | ||
url: targetUrl, | ||
}, { mTLS }); | ||
}, { mTLS, DPoP }); | ||
} | ||
@@ -156,0 +156,0 @@ |
@@ -25,11 +25,18 @@ const Got = require('got'); | ||
module.exports = function request(options, { mTLS = false } = {}) { | ||
module.exports = async function request(options, { mTLS = false, DPoP } = {}) { | ||
const { url } = options; | ||
isAbsoluteUrl(url); | ||
const optsFn = this[HTTP_OPTIONS]; | ||
let opts; | ||
let opts = options; | ||
if (DPoP && 'dpopProof' in this) { | ||
opts.headers = opts.headers || {}; | ||
opts.headers.DPoP = this.dpopProof({ | ||
htu: url, | ||
htm: options.method, | ||
}, DPoP); | ||
} | ||
if (optsFn) { | ||
opts = optsFn.call(this, defaultsDeep({}, options, DEFAULT_HTTP_OPTIONS)); | ||
} else { | ||
opts = options; | ||
opts = optsFn.call(this, defaultsDeep({}, opts, DEFAULT_HTTP_OPTIONS)); | ||
} | ||
@@ -46,2 +53,3 @@ | ||
} | ||
return got(opts); | ||
@@ -48,0 +56,0 @@ }; |
{ | ||
"name": "openid-client", | ||
"version": "4.0.2", | ||
"version": "4.1.0", | ||
"description": "OpenID Connect Relying Party (RP, Client) implementation for Node.js runtime, supports passportjs", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -49,4 +49,5 @@ # openid-client | ||
- [JWT Secured Authorization Response Mode for OAuth 2.0 (JARM) - ID1][feature-jarm] | ||
- [OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer (DPoP) - draft 01][feature-dpop] | ||
Updates to draft specifications (JARM, and FAPI) are released as MINOR library versions, | ||
Updates to draft specifications (DPoP, JARM, and FAPI) are released as MINOR library versions, | ||
if you utilize these specification implementations consider using the tilde `~` operator in your | ||
@@ -308,2 +309,3 @@ package.json since breaking changes may be introduced as part of these version updates. | ||
[feature-fapi]: https://openid.net/specs/openid-financial-api-part-2-ID2.html | ||
[feature-dpop]: https://tools.ietf.org/html/draft-ietf-oauth-dpop-01 | ||
[openid-certified-link]: https://openid.net/certification/ | ||
@@ -310,0 +312,0 @@ [passport-url]: http://passportjs.org |
@@ -12,3 +12,4 @@ /// <reference types="node" /> | ||
import { URL } from 'url'; | ||
import { JWKS, JSONWebKeySet } from 'jose'; | ||
import jose from 'jose'; | ||
import crypto from 'crypto'; | ||
@@ -19,2 +20,3 @@ export type HttpOptions = GotOptions; | ||
export type TokenTypeHint = 'access_token' | 'refresh_token' | string; | ||
export type DPoPInput = crypto.KeyObject | crypto.PrivateKeyInput | jose.JWKRSAKey | jose.JWKECKey | jose.JWKOKPKey; | ||
@@ -187,2 +189,7 @@ /** | ||
clientAssertionPayload?: object; | ||
/** | ||
* Private key to sign the DPoP Proof JWT with. This can be a crypto.KeyObject, crypto.createPrivateKey valid | ||
* inputs, or a JWK formatted private key. | ||
*/ | ||
DPoP?: DPoPInput; | ||
} | ||
@@ -200,2 +207,7 @@ | ||
clientAssertionPayload?: object; | ||
/** | ||
* Private key to sign the DPoP Proof JWT with. This can be a crypto.KeyObject, crypto.createPrivateKey valid | ||
* inputs, or a JWK formatted private key. | ||
*/ | ||
DPoP?: DPoPInput; | ||
} | ||
@@ -215,2 +227,7 @@ | ||
clientAssertionPayload?: object; | ||
/** | ||
* Private key to sign the DPoP Proof JWT with. This can be a crypto.KeyObject, crypto.createPrivateKey valid | ||
* inputs, or a JWK formatted private key. | ||
*/ | ||
DPoP?: DPoPInput; | ||
} | ||
@@ -258,3 +275,3 @@ | ||
*/ | ||
jwks?: JSONWebKeySet; | ||
jwks?: jose.JSONWebKeySet; | ||
/** | ||
@@ -283,2 +300,7 @@ * Initial Access Token to use as a Bearer token during the registration call. | ||
clientAssertionPayload?: object; | ||
/** | ||
* Private key to sign the DPoP Proof JWT with. This can be a crypto.KeyObject, crypto.createPrivateKey valid | ||
* inputs, or a JWK formatted private key. | ||
*/ | ||
DPoP?: DPoPInput; | ||
} | ||
@@ -350,3 +372,3 @@ | ||
export class Client { | ||
constructor(metadata: ClientMetadata, jwks?: JSONWebKeySet, options?: ClientOptions); | ||
constructor(metadata: ClientMetadata, jwks?: jose.JSONWebKeySet, options?: ClientOptions); | ||
[custom.http_options]: CustomHttpOptionsProvider; | ||
@@ -417,3 +439,3 @@ [custom.clock_tolerance]: number; | ||
*/ | ||
userinfo(accessToken: TokenSet | string, options?: { method?: 'GET' | 'POST', via?: 'header' | 'body' | 'query', tokenType?: string, params?: object }): Promise<UserinfoResponse>; | ||
userinfo(accessToken: TokenSet | string, options?: { method?: 'GET' | 'POST', via?: 'header' | 'body' | 'query', tokenType?: string, params?: object, DPoP?: DPoPInput }): Promise<UserinfoResponse>; | ||
@@ -433,2 +455,3 @@ /** | ||
tokenType?: string | ||
DPoP?: DPoPInput | ||
}): CancelableRequest<Response<Buffer>>; | ||
@@ -464,3 +487,3 @@ | ||
static register(metadata: object, other?: RegisterOther & ClientOptions): Promise<Client>; | ||
static fromUri(registrationClientUri: string, registrationAccessToken: string, jwks?: JSONWebKeySet, clientOptions?: ClientOptions): Promise<Client>; | ||
static fromUri(registrationClientUri: string, registrationAccessToken: string, jwks?: jose.JSONWebKeySet, clientOptions?: ClientOptions): Promise<Client>; | ||
static [custom.http_options]: CustomHttpOptionsProvider; | ||
@@ -516,3 +539,3 @@ | ||
export interface TypeOfGenericClient<TClient extends Client> { | ||
new (metadata: ClientMetadata, jwks?: JSONWebKeySet, options?: ClientOptions): TClient; | ||
new (metadata: ClientMetadata, jwks?: jose.JSONWebKeySet, options?: ClientOptions): TClient; | ||
[custom.http_options]: CustomHttpOptionsProvider; | ||
@@ -549,3 +572,3 @@ [custom.clock_tolerance]: number; | ||
*/ | ||
keystore(forceReload?: boolean): Promise<JWKS.KeyStore>; | ||
keystore(forceReload?: boolean): Promise<jose.JWKS.KeyStore>; | ||
@@ -552,0 +575,0 @@ /** |
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
170151
3223
324