Skip to content

Commit

Permalink
refactor!: remove introspection and revocation client metadata
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The non-standard `introspection_endpoint_auth_method`, and `introspection_endpoint_auth_signing_alg` client metadata properties were removed. The client's `token_endpoint_auth_method`, and `token_endpoint_auth_signing_alg` properties are now used as the only indication of how a client must authenticate at the introspection endpoint. The accompanying JWA and authentication methods configuration properties were also removed.
BREAKING CHANGE: The non-standard `revocation_endpoint_auth_method`, and `revocation_endpoint_auth_signing_alg` client metadata properties were removed. The client's `token_endpoint_auth_method`, and `token_endpoint_auth_signing_alg` properties are now used as the only indication of how a client must authenticate at the revocation endpoint. The accompanying JWA and authentication methods configuration properties were also removed.
  • Loading branch information
panva committed Dec 1, 2022
1 parent 7d192a1 commit a6433d0
Show file tree
Hide file tree
Showing 18 changed files with 60 additions and 326 deletions.
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ _**default value**_:
<a id="clients-available-metadata"></a><details><summary>(Click to expand) Available Metadata</summary><br>


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 <br/><br/>The following metadata is available but may not be recognized depending on your provider's configuration.<br/><br/> 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_endpoint_auth_method, introspection_endpoint_auth_signing_alg, introspection_signed_response_alg, request_object_encryption_alg, request_object_encryption_enc, request_object_signing_alg, request_uris, revocation_endpoint_auth_method, revocation_endpoint_auth_signing_alg, 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, scope, sector_identifier_uri, subject_type, token_endpoint_auth_method, tos_uri, userinfo_signed_response_alg <br/><br/>The following metadata is available but may not be recognized depending on your provider's configuration.<br/><br/> 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


