From 330d13cfe2eee22d1745909e90cab738e71e8f5d Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 5 May 2020 17:31:32 +0200 Subject: [PATCH] feat: update DPoP implementation to ietf draft 01 BREAKING CHANGE: DPoP implementation updated to [draft-ietf-oauth-dpop-01](https://tools.ietf.org/html/draft-ietf-oauth-dpop-01) Note: Updates to draft specification versions are released as MINOR library versions, if you utilize these specification implementations consider using the tilde `~` operator in your package.json since breaking changes may be introduced as part of these version updates. Alternatively, [acknowledge](/docs/README.md#features) the version and be notified of breaking changes as part of your CI. --- README.md | 4 +- docs/README.md | 2 +- lib/actions/discovery.js | 4 ++ lib/actions/userinfo.js | 9 ++- lib/helpers/defaults.js | 2 +- lib/helpers/errors.js | 1 + lib/helpers/features.js | 4 +- lib/helpers/oidc_context.js | 57 ++++++----------- package.json | 2 +- test/dpop/dpop.config.js | 1 + test/dpop/dpop.test.js | 120 +++++++++++++++++++++-------------- types/index.d.ts | 5 +- types/oidc-provider-tests.ts | 2 +- 13 files changed, 119 insertions(+), 94 deletions(-) diff --git a/README.md b/README.md index dd239b5a2..bdb41e009 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ The following draft specifications are implemented by oidc-provider. - [JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens - draft 05][jwt-at] - [JWT Response for OAuth Token Introspection - draft 09][jwt-introspection] - [JWT Secured Authorization Response Mode for OAuth 2.0 (JARM) - draft 02][jarm] -- [OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer (DPoP) - draft 00][dpop] +- [OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer (DPoP) - draft 01][dpop] - [OAuth 2.0 JWT Secured Authorization Request (JAR)][jar] - [OAuth 2.0 Pushed Authorization Requests - draft 01][par] - [OAuth 2.0 Resource Indicators - draft 08][resource-indicators] @@ -169,7 +169,7 @@ See the list of available emitted [event names](/docs/events.md) and their descr [jwt-introspection]: https://tools.ietf.org/html/draft-ietf-oauth-jwt-introspection-response-09 [sponsor-auth0]: https://auth0.com/overview?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=oidc-provider&utm_content=auth [mtls]: https://tools.ietf.org/html/rfc8705 -[dpop]: https://tools.ietf.org/html/draft-ietf-oauth-dpop-00 +[dpop]: https://tools.ietf.org/html/draft-ietf-oauth-dpop-01 [resource-indicators]: https://tools.ietf.org/html/draft-ietf-oauth-resource-indicators-08 [jarm]: https://openid.net/specs/openid-financial-api-jarm-wd-02.html [jwt-at]: https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-05 diff --git a/docs/README.md b/docs/README.md index 214d51b74..497454fe2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -731,7 +731,7 @@ _**default value**_: ### features.dPoP -[draft-ietf-oauth-dpop-00](https://tools.ietf.org/html/draft-ietf-oauth-dpop-00) - OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer +[draft-ietf-oauth-dpop-01](https://tools.ietf.org/html/draft-ietf-oauth-dpop-01) - OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer Enables `DPoP` - mechanism for sender-constraining tokens via a proof-of-possession mechanism on the application level. Browser DPoP Proof generation [here](https://www.npmjs.com/package/dpop). diff --git a/lib/actions/discovery.js b/lib/actions/discovery.js index e73b7ad72..30188e186 100644 --- a/lib/actions/discovery.js +++ b/lib/actions/discovery.js @@ -74,6 +74,10 @@ module.exports = function discovery(ctx, next) { ctx.body.introspection_signing_alg_values_supported = config.introspectionSigningAlgValues; } + if (features.dPoP.enabled) { + ctx.body.dpop_signing_alg_values_supported = config.dPoPSigningAlgValues; + } + if (features.revocation.enabled) { ctx.body.revocation_endpoint = ctx.oidc.urlFor('revocation'); ctx.body.revocation_endpoint_auth_methods_supported = [...config.revocationEndpointAuthMethods]; diff --git a/lib/actions/userinfo.js b/lib/actions/userinfo.js index c55ea821a..94efbb7b6 100644 --- a/lib/actions/userinfo.js +++ b/lib/actions/userinfo.js @@ -6,7 +6,7 @@ const Debug = require('debug'); const debug = new Debug('oidc-provider:userinfo'); const uidToGrantId = new Debug('oidc-provider:uid'); -const { InvalidToken, InvalidScope } = require('../helpers/errors'); +const { InvalidDpopProof, InvalidToken, InvalidScope } = require('../helpers/errors'); const setWWWAuthenticate = require('../helpers/set_www_authenticate'); const bodyParser = require('../shared/conditional_body'); const rejectDupes = require('../shared/reject_dupes'); @@ -49,6 +49,10 @@ module.exports = [ scheme = 'Bearer'; } + if (err instanceof InvalidDpopProof) { + err.error = err.message = 'invalid_token'; // eslint-disable-line no-multi-assign + } + setWWWAuthenticate(ctx, scheme, { realm: ctx.oidc.issuer, ...(err.error_description !== 'no access token provided' ? { @@ -56,6 +60,9 @@ module.exports = [ error_description: err.error_description, scope: err.scope, } : undefined), + ...(scheme === 'DPoP' ? { + algs: instance(ctx.oidc.provider).configuration('dPoPSigningAlgValues').join(' '), + } : undefined), }); } throw err; diff --git a/lib/helpers/defaults.js b/lib/helpers/defaults.js index de4a75054..103146070 100644 --- a/lib/helpers/defaults.js +++ b/lib/helpers/defaults.js @@ -365,7 +365,7 @@ const DEFAULTS = { /* * features.dPoP * - * title: [draft-ietf-oauth-dpop-00](https://tools.ietf.org/html/draft-ietf-oauth-dpop-00) - OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer + * title: [draft-ietf-oauth-dpop-01](https://tools.ietf.org/html/draft-ietf-oauth-dpop-01) - OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer * * description: Enables `DPoP` - mechanism for sender-constraining tokens via a * proof-of-possession mechanism on the application level. Browser DPoP Proof generation diff --git a/lib/helpers/errors.js b/lib/helpers/errors.js index 17be14100..534004828 100644 --- a/lib/helpers/errors.js +++ b/lib/helpers/errors.js @@ -76,6 +76,7 @@ const classes = [ ['expired_token'], ['interaction_required'], ['invalid_client'], + ['invalid_dpop_proof'], ['invalid_request_object'], ['invalid_request_uri'], ['invalid_software_statement'], diff --git a/lib/helpers/features.js b/lib/helpers/features.js index 63c03bd4c..0b67fe15d 100644 --- a/lib/helpers/features.js +++ b/lib/helpers/features.js @@ -36,8 +36,8 @@ const DRAFTS = new Map(Object.entries({ dPoP: { name: 'OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer', type: 'IETF OAuth Working Group draft', - url: 'https://tools.ietf.org/html/draft-ietf-oauth-dpop-00', - version: ['id-03', 'individual-draft-03', 'individual-draft-04', 'draft-00'], + url: 'https://tools.ietf.org/html/draft-ietf-oauth-dpop-01', + version: ['draft-01'], }, frontchannelLogout: { name: 'OpenID Connect Front-Channel Logout 1.0 - draft 02', diff --git a/lib/helpers/oidc_context.js b/lib/helpers/oidc_context.js index e463ba7e8..1bf608d26 100644 --- a/lib/helpers/oidc_context.js +++ b/lib/helpers/oidc_context.js @@ -8,12 +8,12 @@ const omitBy = require('lodash/omitBy'); const get = require('lodash/get'); const isUndefined = require('lodash/isUndefined'); const debug = require('debug')('oidc-provider:bearer'); -const { JWT, JWK, errors } = require('jose'); +const { JWT, JWK } = require('jose'); const ctxRef = require('../models/ctx_ref'); const nanoid = require('./nanoid'); -const { InvalidRequest } = require('./errors'); +const { InvalidRequest, InvalidDpopProof } = require('./errors'); const instance = require('./weak_cache'); const resolveResponseMode = require('./resolve_response_mode'); @@ -122,58 +122,39 @@ module.exports = function getContext(provider) { } try { - const { header, payload } = JWT.decode(token, { complete: true }); - let key; + const { payload, key } = JWT.verify( + token, + JWK.EmbeddedJWK, + { + maxTokenAge: `${dPoPConfig.iatTolerance} seconds`, + clockTolerance: `${clockTolerance} seconds`, + algorithms: instance(provider).configuration('dPoPSigningAlgValues'), + complete: true, + typ: 'dpop+jwt', + }, + ); - if (header.typ !== 'dpop+jwt') { - throw new Error('typ must be dpop+jwt'); - } - if (typeof header.alg !== 'string' || !header.alg || header.alg === 'none' || header.alg.startsWith('HS')) { - throw new Error('invalid alg'); - } - if (!instance(provider).configuration('dPoPSigningAlgValues').includes(header.alg)) { - throw new Error('unsupported alg'); - } - if (typeof header.jwk !== 'object' || !header.jwk) { - throw new Error('header must have a jwk'); - } - try { - key = JWK.asKey(header.jwk); - } catch (err) { - throw new Error('failed to import jwk'); - } - if (key.type !== 'public') { - throw new Error('jwk must be a public key'); - } if (typeof payload.jti !== 'string' || !payload.jti) { throw new Error('must have a jti string property'); } - if (typeof payload.iat !== 'number' || !payload.iat) { - throw new Error('must have a iat number property'); - } - if (payload.htm !== this.ctx.method) { + + // HTTP Methods are case-insensitive + if (String(payload.htm).toLowerCase() !== this.ctx.method.toLowerCase()) { throw new Error('htm mismatch'); } + // TODO: allow trailing slash to be added/omitted at will, // see https://github.com/danielfett/draft-dpop/issues/49 if (payload.htu !== this.urlFor(this.route)) { throw new Error('htu mismatch'); } - try { - JWT.verify(token, key, { maxTokenAge: `${dPoPConfig.iatTolerance} seconds`, clockTolerance: `${clockTolerance} seconds` }); - } catch (err) { - if (err instanceof errors.JWTClaimInvalid) { - throw new Error(`failed claim check (${err.message})`); - } - throw err; - } - const result = { jwk: key, jti: payload.jti, iat: payload.iat }; instance(this).dpop = result; + return result; } catch (err) { - throw new InvalidRequest(`invalid DPoP Proof JWT (${err.message})`); + throw new InvalidDpopProof(`invalid DPoP key binding (${err.message})`); } } diff --git a/package.json b/package.json index 03fd9b663..f0461ec3b 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "debug": "^4.1.1", "ejs": "^3.0.1", "got": "^9.6.0", - "jose": "^1.25.2", + "jose": "^1.27.0", "jsesc": "^3.0.1", "koa": "^2.11.0", "koa-compose": "^4.1.0", diff --git a/test/dpop/dpop.config.js b/test/dpop/dpop.config.js index 30c06511f..78ab995f9 100644 --- a/test/dpop/dpop.config.js +++ b/test/dpop/dpop.config.js @@ -3,6 +3,7 @@ const merge = require('lodash/merge'); const config = cloneDeep(require('../default.config')); +config.whitelistedJWA.dPoPSigningAlgValues = ['ES256', 'PS256']; merge(config.features, { dPoP: { enabled: true }, clientCredentials: { enabled: true }, diff --git a/test/dpop/dpop.test.js b/test/dpop/dpop.test.js index 126f26673..e2aa636c4 100644 --- a/test/dpop/dpop.test.js +++ b/test/dpop/dpop.test.js @@ -26,6 +26,14 @@ describe('features.dPoP', () => { this.proof = (uri, method, jwk = this.jwk) => JWT.sign({ htu: uri, htm: method, jti: nanoid() }, jwk, { kid: false, header: { typ: 'dpop+jwt', jwk: JWK.asKey(jwk) } }); }); + it('extends discovery', function () { + return this.agent.get('/.well-known/openid-configuration') + .expect(200) + .expect((response) => { + expect(response.body).to.have.deep.property('dpop_signing_alg_values_supported', ['ES256', 'PS256']); + }); + }); + it('validates the way DPoP Proof JWT is provided', async function () { const at = new this.provider.AccessToken({ accountId: 'account', @@ -36,105 +44,119 @@ describe('features.dPoP', () => { const dpop = await at.save(); + await this.agent.get('/me') + .set('Authorization', `Bearer ${dpop}`) + .expect(401) + .expect({ error: 'invalid_token', error_description: 'invalid token provided' }) + .expect('WWW-Authenticate', /^DPoP /) + .expect('WWW-Authenticate', /error="invalid_token"/) + .expect('WWW-Authenticate', /algs="ES256 PS256"/); + await this.agent.get('/me') .set('Authorization', `DPoP ${dpop}`) .expect(400) - .expect({ error: 'invalid_request', error_description: '`DPoP` header not provided' }); + .expect({ error: 'invalid_request', error_description: '`DPoP` header not provided' }) + .expect('WWW-Authenticate', /^DPoP /) + .expect('WWW-Authenticate', /algs="ES256 PS256"/); await this.agent.post('/me') .set('DPoP', this.proof(`${this.provider.issuer}${this.suitePath('/me')}`, 'POST')) .send({ access_token: dpop }) .type('form') .expect(400) - .expect({ error: 'invalid_request', error_description: '`DPoP` tokens must be provided via an authorization header' }); + .expect({ error: 'invalid_request', error_description: '`DPoP` tokens must be provided via an authorization header' }) + .expect('WWW-Authenticate', /^DPoP /) + .expect('WWW-Authenticate', /algs="ES256 PS256"/); await this.agent.get('/me') .set('DPoP', this.proof(`${this.provider.issuer}${this.suitePath('/me')}`, 'GET')) .set('Authorization', `Bearer ${dpop}`) .expect(400) - .expect({ error: 'invalid_request', error_description: 'authorization header scheme must be `DPoP` when DPoP is used' }); + .expect({ error: 'invalid_request', error_description: 'authorization header scheme must be `DPoP` when DPoP is used' }) + .expect('WWW-Authenticate', /^DPoP /) + .expect('WWW-Authenticate', /algs="ES256 PS256"/); }); it('validates the DPoP Proof JWT is conform', async function () { const key = await JWK.generate('EC'); - for (const value of [undefined, '', 1, true, null]) { // eslint-disable-line no-restricted-syntax - await this.agent.get('/me') // eslint-disable-line no-await-in-loop - .set('DPoP', JWT.sign({}, key, { kid: false, header: { typ: value } })) - .set('Authorization', 'DPoP foo') - .expect(400) - .expect({ error: 'invalid_request', error_description: 'invalid DPoP Proof JWT (typ must be dpop+jwt)' }); - } - - for (const value of [undefined, '', 1, true, null, 'none', 'HS256']) { // eslint-disable-line no-restricted-syntax + for (const value of ['JWT', 'secevent+jwt']) { // eslint-disable-line no-restricted-syntax await this.agent.get('/me') // eslint-disable-line no-await-in-loop - .set('DPoP', `${base64url.encode(JSON.stringify({ typ: 'dpop+jwt', alg: value }))}.e30.`) + .set('DPoP', JWT.sign({}, key, { kid: false, header: { jwk: key, typ: value } })) .set('Authorization', 'DPoP foo') .expect(400) - .expect({ error: 'invalid_request', error_description: 'invalid DPoP Proof JWT (invalid alg)' }); + .expect({ error: 'invalid_token', error_description: 'invalid DPoP key binding (unexpected "typ" JWT header value)' }) + .expect('WWW-Authenticate', /^DPoP /) + .expect('WWW-Authenticate', /error="invalid_token"/) + .expect('WWW-Authenticate', /algs="ES256 PS256"/); } - await this.agent.get('/me') // eslint-disable-line no-await-in-loop - .set('DPoP', `${base64url.encode(JSON.stringify({ typ: 'dpop+jwt', alg: 'Unsupported' }))}.e30.`) - .set('Authorization', 'DPoP foo') - .expect(400) - .expect({ error: 'invalid_request', error_description: 'invalid DPoP Proof JWT (unsupported alg)' }); - - for (const value of [undefined, '', 1, true, null, 'foo']) { // eslint-disable-line no-restricted-syntax + for (const value of [1, true, 'none', 'HS256', 'unsupported']) { // eslint-disable-line no-restricted-syntax await this.agent.get('/me') // eslint-disable-line no-await-in-loop - .set('DPoP', JWT.sign({}, key, { kid: false, header: { typ: 'dpop+jwt', jwk: value } })) + .set('DPoP', `${base64url.encode(JSON.stringify({ jwk: key, typ: 'dpop+jwt', alg: value }))}.e30.`) .set('Authorization', 'DPoP foo') .expect(400) - .expect({ error: 'invalid_request', error_description: 'invalid DPoP Proof JWT (header must have a jwk)' }); + .expect({ error: 'invalid_token', error_description: 'invalid DPoP key binding (alg not whitelisted)' }) + .expect('WWW-Authenticate', /^DPoP /) + .expect('WWW-Authenticate', /error="invalid_token"/) + .expect('WWW-Authenticate', /algs="ES256 PS256"/); } - for (const value of [[], {}, { kty: 'foo' }]) { // eslint-disable-line no-restricted-syntax + for (const value of [undefined, '', 1, true, null, 'foo', []]) { // eslint-disable-line no-restricted-syntax await this.agent.get('/me') // eslint-disable-line no-await-in-loop .set('DPoP', JWT.sign({}, key, { kid: false, header: { typ: 'dpop+jwt', jwk: value } })) .set('Authorization', 'DPoP foo') .expect(400) - .expect({ error: 'invalid_request', error_description: 'invalid DPoP Proof JWT (failed to import jwk)' }); + .expect({ error: 'invalid_token', error_description: 'invalid DPoP key binding (JWS Header Parameter "jwk" must be a JSON object)' }) + .expect('WWW-Authenticate', /^DPoP /) + .expect('WWW-Authenticate', /error="invalid_token"/) + .expect('WWW-Authenticate', /algs="ES256 PS256"/); } await this.agent.get('/me') // eslint-disable-line no-await-in-loop .set('DPoP', JWT.sign({}, key, { kid: false, header: { typ: 'dpop+jwt', jwk: key.toJWK(true) } })) .set('Authorization', 'DPoP foo') .expect(400) - .expect({ error: 'invalid_request', error_description: 'invalid DPoP Proof JWT (jwk must be a public key)' }); + .expect({ error: 'invalid_token', error_description: 'invalid DPoP key binding (JWS Header Parameter "jwk" must be a public key)' }) + .expect('WWW-Authenticate', /^DPoP /) + .expect('WWW-Authenticate', /error="invalid_token"/) + .expect('WWW-Authenticate', /algs="ES256 PS256"/); await this.agent.get('/me') // eslint-disable-line no-await-in-loop .set('DPoP', JWT.sign({}, key, { kid: false, header: { typ: 'dpop+jwt', jwk: await JWK.generate('oct') } })) .set('Authorization', 'DPoP foo') .expect(400) - .expect({ error: 'invalid_request', error_description: 'invalid DPoP Proof JWT (jwk must be a public key)' }); - - for (const value of [[], {}, 1, null, '', false, true]) { // eslint-disable-line no-restricted-syntax - await this.agent.get('/me') // eslint-disable-line no-await-in-loop - .set('DPoP', JWT.sign({ jti: value }, key, { kid: false, header: { typ: 'dpop+jwt', jwk: key } })) - .set('Authorization', 'DPoP foo') - .expect(400) - .expect({ error: 'invalid_request', error_description: 'invalid DPoP Proof JWT (must have a jti string property)' }); - } + .expect({ error: 'invalid_token', error_description: 'invalid DPoP key binding (JWS Header Parameter "jwk" must be a public key)' }) + .expect('WWW-Authenticate', /^DPoP /) + .expect('WWW-Authenticate', /error="invalid_token"/) + .expect('WWW-Authenticate', /algs="ES256 PS256"/); - for (const value of [[], {}, 0, null, '', false, true]) { // eslint-disable-line no-restricted-syntax - await this.agent.get('/me') // eslint-disable-line no-await-in-loop - .set('DPoP', JWT.sign({ jti: 'foo', iat: value }, key, { kid: false, iat: false, header: { typ: 'dpop+jwt', jwk: key } })) - .set('Authorization', 'DPoP foo') - .expect(400) - .expect({ error: 'invalid_request', error_description: 'invalid DPoP Proof JWT (must have a iat number property)' }); - } + await this.agent.get('/me') // eslint-disable-line no-await-in-loop + .set('DPoP', JWT.sign({ htm: 'POST', htu: `${this.provider.issuer}${this.suitePath('/me')}` }, key, { kid: false, header: { typ: 'dpop+jwt', jwk: key } })) + .set('Authorization', 'DPoP foo') + .expect(400) + .expect({ error: 'invalid_token', error_description: 'invalid DPoP key binding (must have a jti string property)' }) + .expect('WWW-Authenticate', /^DPoP /) + .expect('WWW-Authenticate', /error="invalid_token"/) + .expect('WWW-Authenticate', /algs="ES256 PS256"/); await this.agent.get('/me') // eslint-disable-line no-await-in-loop .set('DPoP', JWT.sign({ jti: 'foo', htm: 'POST' }, key, { kid: false, header: { typ: 'dpop+jwt', jwk: key } })) .set('Authorization', 'DPoP foo') .expect(400) - .expect({ error: 'invalid_request', error_description: 'invalid DPoP Proof JWT (htm mismatch)' }); + .expect({ error: 'invalid_token', error_description: 'invalid DPoP key binding (htm mismatch)' }) + .expect('WWW-Authenticate', /^DPoP /) + .expect('WWW-Authenticate', /error="invalid_token"/) + .expect('WWW-Authenticate', /algs="ES256 PS256"/); await this.agent.get('/me') // eslint-disable-line no-await-in-loop .set('DPoP', JWT.sign({ jti: 'foo', htm: 'GET', htu: 'foo' }, key, { kid: false, header: { typ: 'dpop+jwt', jwk: key } })) .set('Authorization', 'DPoP foo') .expect(400) - .expect({ error: 'invalid_request', error_description: 'invalid DPoP Proof JWT (htu mismatch)' }); + .expect({ error: 'invalid_token', error_description: 'invalid DPoP key binding (htu mismatch)' }) + .expect('WWW-Authenticate', /^DPoP /) + .expect('WWW-Authenticate', /error="invalid_token"/) + .expect('WWW-Authenticate', /algs="ES256 PS256"/); await this.agent.get('/me') // eslint-disable-line no-await-in-loop .set('DPoP', JWT.sign({ @@ -142,7 +164,10 @@ describe('features.dPoP', () => { }, key, { kid: false, iat: false, header: { typ: 'dpop+jwt', jwk: key } })) .set('Authorization', 'DPoP foo') .expect(400) - .expect({ error: 'invalid_request', error_description: 'invalid DPoP Proof JWT (failed claim check ("iat" claim timestamp check failed (too far in the past)))' }); + .expect({ error: 'invalid_token', error_description: 'invalid DPoP key binding ("iat" claim timestamp check failed (too far in the past))' }) + .expect('WWW-Authenticate', /^DPoP /) + .expect('WWW-Authenticate', /error="invalid_token"/) + .expect('WWW-Authenticate', /algs="ES256 PS256"/); await this.agent.get('/me') // eslint-disable-line no-await-in-loop .set('DPoP', JWT.sign({ @@ -150,7 +175,10 @@ describe('features.dPoP', () => { }, key, { kid: false, header: { typ: 'dpop+jwt', jwk: await JWK.generate('EC') } })) .set('Authorization', 'DPoP foo') .expect(400) - .expect({ error: 'invalid_request', error_description: 'invalid DPoP Proof JWT (signature verification failed)' }); + .expect({ error: 'invalid_token', error_description: 'invalid DPoP key binding (signature verification failed)' }) + .expect('WWW-Authenticate', /^DPoP /) + .expect('WWW-Authenticate', /error="invalid_token"/) + .expect('WWW-Authenticate', /algs="ES256 PS256"/); }); describe('userinfo', () => { diff --git a/types/index.d.ts b/types/index.d.ts index dd78d4771..253dd4aab 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -934,7 +934,7 @@ export interface Configuration { dPoP?: { enabled?: boolean, iatTolerance?: number, - ack?: 'id-03' | 'individual-draft-03' | 'individual-draft-04' | 'draft-00' + ack?: 'draft-01' }, secp256k1?: { @@ -1577,6 +1577,9 @@ export namespace errors { class InvalidClient extends OIDCProviderError { constructor(description?: string, detail?: string); } + class InvalidDpopProof extends OIDCProviderError { + constructor(description?: string, detail?: string); + } class InvalidClientAuth extends OIDCProviderError { constructor(detail: string); } diff --git a/types/oidc-provider-tests.ts b/types/oidc-provider-tests.ts index f440fd23e..5df59a4da 100644 --- a/types/oidc-provider-tests.ts +++ b/types/oidc-provider-tests.ts @@ -385,7 +385,7 @@ const provider = new Provider('https://op.example.com', { clientCredentials: { enabled: false }, backchannelLogout: { enabled: false, ack: 4 }, ietfJWTAccessTokenProfile: { enabled: false, ack: 2 }, - dPoP: { enabled: false, ack: 'id-03', iatTolerance: 120 }, + dPoP: { enabled: false, ack: 'draft-01', iatTolerance: 120 }, frontchannelLogout: { ack: 2, enabled: false,