diff --git a/lib/helpers/validate_dpop.js b/lib/helpers/validate_dpop.js index e3a6267ce..dc753140a 100644 --- a/lib/helpers/validate_dpop.js +++ b/lib/helpers/validate_dpop.js @@ -40,10 +40,7 @@ export default async (ctx, accessToken) => { if (typeof requireNonce !== 'boolean') { throw new Error('features.dPoP.requireNonce must return a boolean'); } - - if (DPoPNonces) { - ctx.set('DPoP-Nonce', DPoPNonces.nextNonce()); - } else if (requireNonce) { + if (requireNonce && !DPoPNonces) { throw new Error('features.dPoP.nonceSecret configuration is missing'); } @@ -70,6 +67,8 @@ export default async (ctx, accessToken) => { if (diff > DPOP_OK_WINDOW) { throw new InvalidDpopProof('DPoP proof iat is not recent enough'); } + } else if (!DPoPNonces) { + throw new InvalidDpopProof('DPoP nonces are not supported'); } if (payload.htm !== ctx.method) { @@ -103,14 +102,21 @@ export default async (ctx, accessToken) => { throw new InvalidDpopProof('invalid DPoP key binding', err.message); } + const nextNonce = DPoPNonces?.nextNonce(); if (!payload.nonce && requireNonce) { + ctx.set('DPoP-Nonce', nextNonce); throw new UseDpopNonce('nonce is required in the DPoP proof'); } - if (payload.nonce && (!DPoPNonces || !DPoPNonces.checkNonce(payload.nonce))) { + if (payload.nonce && !DPoPNonces.checkNonce(payload.nonce)) { + ctx.set('DPoP-Nonce', nextNonce); throw new UseDpopNonce('invalid nonce in DPoP proof'); } + if (payload.nonce !== nextNonce) { + ctx.set('DPoP-Nonce', nextNonce); + } + const thumbprint = await calculateJwkThumbprint(protectedHeader.jwk); const result = { thumbprint, jti: payload.jti, iat: payload.iat }; diff --git a/test/dpop/dpop.test.js b/test/dpop/dpop.test.js index 00833662f..cbeece648 100644 --- a/test/dpop/dpop.test.js +++ b/test/dpop/dpop.test.js @@ -994,7 +994,11 @@ describe('features.dPoP', () => { }) .set('DPoP', await DPoP(this.keypair, `${this.provider.issuer}${this.suitePath('/request')}`, 'POST', nonce)) .type('form') - .expect(201); + .expect(201) + .expect((response) => { + // because the sent one is fresh + expect(response.headers).not.to.have.property('dpop-nonce'); + }); }); it('@ userinfo', async function () { @@ -1012,6 +1016,10 @@ describe('features.dPoP', () => { .send({ grant_type: 'client_credentials' }) .set('DPoP', await DPoP(this.keypair, `${this.provider.issuer}${this.suitePath('/me')}`, 'GET', nonce, 'foo')) .expect(401) + .expect((response) => { + // because the sent one is fresh + expect(response.headers).not.to.have.property('dpop-nonce'); + }) .expect({ error: 'invalid_token', error_description: 'invalid token provided' }); }); @@ -1031,7 +1039,11 @@ describe('features.dPoP', () => { .send({ grant_type: 'client_credentials' }) .set('DPoP', await DPoP(this.keypair, `${this.provider.issuer}${this.suitePath('/token')}`, 'POST', nonce)) .type('form') - .expect(200); + .expect(200) + .expect((response) => { + // because the sent one is fresh + expect(response.headers).not.to.have.property('dpop-nonce'); + }); }); }); });