</details>
Expand Down Expand Up @@ -1149,7 +1149,7 @@ Helper function used to determine whether the client/RS (client argument) is all
_**default value**_:
```js
async function introspectionAllowedPolicy(ctx, client, token) {
if (client.introspectionEndpointAuthMethod === 'none' && token.clientId !== ctx.oidc.client.clientId) {
if (client.tokenEndpointAuthMethod === 'none' && token.clientId !== ctx.oidc.client.clientId) {
return false;
}
return true;
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/authorization/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ module.exports = function authorizationAction(provider, endpoint) {
use(() => deviceUserFlow.bind(undefined, allowList), CV, DR );
use(() => parseBody, A, DA, PAR, BA);
if (authRequired.has(endpoint)) {
const { params: authParams, middleware: tokenAuth } = getTokenAuth(provider, 'token');
const { params: authParams, middleware: tokenAuth } = getTokenAuth(provider);
use(() => paramsMiddleware.bind(undefined, authParams), DA, PAR, BA);
tokenAuth.forEach((tokenAuthMiddleware) => {
use(() => tokenAuthMiddleware, DA, PAR, BA);
Expand Down
4 changes: 0 additions & 4 deletions lib/actions/discovery.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ module.exports = function discovery(ctx, next) {

if (features.introspection.enabled) {
ctx.body.introspection_endpoint = ctx.oidc.urlFor('introspection');
ctx.body.introspection_endpoint_auth_methods_supported = [...config.introspectionEndpointAuthMethods];
ctx.body.introspection_endpoint_auth_signing_alg_values_supported = config.introspectionEndpointAuthSigningAlgValues;
}

if (features.jwtIntrospection.enabled) {
Expand All @@ -86,8 +84,6 @@ module.exports = function discovery(ctx, next) {

if (features.revocation.enabled) {
ctx.body.revocation_endpoint = ctx.oidc.urlFor('revocation');
ctx.body.revocation_endpoint_auth_methods_supported = [...config.revocationEndpointAuthMethods];
ctx.body.revocation_endpoint_auth_signing_alg_values_supported = config.revocationEndpointAuthSigningAlgValues;
}

if (features.encryption.enabled) {
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/introspection.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const introspectable = new Set(['AccessToken', 'ClientCredentials', 'RefreshToke
const JWT = 'application/token-introspection+jwt';

module.exports = function introspectionAction(provider) {
const { params: authParams, middleware: tokenAuth } = getTokenAuth(provider, 'introspection');
const { params: authParams, middleware: tokenAuth } = getTokenAuth(provider);
const PARAM_LIST = new Set(['token', 'token_type_hint', ...authParams]);
const configuration = instance(provider).configuration();
const {
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/revocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const revoke = require('../helpers/revoke');
const revokeable = new Set(['AccessToken', 'ClientCredentials', 'RefreshToken']);

module.exports = function revocationAction(provider) {
const { params: authParams, middleware: tokenAuth } = getTokenAuth(provider, 'revocation');
const { params: authParams, middleware: tokenAuth } = getTokenAuth(provider);
const PARAM_LIST = new Set(['token', 'token_type_hint', ...authParams]);
const { grantTypeHandlers } = instance(provider);

Expand Down
2 changes: 1 addition & 1 deletion lib/actions/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const paramsMiddleware = require('../shared/assemble_params');
const grantTypeSet = new Set(['grant_type']);

module.exports = function tokenAction(provider) {
const { params: authParams, middleware: tokenAuth } = getTokenAuth(provider, 'token');
const { params: authParams, middleware: tokenAuth } = getTokenAuth(provider);
const { grantTypeParams } = instance(provider);

return [
Expand Down
4 changes: 0 additions & 4 deletions lib/consts/client_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,6 @@ const STRING = [
'userinfo_encrypted_response_enc',
'userinfo_signed_response_alg',

// must be after token_endpoint_auth_method
'introspection_endpoint_auth_method',
'revocation_endpoint_auth_method',

// in arrays
'contacts',
'default_acr_values',
Expand Down
2 changes: 0 additions & 2 deletions lib/consts/jwa.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ const encryptionEncValues = [

module.exports = {
tokenEndpointAuthSigningAlgValues: [...signingAlgValues],
introspectionEndpointAuthSigningAlgValues: [...signingAlgValues],
revocationEndpointAuthSigningAlgValues: [...signingAlgValues],

idTokenSigningAlgValues: [...signingAlgValues, 'none'],
requestObjectSigningAlgValues: [...signingAlgValues, 'none'],
Expand Down
80 changes: 8 additions & 72 deletions lib/helpers/client_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const pick = require('./_/pick');
const without = require('./_/without');
const omitBy = require('./_/omit_by');

const clientAuthEndpoints = ['token', 'introspection', 'revocation'];
const W3CEmailRegExp = /^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
const needsJwks = {
jwe: /^(RSA|ECDH)/,
Expand All @@ -37,10 +36,6 @@ function isUndefined(value) {
return value === undefined;
}

function checkClientAuth(schema) {
return !!clientAuthEndpoints.find((endpoint) => ['private_key_jwt', 'self_signed_tls_client_auth'].includes(schema[`${endpoint}_endpoint_auth_method`]));
}

module.exports = function getSchema(provider) {
const configuration = instance(provider).configuration();
const { features } = configuration;
Expand Down Expand Up @@ -73,11 +68,6 @@ module.exports = function getSchema(provider) {
}

if (features.introspection.enabled) {
RECOGNIZED_METADATA.push('introspection_endpoint_auth_method');
if (configuration.introspectionEndpointAuthSigningAlgValues) {
RECOGNIZED_METADATA.push('introspection_endpoint_auth_signing_alg');
}

if (features.jwtIntrospection.enabled) {
RECOGNIZED_METADATA.push('introspection_signed_response_alg');

Expand All @@ -88,13 +78,6 @@ module.exports = function getSchema(provider) {
}
}

if (features.revocation.enabled) {
RECOGNIZED_METADATA.push('revocation_endpoint_auth_method');
if (configuration.revocationEndpointAuthSigningAlgValues) {
RECOGNIZED_METADATA.push('revocation_endpoint_auth_signing_alg');
}
}

if (features.rpInitiatedLogout.enabled) {
RECOGNIZED_METADATA.push('post_logout_redirect_uris');
}
Expand Down Expand Up @@ -215,50 +198,12 @@ module.exports = function getSchema(provider) {
authorization_encrypted_response_alg: () => configuration.authorizationEncryptionAlgValues,
authorization_encrypted_response_enc: () => configuration.authorizationEncryptionEncValues,
authorization_signed_response_alg: () => configuration.authorizationSigningAlgValues,

// must be after token_* specific
introspection_endpoint_auth_method: () => configuration.introspectionEndpointAuthMethods,
introspection_endpoint_auth_signing_alg: ({ introspection_endpoint_auth_method: method }) => {
switch (method) {
case 'private_key_jwt':
return configuration.introspectionEndpointAuthSigningAlgValues.filter((x) => !x.startsWith('HS'));
case 'client_secret_jwt':
return configuration.introspectionEndpointAuthSigningAlgValues.filter((x) => x.startsWith('HS'));
default:
return [];
}
},
revocation_endpoint_auth_method: () => configuration.revocationEndpointAuthMethods,
revocation_endpoint_auth_signing_alg: ({ revocation_endpoint_auth_method: method }) => {
switch (method) {
case 'private_key_jwt':
return configuration.revocationEndpointAuthSigningAlgValues.filter((x) => !x.startsWith('HS'));
case 'client_secret_jwt':
return configuration.revocationEndpointAuthSigningAlgValues.filter((x) => x.startsWith('HS'));
default:
return [];
}
},
};

class Schema {
constructor(
metadata, ctx, processCustomMetadata = !!configuration.extraClientMetadata.properties.length,
) {
// unless explicitly provided use token_* values
['revocation', 'introspection'].forEach((endpoint) => {
if (metadata[`${endpoint}_endpoint_auth_method`] === undefined) {
Object.assign(metadata, {
[`${endpoint}_endpoint_auth_method`]: metadata.token_endpoint_auth_method || configuration.clientDefaults.token_endpoint_auth_method,
});
}
if (metadata[`${endpoint}_endpoint_auth_signing_alg`] === undefined && metadata.token_endpoint_auth_signing_alg) {
Object.assign(metadata, {
[`${endpoint}_endpoint_auth_signing_alg`]: metadata.token_endpoint_auth_signing_alg,
});
}
});

Object.assign(
this,
omitBy(
Expand Down Expand Up @@ -329,24 +274,15 @@ module.exports = function getSchema(provider) {
this.tls_client_auth_subject_dn,
].filter(Boolean);

let used;
for (const endpoint of clientAuthEndpoints) { // eslint-disable-line no-restricted-syntax
if (this[`${endpoint}_endpoint_auth_method`] === 'tls_client_auth') {
if (length === 0) {
this.invalidate('tls_client_auth requires one of the certificate subject value parameters');
}

if (length !== 1) {
this.invalidate('only one tls_client_auth certificate subject value must be provided');
}

used = true;

break;
if (this.token_endpoint_auth_method === 'tls_client_auth') {
if (length === 0) {
this.invalidate('tls_client_auth requires one of the certificate subject value parameters');
}
}

if (length && !used) {
if (length !== 1) {
this.invalidate('only one tls_client_auth certificate subject value must be provided');
}
} else {
delete this.tls_client_auth_san_dns;
delete this.tls_client_auth_san_email;
delete this.tls_client_auth_san_ip;
Expand Down Expand Up @@ -430,7 +366,7 @@ module.exports = function getSchema(provider) {
}
});

const requireJwks = checkClientAuth(this)
const requireJwks = ['private_key_jwt', 'self_signed_tls_client_auth'].includes(this.token_endpoint_auth_method)
|| (needsJwks.jws.test(this.request_object_signing_alg))
|| (needsJwks.jws.test(this.backchannel_authentication_request_signing_alg))
|| (needsJwks.jwe.test(this.id_token_encrypted_response_alg))
Expand Down
76 changes: 19 additions & 57 deletions lib/helpers/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,6 @@ const { STABLE, DRAFTS } = require('./features');
const attention = require('./attention');
const instance = require('./weak_cache');

function authEndpointDefaults(config) {
[
'tokenEndpointAuthMethods',
'tokenEndpointAuthSigningAlgValues',
'enabledJWA.tokenEndpointAuthSigningAlgValues',
].forEach((prop) => {
['introspection', 'revocation'].forEach((endpoint) => {
if (get(config, prop) && !get(config, prop.replace('token', endpoint))) {
set(config, prop.replace('token', endpoint), get(config, prop));
}
});
});
}

function featuresTypeErrorCheck({ features }) {
for (const value of Object.values(features)) { // eslint-disable-line no-restricted-syntax
if (typeof value === 'boolean') {
Expand All @@ -35,17 +21,6 @@ function featuresTypeErrorCheck({ features }) {
}
}

function clientAuthDefaults(clientDefaults) {
['token_endpoint_auth_method', 'token_endpoint_auth_signing_alg'].forEach((prop) => {
['introspection', 'revocation'].forEach((endpoint) => {
const endpointProp = prop.replace('token_', `${endpoint}_`);
if (clientDefaults[prop] && !clientDefaults[endpointProp]) {
set(clientDefaults, endpointProp, get(clientDefaults, prop));
}
});
});
}

function filterHS(alg) {
return alg.startsWith('HS');
}
Expand All @@ -62,12 +37,9 @@ const fapiProfiles = new Set(['1.0 Final', '1.0 ID2']);

class Configuration {
constructor(config) {
authEndpointDefaults(config);

Object.assign(this, merge({}, this.defaults, pick(config, ...Object.keys(this.defaults))));

featuresTypeErrorCheck(this);
clientAuthDefaults(this.clientDefaults);

this.logDraftNotice();

Expand Down Expand Up @@ -117,9 +89,7 @@ class Configuration {

ensureSets() {
[
'scopes', 'subjectTypes', 'extraParams', 'acrValues',
'tokenEndpointAuthMethods', 'introspectionEndpointAuthMethods', 'revocationEndpointAuthMethods',
'features.ciba.deliveryModes',
'scopes', 'subjectTypes', 'extraParams', 'acrValues', 'tokenEndpointAuthMethods', 'features.ciba.deliveryModes',
].forEach((prop) => {
if (!(get(this, prop) instanceof Set)) {
if (!Array.isArray(get(this, prop))) {
Expand Down Expand Up @@ -298,28 +268,22 @@ class Configuration {

this.setAlgs('dPoPSigningAlgValues', allowList.dPoPSigningAlgValues.slice(), 'dPoP.enabled');

this.endpointAuth('token');
this.endpointAuth('introspection');
this.endpointAuth('revocation');
}

endpointAuth(endpoint) {
this[`${endpoint}EndpointAuthSigningAlgValues`] = this.enabledJWA[`${endpoint}EndpointAuthSigningAlgValues`];
this.tokenEndpointAuthSigningAlgValues = this.enabledJWA.tokenEndpointAuthSigningAlgValues;

if (!this[`${endpoint}EndpointAuthMethods`].has('client_secret_jwt')) {
remove(this[`${endpoint}EndpointAuthSigningAlgValues`], filterHS);
} else if (!this[`${endpoint}EndpointAuthSigningAlgValues`].find(filterHS)) {
this[`${endpoint}EndpointAuthMethods`].delete('client_secret_jwt');
if (!this.tokenEndpointAuthMethods.has('client_secret_jwt')) {
remove(this.tokenEndpointAuthSigningAlgValues, filterHS);
} else if (!this.tokenEndpointAuthSigningAlgValues.find(filterHS)) {
this.tokenEndpointAuthMethods.delete('client_secret_jwt');
}

if (!this[`${endpoint}EndpointAuthMethods`].has('private_key_jwt')) {
remove(this[`${endpoint}EndpointAuthSigningAlgValues`], filterAsymmetricSig);
} else if (!this[`${endpoint}EndpointAuthSigningAlgValues`].find(filterAsymmetricSig)) {
this[`${endpoint}EndpointAuthMethods`].delete('private_key_jwt');
if (!this.tokenEndpointAuthMethods.has('private_key_jwt')) {
remove(this.tokenEndpointAuthSigningAlgValues, filterAsymmetricSig);
} else if (!this.tokenEndpointAuthSigningAlgValues.find(filterAsymmetricSig)) {
this.tokenEndpointAuthMethods.delete('private_key_jwt');
}

if (!this[`${endpoint}EndpointAuthSigningAlgValues`].length) {
this[`${endpoint}EndpointAuthSigningAlgValues`] = undefined;
if (!this.tokenEndpointAuthSigningAlgValues.length) {
this.tokenEndpointAuthSigningAlgValues = undefined;
}
}

Expand Down Expand Up @@ -457,15 +421,13 @@ class Configuration {
authMethods.add('self_signed_tls_client_auth');
}

['token', 'introspection', 'revocation'].forEach((endpoint) => {
if (this[`${endpoint}EndpointAuthMethods`]) {
this[`${endpoint}EndpointAuthMethods`].forEach((method) => {
if (!authMethods.has(method)) {
throw new TypeError(`only supported ${endpoint}EndpointAuthMethods are ${formatters.formatList([...authMethods])}`);
}
});
}
});
if (this.tokenEndpointAuthMethods) {
this.tokenEndpointAuthMethods.forEach((method) => {
if (!authMethods.has(method)) {
throw new TypeError(`only supported tokenEndpointAuthMethods are ${formatters.formatList([...authMethods])}`);
}
});
}
}

checkDeviceFlow() {
Expand Down
Loading

0 comments on commit a6433d0

Please sign in to comment.