From 76f9af086ebffaa50e6cc4f9ccdb2c25ac307444 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 30 Mar 2023 20:57:27 +0200 Subject: [PATCH] feat: add response_modes client metadata allow list --- certification/fapi/index.js | 8 ++++ docs/README.md | 2 +- .../authorization/check_response_mode.js | 20 ++++----- lib/actions/authorization/index.js | 2 +- .../authorization/process_request_object.js | 23 ++++------- lib/consts/client_attributes.js | 3 ++ lib/helpers/client_schema.js | 5 +++ lib/helpers/defaults.js | 4 +- lib/models/client.js | 9 ++++ test/configuration/client_metadata.test.js | 41 ++++++++++++++++++- test/core/basic/code.authorization.test.js | 2 +- .../code+id_token+token.authorization.test.js | 2 +- .../code+id_token.authorization.test.js | 2 +- .../hybrid/code+token.authorization.test.js | 2 +- .../id_token+token.authorization.test.js | 2 +- .../implicit/id_token.authorization.test.js | 2 +- test/fapi/fapi-final.test.js | 12 ++++-- test/fapi/fapi-id2.test.js | 11 +++-- .../jwt_response_modes.test.js | 2 +- test/request/jwt_request.test.js | 7 ++-- 20 files changed, 111 insertions(+), 50 deletions(-) diff --git a/certification/fapi/index.js b/certification/fapi/index.js index 44a511621..c50dfa35b 100644 --- a/certification/fapi/index.js +++ b/certification/fapi/index.js @@ -71,6 +71,13 @@ function jar(metadata) { }; } +function jarm(metadata) { + return { + ...metadata, + response_modes: ['jwt'], + }; +} + function fapi1(metadata) { return mtlsPoP(jar({ ...metadata, @@ -160,6 +167,7 @@ const adapter = (name) => { break; case 'messagesigning': metadata = jar(metadata); + metadata = jarm(metadata); break; default: return orig.call(this, id); diff --git a/docs/README.md b/docs/README.md index c1c744e8a..718d5dd29 100644 --- a/docs/README.md +++ b/docs/README.md @@ -523,7 +523,7 @@ _**default value**_:
(Click to expand) Available Metadata
-application_type, client_id, client_name, client_secret, client_uri, contacts, default_acr_values, default_max_age, grant_types, id_token_signed_response_alg, initiate_login_uri, jwks, jwks_uri, logo_uri, policy_uri, post_logout_redirect_uris, redirect_uris, require_auth_time, response_types, scope, sector_identifier_uri, subject_type, token_endpoint_auth_method, tos_uri, userinfo_signed_response_alg

The following metadata is available but may not be recognized depending on your provider's configuration.

authorization_encrypted_response_alg, authorization_encrypted_response_enc, authorization_signed_response_alg, backchannel_logout_session_required, backchannel_logout_uri, id_token_encrypted_response_alg, id_token_encrypted_response_enc, introspection_encrypted_response_alg, introspection_encrypted_response_enc, introspection_signed_response_alg, request_object_encryption_alg, request_object_encryption_enc, request_object_signing_alg, request_uris, tls_client_auth_san_dns, tls_client_auth_san_email, tls_client_auth_san_ip, tls_client_auth_san_uri, tls_client_auth_subject_dn, tls_client_certificate_bound_access_tokens, token_endpoint_auth_signing_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, web_message_uris +application_type, client_id, client_name, client_secret, client_uri, contacts, default_acr_values, default_max_age, grant_types, id_token_signed_response_alg, initiate_login_uri, jwks, jwks_uri, logo_uri, policy_uri, post_logout_redirect_uris, redirect_uris, require_auth_time, response_types, response_modes, scope, sector_identifier_uri, subject_type, token_endpoint_auth_method, tos_uri, userinfo_signed_response_alg

The following metadata is available but may not be recognized depending on your provider's configuration.

authorization_encrypted_response_alg, authorization_encrypted_response_enc, authorization_signed_response_alg, backchannel_logout_session_required, backchannel_logout_uri, id_token_encrypted_response_alg, id_token_encrypted_response_enc, introspection_encrypted_response_alg, introspection_encrypted_response_enc, introspection_signed_response_alg, request_object_encryption_alg, request_object_encryption_enc, request_object_signing_alg, request_uris, tls_client_auth_san_dns, tls_client_auth_san_email, tls_client_auth_san_ip, tls_client_auth_san_uri, tls_client_auth_subject_dn, tls_client_certificate_bound_access_tokens, token_endpoint_auth_signing_alg, userinfo_encrypted_response_alg, userinfo_encrypted_response_enc, web_message_uris
diff --git a/lib/actions/authorization/check_response_mode.js b/lib/actions/authorization/check_response_mode.js index 278fd99fe..f1dfd6532 100644 --- a/lib/actions/authorization/check_response_mode.js +++ b/lib/actions/authorization/check_response_mode.js @@ -1,4 +1,4 @@ -import { InvalidRequest, UnsupportedResponseMode } from '../../helpers/errors.js'; +import { InvalidRequest, UnauthorizedClient, UnsupportedResponseMode } from '../../helpers/errors.js'; import instance from '../../helpers/weak_cache.js'; import { isFrontChannel } from '../../helpers/resolve_response_mode.js'; @@ -8,7 +8,7 @@ import { isFrontChannel } from '../../helpers/resolve_response_mode.js'; * * @throws: invalid_request */ -export default function checkResponseMode(ctx, next, forceCheck) { +export default function checkResponseMode(ctx, next) { const { params, client } = ctx.oidc; const frontChannel = isFrontChannel(params.response_type); @@ -23,6 +23,10 @@ export default function checkResponseMode(ctx, next, forceCheck) { throw new UnsupportedResponseMode(); } + if (!ctx.oidc.client.responseModeAllowed(mode, params.response_type, ctx.oidc.fapiProfile)) { + throw new UnauthorizedClient('requested response_mode is not allowed for this client or request'); + } + const JWT = /jwt/.test(mode); if ( @@ -41,17 +45,11 @@ export default function checkResponseMode(ctx, next, forceCheck) { } } + const msg = 'requested response_mode is not allowed for the requested response_type'; if (mode === 'query' && frontChannel) { - throw new InvalidRequest('response_mode not allowed for this response_type'); + throw new InvalidRequest(msg); } else if (mode === 'query.jwt' && frontChannel && !client.authorizationEncryptedResponseAlg) { - throw new InvalidRequest('response_mode not allowed for this response_type unless encrypted'); - } - - const fapiProfile = ctx.oidc.isFapi('1.0 Final', '1.0 ID2'); - if (params.response_type && fapiProfile) { - if (((!params.request && !params.request_uri) || forceCheck) && !params.response_type.includes('id_token') && !JWT) { - throw new InvalidRequest(`requested response_mode not allowed for the requested response_type in FAPI ${fapiProfile}`); - } + throw new InvalidRequest(`${msg} unless encrypted`); } return next(); diff --git a/lib/actions/authorization/index.js b/lib/actions/authorization/index.js index 3b537923b..3a58cba01 100644 --- a/lib/actions/authorization/index.js +++ b/lib/actions/authorization/index.js @@ -149,13 +149,13 @@ export default function authorizationAction(provider, endpoint) { use(() => stripOutsideJarParams, PAR, BA); use(() => checkClient, A, DA, R, CV, DR ); use(() => checkClientGrantType, DA, BA); - use(() => checkResponseMode, A, PAR ); use(() => pushedAuthorizationRequestRemapErrors, PAR ); use(() => backchannelRequestRemapErrors, BA); use(() => fetchRequestUri, A ); use(() => processRequestObject.bind( undefined, allowList, rejectDupesMiddleware, ), A, DA, PAR, BA); + use(() => checkResponseMode, A, PAR ); use(() => oneRedirectUriClients, A, PAR ); use(() => oauthRequired, A, PAR ); use(() => rejectRegistration, A, DA, PAR, BA); diff --git a/lib/actions/authorization/process_request_object.js b/lib/actions/authorization/process_request_object.js index c06a66811..eaed9ca90 100644 --- a/lib/actions/authorization/process_request_object.js +++ b/lib/actions/authorization/process_request_object.js @@ -5,8 +5,6 @@ import isPlainObject from '../../helpers/_/is_plain_object.js'; import dpopValidate from '../../helpers/validate_dpop.js'; import epochTime from '../../helpers/epoch_time.js'; -import checkResponseMode from './check_response_mode.js'; - /* * Decrypts and validates the content of provided request parameter and replaces the parameters * provided via OAuth2.0 authorization request with these @@ -107,19 +105,12 @@ export default async function processRequestObject(PARAM_LIST, rejectDupesMiddle rejectDupesMiddleware({ oidc: { params: request } }, () => {}); - if (request.state !== undefined) { - params.state = request.state; - } - - const isFapi1 = ctx.oidc.isFapi('1.0 Final', '1.0 ID2'); - if (request.response_mode !== undefined || isFapi1) { - if (request.response_mode !== undefined) { - params.response_mode = request.response_mode; - } - if (request.response_type !== undefined) { - params.response_type = request.response_type; + const original = {}; + for (const param of ['state', 'response_mode', 'response_type']) { + original[param] = params[param]; + if (request[param] !== undefined) { + params[param] = request[param]; } - checkResponseMode(ctx, () => {}, isFapi1); } if (request.request !== undefined || request.request_uri !== undefined) { @@ -127,9 +118,9 @@ export default async function processRequestObject(PARAM_LIST, rejectDupesMiddle } if ( - params.response_type + original.response_type && request.response_type !== undefined - && request.response_type !== params.response_type + && request.response_type !== original.response_type ) { throw new InvalidRequestObject('request response_type must equal the one in request parameters'); } diff --git a/lib/consts/client_attributes.js b/lib/consts/client_attributes.js index 1824283e2..af223dfa1 100644 --- a/lib/consts/client_attributes.js +++ b/lib/consts/client_attributes.js @@ -19,6 +19,7 @@ const RECOGNIZED_METADATA = [ 'redirect_uris', 'require_auth_time', 'response_types', + 'response_modes', 'scope', 'sector_identifier_uri', 'subject_type', @@ -70,6 +71,7 @@ const ARYS = [ 'redirect_uris', 'request_uris', 'response_types', + 'response_modes', 'web_message_uris', ]; @@ -121,6 +123,7 @@ const STRING = [ 'redirect_uris', 'request_uris', 'response_types', + 'response_modes', 'web_message_uris', ]; diff --git a/lib/helpers/client_schema.js b/lib/helpers/client_schema.js index d8412caf6..56031d542 100644 --- a/lib/helpers/client_schema.js +++ b/lib/helpers/client_schema.js @@ -164,6 +164,7 @@ export default function getSchema(provider) { request_object_encryption_alg: () => configuration.requestObjectEncryptionAlgValues, request_object_encryption_enc: () => configuration.requestObjectEncryptionEncValues, response_types: () => configuration.responseTypes, + response_modes: () => [...instance(provider).responseModes.keys()], subject_type: () => configuration.subjectTypes, token_endpoint_auth_method: (metadata) => { if (metadata.subject_type === 'pairwise') { @@ -253,6 +254,10 @@ export default function getSchema(provider) { this.invalidate('redirect_uris must contain members'); } + if (this.redirect_uris.length && this.response_modes?.length === 0) { + this.invalidate('response_modes must contain members'); + } + if (responseTypes.includes('code') && !this.grant_types.includes('authorization_code')) { this.invalidate("grant_types must contain 'authorization_code' when code is amongst response_types"); } diff --git a/lib/helpers/defaults.js b/lib/helpers/defaults.js index 5c1e4a861..2b6f48707 100644 --- a/lib/helpers/defaults.js +++ b/lib/helpers/defaults.js @@ -613,8 +613,8 @@ function makeDefaults() { * application_type, client_id, client_name, client_secret, client_uri, contacts, * default_acr_values, default_max_age, grant_types, id_token_signed_response_alg, * initiate_login_uri, jwks, jwks_uri, logo_uri, policy_uri, post_logout_redirect_uris, - * redirect_uris, require_auth_time, response_types, scope, sector_identifier_uri, subject_type, - * token_endpoint_auth_method, tos_uri, userinfo_signed_response_alg + * redirect_uris, require_auth_time, response_types, response_modes, scope, sector_identifier_uri, + * subject_type, token_endpoint_auth_method, tos_uri, userinfo_signed_response_alg * *

The following metadata is available but may not be recognized depending on your * provider's configuration.

diff --git a/lib/models/client.js b/lib/models/client.js index 0d02f870c..ab025e3f7 100644 --- a/lib/models/client.js +++ b/lib/models/client.js @@ -489,6 +489,15 @@ export default function getClient(provider) { return this.responseTypes.includes(type); } + // eslint-disable-next-line no-unused-vars + responseModeAllowed(responseMode, responseType, fapiProfile) { + if ((fapiProfile === '1.0 Final' || fapiProfile === '1.0 ID2') && !responseType.includes('id_token') && !responseMode.includes('jwt')) { + return false; + } + + return this.responseModes?.includes(responseMode) !== false; + } + grantTypeAllowed(type) { return this.grantTypes.includes(type); } diff --git a/test/configuration/client_metadata.test.js b/test/configuration/client_metadata.test.js index a1d076163..42715aadc 100644 --- a/test/configuration/client_metadata.test.js +++ b/test/configuration/client_metadata.test.js @@ -155,7 +155,7 @@ describe('Client metadata validation', () => { }); }; - const defaultsTo = (prop, value, metadata, configuration) => { + const defaultsTo = (prop, value, metadata, configuration, additionalAssertion) => { let msg = util.format('defaults to %s', value); if (metadata) msg = util.format(`${msg}, [client %j]`, omit(metadata, ['jwks.keys'])); if (configuration) msg = util.format(`${msg}, [provider %j]`, configuration); @@ -166,6 +166,12 @@ describe('Client metadata validation', () => { } else { expect(client.metadata()).to.have.property(prop).and.eql(value); } + + if (additionalAssertion) { + return additionalAssertion(client); + } + + return undefined; })); }; @@ -749,6 +755,39 @@ describe('Client metadata validation', () => { rejects(this.title, ['not-a-type', 'none']); }); + context('response_modes', function () { + defaultsTo(this.title, undefined, undefined, undefined, (client) => { + expect(client.responseModeAllowed('query')).to.be.true; + expect(client.responseModeAllowed('fragment')).to.be.true; + expect(client.responseModeAllowed('form_post')).to.be.true; + }); + mustBeArray(this.title); + + allows(this.title, ['query', 'fragment', 'form_post']); + allows(this.title, ['query']); + allows(this.title, ['fragment']); + allows(this.title, ['form_post']); + + allows(this.title, ['query', 'fragment'], undefined, undefined, (client) => { + expect(client.responseModeAllowed('query')).to.be.true; + expect(client.responseModeAllowed('fragment')).to.be.true; + expect(client.responseModeAllowed('form_post')).to.be.false; + }); + + allows(this.title, ['jwt'], undefined, { features: { jwtResponseModes: { enabled: true } } }); + allows(this.title, ['fragment.jwt'], undefined, { features: { jwtResponseModes: { enabled: true } } }); + allows(this.title, ['query.jwt'], undefined, { features: { jwtResponseModes: { enabled: true } } }); + allows(this.title, ['form_post.jwt'], undefined, { features: { jwtResponseModes: { enabled: true } } }); + + allows(this.title, ['web_message'], undefined, { features: { webMessageResponseMode: { enabled: true } } }); + allows(this.title, ['web_message.jwt'], undefined, { features: { webMessageResponseMode: { enabled: true }, jwtResponseModes: { enabled: true } } }); + + rejects(this.title, [123], /must only contain strings$/); + rejects(this.title, [], /must contain members$/); + rejects(this.title, ['not-a-mode']); + rejects(this.title, ['not-a-mode']); + }); + context('sector_identifier_uri', function () { mustBeString(this.title); // must be a valid sector uri => GOTO: pairwise_clients.test.js diff --git a/test/core/basic/code.authorization.test.js b/test/core/basic/code.authorization.test.js index aedd292a3..81943d7fe 100644 --- a/test/core/basic/code.authorization.test.js +++ b/test/core/basic/code.authorization.test.js @@ -491,7 +491,7 @@ describe('BASIC code', () => { .expect(auth.validateState) .expect(auth.validateClientLocation) .expect(auth.validateError('invalid_request')) - .expect(auth.validateErrorDescription('response_mode not allowed for this response_type')); + .expect(auth.validateErrorDescription('requested response_mode is not allowed for the requested response_type')); }); ['request', 'request_uri', 'registration'].forEach((param) => { diff --git a/test/core/hybrid/code+id_token+token.authorization.test.js b/test/core/hybrid/code+id_token+token.authorization.test.js index 1ec30a4b8..ee0a53dd5 100644 --- a/test/core/hybrid/code+id_token+token.authorization.test.js +++ b/test/core/hybrid/code+id_token+token.authorization.test.js @@ -153,7 +153,7 @@ describe('HYBRID code+id_token+token', () => { .expect(auth.validateState) .expect(auth.validateClientLocation) .expect(auth.validateError('invalid_request')) - .expect(auth.validateErrorDescription('response_mode not allowed for this response_type')); + .expect(auth.validateErrorDescription('requested response_mode is not allowed for the requested response_type')); }); it('missing mandatory parameter nonce', function () { diff --git a/test/core/hybrid/code+id_token.authorization.test.js b/test/core/hybrid/code+id_token.authorization.test.js index ec482d758..e948f159b 100644 --- a/test/core/hybrid/code+id_token.authorization.test.js +++ b/test/core/hybrid/code+id_token.authorization.test.js @@ -116,7 +116,7 @@ describe('HYBRID code+id_token', () => { .expect(auth.validateState) .expect(auth.validateClientLocation) .expect(auth.validateError('invalid_request')) - .expect(auth.validateErrorDescription('response_mode not allowed for this response_type')); + .expect(auth.validateErrorDescription('requested response_mode is not allowed for the requested response_type')); }); it('missing mandatory parameter nonce', function () { diff --git a/test/core/hybrid/code+token.authorization.test.js b/test/core/hybrid/code+token.authorization.test.js index 99b978741..349f0be55 100644 --- a/test/core/hybrid/code+token.authorization.test.js +++ b/test/core/hybrid/code+token.authorization.test.js @@ -123,7 +123,7 @@ describe('HYBRID code+token', () => { .expect(auth.validateState) .expect(auth.validateClientLocation) .expect(auth.validateError('invalid_request')) - .expect(auth.validateErrorDescription('response_mode not allowed for this response_type')); + .expect(auth.validateErrorDescription('requested response_mode is not allowed for the requested response_type')); }); }); }); diff --git a/test/core/implicit/id_token+token.authorization.test.js b/test/core/implicit/id_token+token.authorization.test.js index ba7911000..29df5b352 100644 --- a/test/core/implicit/id_token+token.authorization.test.js +++ b/test/core/implicit/id_token+token.authorization.test.js @@ -127,7 +127,7 @@ describe('IMPLICIT id_token+token', () => { .expect(auth.validateState) .expect(auth.validateClientLocation) .expect(auth.validateError('invalid_request')) - .expect(auth.validateErrorDescription('response_mode not allowed for this response_type')); + .expect(auth.validateErrorDescription('requested response_mode is not allowed for the requested response_type')); }); it('missing mandatory parameter nonce', function () { diff --git a/test/core/implicit/id_token.authorization.test.js b/test/core/implicit/id_token.authorization.test.js index c27949aff..a9f1a3d33 100644 --- a/test/core/implicit/id_token.authorization.test.js +++ b/test/core/implicit/id_token.authorization.test.js @@ -81,7 +81,7 @@ describe('IMPLICIT id_token', () => { .expect(auth.validateState) .expect(auth.validateClientLocation) .expect(auth.validateError('invalid_request')) - .expect(auth.validateErrorDescription('response_mode not allowed for this response_type')); + .expect(auth.validateErrorDescription('requested response_mode is not allowed for the requested response_type')); }); it('HMAC ID Token Hint with expired secret errors', async function () { diff --git a/test/fapi/fapi-final.test.js b/test/fapi/fapi-final.test.js index e7321f81f..6b3e8ecbe 100644 --- a/test/fapi/fapi-final.test.js +++ b/test/fapi/fapi-final.test.js @@ -56,8 +56,8 @@ describe('Financial-grade API Security Profile 1.0 - Part 2: Advanced (FINAL) be }) .expect(303) .expect(auth.validateClientLocation) - .expect(auth.validateError('invalid_request')) - .expect(auth.validateErrorDescription('requested response_mode not allowed for the requested response_type in FAPI 1.0 Final')); + .expect(auth.validateError('unauthorized_client')) + .expect(auth.validateErrorDescription('requested response_mode is not allowed for this client or request')); }); it('requires jwt response mode to be used when id token is not issued by authorization endpoint (JAR)', async function () { @@ -66,6 +66,7 @@ describe('Financial-grade API Security Profile 1.0 - Part 2: Advanced (FINAL) be client_id: 'client', response_type: 'code', nonce: 'foo', + iss: 'client', aud: this.provider.issuer, exp: epochTime() + 60, nbf: epochTime(), @@ -84,8 +85,8 @@ describe('Financial-grade API Security Profile 1.0 - Part 2: Advanced (FINAL) be }) .expect(303) .expect(auth.validateClientLocation) - .expect(auth.validateError('invalid_request')) - .expect(auth.validateErrorDescription('requested response_mode not allowed for the requested response_type in FAPI 1.0 Final')); + .expect(auth.validateError('unauthorized_client')) + .expect(auth.validateErrorDescription('requested response_mode is not allowed for this client or request')); }); }); @@ -133,6 +134,7 @@ describe('Financial-grade API Security Profile 1.0 - Part 2: Advanced (FINAL) be aud: this.provider.issuer, // exp: epochTime() + 60, nbf: epochTime(), + iss: 'client', client_id: 'client', scope: 'openid', response_type: 'code id_token', @@ -168,6 +170,7 @@ describe('Financial-grade API Security Profile 1.0 - Part 2: Advanced (FINAL) be // nbf: epochTime(), client_id: 'client', scope: 'openid', + iss: 'client', response_type: 'code id_token', nonce: 'foo', }).setProtectedHeader({ alg: 'ES256' }).sign(keypair.privateKey); @@ -201,6 +204,7 @@ describe('Financial-grade API Security Profile 1.0 - Part 2: Advanced (FINAL) be aud: this.provider.issuer, client_id: 'client', scope: 'openid', + iss: 'client', response_type: 'code id_token', nonce: 'foo', }).setProtectedHeader({ alg: 'ES256' }).sign(keypair.privateKey); diff --git a/test/fapi/fapi-id2.test.js b/test/fapi/fapi-id2.test.js index 15a36d2c8..27ed761a9 100644 --- a/test/fapi/fapi-id2.test.js +++ b/test/fapi/fapi-id2.test.js @@ -56,14 +56,16 @@ describe('Financial-grade API - Part 2: Read and Write API Security Profile (ID2 }) .expect(303) .expect(auth.validateClientLocation) - .expect(auth.validateError('invalid_request')) - .expect(auth.validateErrorDescription('requested response_mode not allowed for the requested response_type in FAPI 1.0 ID2')); + .expect(auth.validateError('unauthorized_client')) + .expect(auth.validateErrorDescription('requested response_mode is not allowed for this client or request')); }); it('requires jwt response mode to be used when id token is not issued by authorization endpoint (JAR)', async function () { const request = await new SignJWT({ scope: 'openid', client_id: 'client', + iss: 'client', + aud: this.provider.issuer, response_type: 'code', nonce: 'foo', exp: epochTime() + 60, @@ -82,8 +84,8 @@ describe('Financial-grade API - Part 2: Read and Write API Security Profile (ID2 }) .expect(303) .expect(auth.validateClientLocation) - .expect(auth.validateError('invalid_request')) - .expect(auth.validateErrorDescription('requested response_mode not allowed for the requested response_type in FAPI 1.0 ID2')); + .expect(auth.validateError('unauthorized_client')) + .expect(auth.validateErrorDescription('requested response_mode is not allowed for this client or request')); }); }); @@ -129,6 +131,7 @@ describe('Financial-grade API - Part 2: Read and Write API Security Profile (ID2 const request = await new SignJWT({ client_id: 'client', scope: 'openid', + iss: 'client', response_type: 'code id_token', nonce: 'foo', }).setProtectedHeader({ alg: 'ES256' }).sign(keypair.privateKey); diff --git a/test/jwt_response_modes/jwt_response_modes.test.js b/test/jwt_response_modes/jwt_response_modes.test.js index fae128ed4..33ec17b14 100644 --- a/test/jwt_response_modes/jwt_response_modes.test.js +++ b/test/jwt_response_modes/jwt_response_modes.test.js @@ -165,7 +165,7 @@ describe('configuration features.jwtResponseModes', () => { const { payload } = decode(response); expect(payload).to.have.all.keys('error', 'error_description', 'state', 'aud', 'exp', 'iss'); expect(payload.error).to.eql('invalid_request'); - expect(payload.error_description).to.eql('response_mode not allowed for this response_type unless encrypted'); + expect(payload.error_description).to.eql('requested response_mode is not allowed for the requested response_type unless encrypted'); }); }); diff --git a/test/request/jwt_request.test.js b/test/request/jwt_request.test.js index e0f021986..39ac60430 100644 --- a/test/request/jwt_request.test.js +++ b/test/request/jwt_request.test.js @@ -676,17 +676,17 @@ describe('request parameter features', () => { .expect(successFnCheck)); }); - it('re-checks the response mode from the request', function () { + it('checks the response mode from the request', function () { const spy = sinon.spy(); this.provider.once(errorEvt, spy); return JWT.sign({ jti: randomBytes(16).toString('base64url'), - client_id: 'client2', + client_id: 'client', response_type: 'code', redirect_uri: 'https://client.example.com/cb', response_mode: 'foo', - }, Buffer.from('secret'), 'HS256', { issuer: 'client2', audience: this.provider.issuer, expiresIn: 30 }).then((request) => this.wrap({ + }, Buffer.from('secret'), 'HS256', { issuer: 'client', audience: this.provider.issuer, expiresIn: 30 }).then((request) => this.wrap({ agent: this.agent, route, verb, @@ -695,6 +695,7 @@ describe('request parameter features', () => { scope: 'openid', client_id: 'client', response_type: 'code', + response_mode: 'query', }, }) .expect(errorCode)