Skip to content

Commit

Permalink
feat: update PAR implementation to ietf draft 02
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Jul 14, 2020
1 parent fded7c6 commit fd2ccee
Show file tree
Hide file tree
Showing 14 changed files with 509 additions and 391 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ The following draft specifications are implemented by oidc-provider.
- [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 01][dpop]
- [OAuth 2.0 JWT Secured Authorization Request (JAR)][jar]
- [OAuth 2.0 Pushed Authorization Requests - draft 01][par]
- [OAuth 2.0 Pushed Authorization Requests - draft 02][par]
- [OAuth 2.0 Resource Indicators - draft 08][resource-indicators]
- [OAuth 2.0 Web Message Response Mode - individual draft 00][wmrm]
- [OpenID Connect Back-Channel Logout 1.0 - draft 04][backchannel-logout]
Expand Down Expand Up @@ -173,4 +173,4 @@ See the list of available emitted [event names](/docs/events.md) and their descr
[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
[support-sponsor]: https://github.com/sponsors/panva
[par]: https://tools.ietf.org/html/draft-ietf-oauth-par-01
[par]: https://tools.ietf.org/html/draft-ietf-oauth-par-02
18 changes: 17 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1281,10 +1281,26 @@ Enables the use of `pushed_authorization_request_endpoint` defined by the Pushed
_**default value**_:
```js
{
enabled: false
enabled: false,
requirePushedAuthorizationRequests: false
}
```

<details><summary>(Click to expand) features.pushedAuthorizationRequests options details</summary><br>


#### requirePushedAuthorizationRequests

Makes the use of PAR required for all authorization requests as an OP policy.


_**default value**_:
```js
false
```

</details>

### features.registration

[Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0.html)
Expand Down
20 changes: 10 additions & 10 deletions lib/actions/authorization/process_request_object.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ const checkResponseMode = require('./check_response_mode');
module.exports = async function processRequestObject(PARAM_LIST, rejectDupesMiddleware, ctx, next) {
const { params, client, route } = ctx.oidc;

const pushedRequestObject = 'PushedAuthorizationRequest' in ctx.oidc.entities;
if (client.requirePushedAuthorizationRequests && route !== 'pushed_authorization_request' && !pushedRequestObject) {
throw new InvalidRequest('Pushed Authorization Request must be used');
}

if (
client.requestObjectSigningAlg
&& params.request === undefined
&& route !== 'pushed_authorization_request'
) {
throw new InvalidRequest('Request Object must be used by this client');
}
Expand Down Expand Up @@ -129,16 +133,12 @@ module.exports = async function processRequestObject(PARAM_LIST, rejectDupesMidd
}
}

const pushedRequestObject = 'PushedAuthorizationRequest' in ctx.oidc.entities;

if (!(alg === 'none' && pushedRequestObject)) {
if (client.requestObjectSigningAlg && client.requestObjectSigningAlg !== alg) {
throw new InvalidRequestObject('the preregistered alg must be used in request or request_uri');
}
if (client.requestObjectSigningAlg && alg !== client.requestObjectSigningAlg) {
throw new InvalidRequestObject('the preregistered alg must be used in request or request_uri');
}

if (!conf('requestObjectSigningAlgValues').includes(alg)) {
throw new InvalidRequestObject('unsupported signed request alg');
}
if (!pushedRequestObject && !conf('requestObjectSigningAlgValues').includes(alg)) {
throw new InvalidRequestObject('unsupported signed request alg');
}

const opts = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@ const { InvalidRequest } = require('../../helpers/errors');
module.exports = function pushedAuthorizationRequestParams(ctx, next) {
const JAR = !!ctx.oidc.params.request;

if (
ctx.oidc.client.requestObjectSigningAlg
&& ctx.oidc.client.tokenEndpointAuthMethod === 'none'
&& !JAR
) {
throw new InvalidRequest('Request Object must be used by this client');
}

for (const [param, value] of Object.entries(ctx.oidc.params)) { // eslint-disable-line no-restricted-syntax, max-len
if (value !== undefined) {
if (param === 'request_uri') {
Expand Down
6 changes: 5 additions & 1 deletion lib/actions/discovery.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ module.exports = function discovery(ctx, next) {
request_parameter_supported: features.requestObjects.request,
request_uri_parameter_supported: features.requestObjects.requestUri,
require_request_uri_registration: features.requestObjects.requestUri && features.requestObjects.requireUriRegistration ? true : undefined,
pushed_authorization_request_endpoint: features.pushedAuthorizationRequests.enabled ? ctx.oidc.urlFor('pushed_authorization_request') : undefined,
response_modes_supported: ['form_post', 'fragment', 'query'],
response_types_supported: config.responseTypes,
scopes_supported: [...config.scopes].concat([...config.dynamicScopes].map((s) => s[DYNAMIC_SCOPE_LABEL]).filter(Boolean)),
Expand All @@ -38,6 +37,11 @@ module.exports = function discovery(ctx, next) {
token_endpoint: ctx.oidc.urlFor('token'),
};

if (features.pushedAuthorizationRequests.enabled) {
ctx.body.pushed_authorization_request_endpoint = ctx.oidc.urlFor('pushed_authorization_request');
ctx.body.require_pushed_authorization_requests = features.pushedAuthorizationRequests.requirePushedAuthorizationRequests ? true : undefined;
}

if (features.userinfo.enabled) {
ctx.body.userinfo_endpoint = ctx.oidc.urlFor('userinfo');
if (features.jwtUserinfo.enabled) {
Expand Down
2 changes: 2 additions & 0 deletions lib/consts/client_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const DEFAULT = {
post_logout_redirect_uris: [],
backchannel_logout_session_required: false,
frontchannel_logout_session_required: false,
require_pushed_authorization_requests: false,
};

const REQUIRED = [
Expand All @@ -53,6 +54,7 @@ const BOOL = [
'frontchannel_logout_session_required',
'require_auth_time',
'tls_client_certificate_bound_access_tokens',
'require_pushed_authorization_requests',
];

const ARYS = [
Expand Down
16 changes: 15 additions & 1 deletion lib/helpers/client_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ module.exports = function getSchema(provider) {
}
}

if (features.pushedAuthorizationRequests.enabled) {
RECOGNIZED_METADATA.push('require_pushed_authorization_requests');
}

if (features.encryption.enabled) {
RECOGNIZED_METADATA.push('id_token_encrypted_response_alg');
RECOGNIZED_METADATA.push('id_token_encrypted_response_enc');
Expand Down Expand Up @@ -212,7 +216,9 @@ module.exports = function getSchema(provider) {
};

class Schema {
constructor(metadata, ctx, processCustomMetadata = !!configuration.extraClientMetadata.properties.length) {
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) {
Expand Down Expand Up @@ -247,6 +253,7 @@ module.exports = function getSchema(provider) {
this.webMessageUris();
this.checkContacts();
this.backchannelLogoutNeedsIdTokenAlg();
this.parPolicy();

// max_age and client_secret_expires_at format
['default_max_age', 'client_secret_expires_at'].forEach((prop) => {
Expand Down Expand Up @@ -575,6 +582,13 @@ module.exports = function getSchema(provider) {
});
}

parPolicy() {
const par = configuration.features.pushedAuthorizationRequests;
if (par.enabled && par.requirePushedAuthorizationRequests) {
this.require_pushed_authorization_requests = true;
}
}

ensureStripUnrecognized() {
const whitelisted = [...RECOGNIZED_METADATA, ...configuration.extraClientMetadata.properties];
Object.keys(this).forEach((prop) => {
Expand Down
12 changes: 11 additions & 1 deletion lib/helpers/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -1127,7 +1127,17 @@ function getDefaults() {
* Authorization Requests draft.
*
*/
pushedAuthorizationRequests: { enabled: false },
pushedAuthorizationRequests: {
enabled: false,

/*
* features.pushedAuthorizationRequests.requirePushedAuthorizationRequests
*
* description: Makes the use of PAR required for all authorization
* requests as an OP policy.
*/
requirePushedAuthorizationRequests: false,
},

/*
* features.registration
Expand Down
6 changes: 3 additions & 3 deletions lib/helpers/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ const DRAFTS = new Map(Object.entries({
version: [1, 2, 'draft-02'],
},
pushedAuthorizationRequests: {
name: 'OAuth 2.0 Pushed Authorization Requests - draft 01',
name: 'OAuth 2.0 Pushed Authorization Requests - draft 02',
type: 'IETF OAuth Working Group draft',
url: 'https://tools.ietf.org/html/draft-ietf-oauth-par-01',
version: [0, 'individual-draft-01', 'draft-00', 'draft-01'],
url: 'https://tools.ietf.org/html/draft-ietf-oauth-par-02',
version: [0, 'individual-draft-01', 'draft-00', 'draft-01', 'draft-02'],
},
resourceIndicators: {
name: 'Resource Indicators for OAuth 2.0 - draft 08',
Expand Down
24 changes: 24 additions & 0 deletions test/configuration/client_metadata.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,30 @@ describe('Client metadata validation', () => {
});
});

describe('features.pushedAuthorizationRequests', () => {
context('require_pushed_authorization_requests', function () {
const configuration = (value = false) => ({
features: {
pushedAuthorizationRequests: {
enabled: true,
requirePushedAuthorizationRequests: value,
},
},
});
mustBeBoolean(this.title, undefined, configuration());
mustBeBoolean(this.title, undefined, configuration(true));
defaultsTo(this.title, false, undefined, configuration());
defaultsTo(this.title, true, undefined, configuration(true));
defaultsTo(this.title, true, {
require_pushed_authorization_requests: false,
}, configuration(true));
defaultsTo(this.title, true, undefined, {
...configuration(),
clientDefaults: { require_pushed_authorization_requests: true },
});
});
});

context('jwks', function () {
const configuration = {
features: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,30 @@ const config = cloneDeep(require('../default.config'));
config.whitelistedJWA.requestObjectSigningAlgValues = config.whitelistedJWA.requestObjectSigningAlgValues.filter((alg) => alg !== 'none');

merge(config.features, {
pushedAuthorizationRequests: { enabled: true },
pushedAuthorizationRequests: {
requirePushedAuthorizationRequests: false,
enabled: true,
},
requestObjects: {
request: true,
},
});

module.exports = {
config,
clients: [{
client_id: 'client',
client_secret: 'secret',
request_object_signing_alg: 'HS256',
redirect_uris: ['https://rp.example.com/cb'],
}, {
client_id: 'client-none',
request_object_signing_alg: 'RS256',
client_id: 'client-par-required',
client_secret: 'secret',
redirect_uris: ['https://rp.example.com/cb'],
require_pushed_authorization_requests: true,
}, {
client_id: 'client-alg-registered',
client_secret: 'secret',
request_object_signing_alg: 'HS256',
redirect_uris: ['https://rp.example.com/cb'],
token_endpoint_auth_method: 'none',
jwks_uri: 'https://rp.example.com/jwks',
}],
};
Loading

0 comments on commit fd2ccee

Please sign in to comment.