Skip to content

Commit

Permalink
feat: add options to disable userinfo and userinfo jwt responses
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Jun 22, 2019
1 parent b1d4ddc commit 3620aed
Show file tree
Hide file tree
Showing 18 changed files with 174 additions and 66 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@

[![build][travis-image]][travis-url] [![codecov][codecov-image]][codecov-url]

oidc-provider is an OpenID Provider implementation of [OpenID Connect][openid-connect]. It allows to
export a complete mountable or standalone OpenID Provider implementation. This implementation does
not dictate a fixed data model or persistence store, instead, you must provide adapters for these.
A generic in-memory adapter is available to get you started as well as feature-less dev-only views
to be able to get off the ground.
oidc-provider is an OAuth 2.0 Authorization Server with [OpenID Connect][openid-connect] and many
additional features and standards implemented.

## v6.0.0 beta notice

Expand Down Expand Up @@ -44,16 +41,16 @@ See [v5.x](https://github.com/panva/node-oidc-provider/tree/v5.x) for the last v
The following specifications are implemented by oidc-provider. Note that not all features are
enabled by default, check the configuration section on how to enable them.

- [OpenID Connect Core 1.0][core]
- [RFC6749 - OAuth 2.0][oauth2] & [OpenID Connect Core 1.0][core]
- Authorization (Authorization Code Flow, Implicit Flow, Hybrid Flow)
- UserInfo Endpoint and ID Tokens including Signing and Encryption
- Passing a Request Object by Value or Reference including Signing and Encryption
- Passing a Request Object by Value or Reference including Signing and Encryption (JAR - JWT Secured Authorization Request)
- Public and Pairwise Subject Identifier Types
- Offline Access / Refresh Token Grant
- Client Credentials Grant
- Client Authentication incl. client_secret_jwt and private_key_jwt methods
- [OpenID Connect Discovery 1.0][discovery]
- [OpenID Connect Dynamic Client Registration 1.0][registration]
- [OpenID Connect Dynamic Client Registration 1.0][registration] and [RFC7591 - OAuth 2.0 Dynamic Client Registration Protocol][oauth2-registration]
- [OAuth 2.0 Form Post Response Mode][form-post]
- [RFC7636 - Proof Key for Code Exchange by OAuth Public Clients][pkce]
- [RFC7009 - OAuth 2.0 Token Revocation][revocation]
Expand All @@ -63,7 +60,7 @@ enabled by default, check the configuration section on how to enable them.
The following drafts/experimental specifications are implemented by oidc-provider.
- [JWT Response for OAuth Token Introspection - draft 03][jwt-introspection]
- [JWT Secured Authorization Response Mode for OAuth 2.0 (JARM) - draft 02][jarm]
- [OAuth 2.0 Device Authorization Grant - draft 15][device-flow]
- [OAuth 2.0 Device Authorization Grant (Device Flow) - draft 15][device-flow]
- [OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound Access Tokens - draft 12][mtls]
- [OAuth 2.0 Resource Indicators - draft 02][resource-indicators]
- [OAuth 2.0 Web Message Response Mode - draft 00][wmrm]
Expand Down Expand Up @@ -176,9 +173,12 @@ See the list of available emitted [event names](/docs/events.md) and their descr
[openid-connect]: https://openid.net/connect/
[core]: https://openid.net/specs/openid-connect-core-1_0.html
[discovery]: https://openid.net/specs/openid-connect-discovery-1_0.html
[oauth2-registration]: https://tools.ietf.org/html/rfc7591
[registration]: https://openid.net/specs/openid-connect-registration-1_0.html
[session-management]: https://openid.net/specs/openid-connect-session-1_0-28.html
[form-post]: https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html
[oauth2]: https://tools.ietf.org/html/rfc6749
[oauth2-bearer]: https://tools.ietf.org/html/rfc6750
[revocation]: https://tools.ietf.org/html/rfc7009
[introspection]: https://tools.ietf.org/html/rfc7662
[pkce]: https://tools.ietf.org/html/rfc7636
Expand Down
4 changes: 4 additions & 0 deletions certification/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ let server;
if (ctx.oidc && ctx.oidc.route === 'discovery') {
ctx.body.mtls_endpoint_aliases = {};
['token', 'introspection', 'revocation', 'userinfo', 'device_authorization'].forEach((endpoint) => {
if (!ctx.body[`${endpoint}_endpoint`]) {
return;
}

ctx.body.mtls_endpoint_aliases[`${endpoint}_endpoint`] = ctx.body[`${endpoint}_endpoint`].replace('https://', 'https://mtls.');
if (ctx.body[`${endpoint}_endpoint_auth_methods_supported`]) {
const methods = new Set(ctx.body[`${endpoint}_endpoint_auth_methods_supported`]);
Expand Down
44 changes: 37 additions & 7 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,22 @@ If you or your business use oidc-provider, please consider becoming a [Patron][s
- [certificateBoundAccessTokens](#featurescertificateboundaccesstokens)
- [claimsParameter](#featuresclaimsparameter)
- [clientCredentials](#featuresclientcredentials)
- [devInteractions](#featuresdevinteractions)
- [deviceFlow](#featuresdeviceflow)
- [devInteractions](#featuresdevinteractions)
- [encryption](#featuresencryption)
- [frontchannelLogout](#featuresfrontchannellogout)
- [introspection](#featuresintrospection)
- [jwtIntrospection](#featuresjwtintrospection)
- [jwtResponseModes](#featuresjwtresponsemodes)
- [jwtUserinfo](#featuresjwtuserinfo)
- [registration](#featuresregistration)
- [registrationManagement](#featuresregistrationmanagement)
- [request](#featuresrequest)
- [requestUri](#featuresrequesturi)
- [resourceIndicators](#featuresresourceindicators)
- [revocation](#featuresrevocation)
- [sessionManagement](#featuressessionmanagement)
- [userinfo](#featuresuserinfo)
- [webMessageResponseMode](#featureswebmessageresponsemode)
- [acrValues](#acrvalues)
- [audiences](#audiences)
Expand Down Expand Up @@ -744,7 +746,7 @@ When doing that be sure to remove the client provided headers of the same name o

### features.claimsParameter

[Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.5.5) - Requesting Claims using the "claims" Request Parameter
[Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter) - Requesting Claims using the "claims" Request Parameter

Enables the use and validations of `claims` parameter as described in the specification.

Expand Down Expand Up @@ -788,7 +790,7 @@ _**default value**_:

[draft-ietf-oauth-device-flow-15](https://tools.ietf.org/html/draft-ietf-oauth-device-flow-15) - OAuth 2.0 Device Authorization Grant

Enables Device Authorization Grant
Enables Device Authorization Grant (Device Flow)


_**default value**_:
Expand Down Expand Up @@ -1077,6 +1079,20 @@ _**default value**_:
}
```

### features.jwtUserinfo

[Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo) - JWT UserInfo Endpoint Responses

Enables the userinfo to optionally return signed and/or encrypted JWTs, also enables the relevant client metadata for setting up signing and/or encryption


_**default value**_:
```js
{
enabled: true
}
```

### features.registration

[Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0.html)
Expand Down Expand Up @@ -1314,7 +1330,7 @@ false

### features.request

[Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.6.1) - Passing a Request Object by Value
[Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#RequestObject) - Passing a Request Object by Value

Enables the use and validations of `request` parameter

Expand All @@ -1328,7 +1344,7 @@ _**default value**_:

### features.requestUri

[Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.6.2) - Passing a Request Object by Reference
[Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#RequestUriParameter) - Passing a Request Object by Reference

Enables the use and validations of `request_uri` parameter

Expand Down Expand Up @@ -1477,6 +1493,20 @@ false

</details>

### features.userinfo

[Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo) - UserInfo Endpoint

Enables the userinfo endpoint.


_**default value**_:
```js
{
enabled: true
}
```

### features.webMessageResponseMode

[draft-sakimura-oauth-wmrm-00](https://tools.ietf.org/html/draft-sakimura-oauth-wmrm-00) - OAuth 2.0 Web Message Response Mode
Expand Down Expand Up @@ -1613,7 +1643,7 @@ _**default value**_:

ID Token only contains End-User claims when the requested `response_type` is `id_token`

[Core 1.0 - 5.4. Requesting Claims using Scope Values](https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.5.4) defines that claims requested using the `scope` parameter are only returned from the UserInfo Endpoint unless the `response_type` is `id_token`. This is the default oidc-provider behaviour, you can turn this behaviour off and return End-User claims with all ID Tokens by providing this configuration as `false`.
[Core 1.0 - Requesting Claims using Scope Values](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) defines that claims requested using the `scope` parameter are only returned from the UserInfo Endpoint unless the `response_type` is `id_token`. This is the default oidc-provider behaviour, you can turn this behaviour off and return End-User claims with all ID Tokens by providing this configuration as `false`.



Expand Down Expand Up @@ -2384,7 +2414,7 @@ async logoutSource(ctx, form) {

### pairwiseIdentifier

Function used by the OP when resolving pairwise ID Token and Userinfo sub claim values. See [Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.8.1)
Function used by the OP when resolving pairwise ID Token and Userinfo sub claim values. See [Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg)

_**recommendation**_: Since this might be called several times in one request with the same arguments consider using memoization or otherwise caching the result based on account and client ids.

Expand Down
57 changes: 33 additions & 24 deletions lib/actions/authorization/check_claims.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,46 @@ const instance = require('../../helpers/weak_cache');
module.exports = function checkClaims(ctx, next) {
const { params } = ctx.oidc;

if (params.claims !== undefined && instance(ctx.oidc.provider).configuration('features.claimsParameter.enabled')) {
if (params.response_type === 'none') {
throw new InvalidRequest('claims parameter should not be combined with response_type none');
}

let claims;
if (params.claims !== undefined) {
const { features: { claimsParameter, userinfo } } = instance(ctx.oidc.provider).configuration();

try {
claims = JSON.parse(params.claims);
} catch (err) {
throw new InvalidRequest('could not parse the claims parameter JSON');
}
if (claimsParameter.enabled) {
if (params.response_type === 'none') {
throw new InvalidRequest('claims parameter should not be combined with response_type none');
}

if (!isPlainObject(claims)) {
throw new InvalidRequest('claims parameter should be a JSON object');
}
let claims;

if (claims.userinfo === undefined && claims.id_token === undefined) {
throw new InvalidRequest('claims parameter should have userinfo or id_token properties');
}
try {
claims = JSON.parse(params.claims);
} catch (err) {
throw new InvalidRequest('could not parse the claims parameter JSON');
}

if (claims.userinfo !== undefined && !isPlainObject(claims.userinfo)) {
throw new InvalidRequest('claims.userinfo should be an object');
}
if (!isPlainObject(claims)) {
throw new InvalidRequest('claims parameter should be a JSON object');
}

if (claims.id_token !== undefined && !isPlainObject(claims.id_token)) {
throw new InvalidRequest('claims.id_token should be an object');
}
if (claims.userinfo === undefined && claims.id_token === undefined) {
throw new InvalidRequest('claims parameter should have userinfo or id_token properties');
}

if (claims.userinfo !== undefined && !isPlainObject(claims.userinfo)) {
throw new InvalidRequest('claims.userinfo should be an object');
}

if (claims.id_token !== undefined && !isPlainObject(claims.id_token)) {
throw new InvalidRequest('claims.id_token should be an object');
}

if (claims.userinfo && !userinfo.enabled) {
throw new InvalidRequest('claims.userinfo should not be used since userinfo endpoint is not supported');
}

if (params.response_type === 'id_token' && claims.userinfo) {
throw new InvalidRequest('claims.userinfo should not be used if access_token is not issued');
if (params.response_type === 'id_token' && claims.userinfo) {
throw new InvalidRequest('claims.userinfo should not be used if access_token is not issued');
}
}
}

Expand Down
7 changes: 5 additions & 2 deletions lib/actions/authorization/process_response_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,11 @@ async function idTokenHandler(ctx) {
auth_time: ctx.oidc.session.authTime(),
}, { ctx });

const { conformIdTokenClaims } = instance(ctx.oidc.provider).configuration();
if (conformIdTokenClaims) {
const {
conformIdTokenClaims, features: { userinfo },
} = instance(ctx.oidc.provider).configuration();

if (conformIdTokenClaims && userinfo.enabled) {
if (ctx.oidc.params.response_type === 'id_token') {
idToken.scope = ctx.oidc.acceptedScope();
} else {
Expand Down
16 changes: 12 additions & 4 deletions lib/actions/discovery.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,15 @@ module.exports = function discovery(ctx, next) {
token_endpoint_auth_methods_supported: [...config.tokenEndpointAuthMethods],
token_endpoint_auth_signing_alg_values_supported: config.tokenEndpointAuthSigningAlgValues,
token_endpoint: ctx.oidc.urlFor('token'),
userinfo_endpoint: ctx.oidc.urlFor('userinfo'),
userinfo_signing_alg_values_supported: config.userinfoSigningAlgValues,
};

if (config.features.userinfo.enabled) {
ctx.body.userinfo_endpoint = ctx.oidc.urlFor('userinfo');
if (config.features.jwtUserinfo.enabled) {
ctx.body.userinfo_signing_alg_values_supported = config.userinfoSigningAlgValues;
}
}

if (config.features.webMessageResponseMode.enabled) {
ctx.body.response_modes_supported.push('web_message');
}
Expand Down Expand Up @@ -72,8 +77,11 @@ module.exports = function discovery(ctx, next) {
if (config.features.encryption.enabled) {
ctx.body.id_token_encryption_alg_values_supported = config.idTokenEncryptionAlgValues;
ctx.body.id_token_encryption_enc_values_supported = config.idTokenEncryptionEncValues;
ctx.body.userinfo_encryption_alg_values_supported = config.userinfoEncryptionAlgValues;
ctx.body.userinfo_encryption_enc_values_supported = config.userinfoEncryptionEncValues;

if (config.features.jwtUserinfo.enabled) {
ctx.body.userinfo_encryption_alg_values_supported = config.userinfoEncryptionAlgValues;
ctx.body.userinfo_encryption_enc_values_supported = config.userinfoEncryptionEncValues;
}

if (config.features.jwtIntrospection.enabled) {
ctx.body.introspection_encryption_alg_values_supported = config.introspectionEncryptionAlgValues;
Expand Down
3 changes: 2 additions & 1 deletion lib/actions/grants/authorization_code.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports.handler = async function authorizationCodeHandler(ctx, next) {
issueRefreshToken,
audiences,
conformIdTokenClaims,
features: { userinfo },
} = instance(ctx.oidc.provider).configuration();

if (ctx.oidc.params.redirect_uri === undefined) {
Expand Down Expand Up @@ -140,7 +141,7 @@ module.exports.handler = async function authorizationCodeHandler(ctx, next) {
auth_time: code.authTime,
}, { ctx });

if (conformIdTokenClaims) {
if (conformIdTokenClaims && userinfo.enabled) {
token.scope = 'openid';
} else {
token.scope = code.scope;
Expand Down
3 changes: 2 additions & 1 deletion lib/actions/grants/device_code.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports.handler = async function deviceCodeHandler(ctx, next) {
issueRefreshToken,
conformIdTokenClaims,
audiences,
features: { userinfo },
} = instance(ctx.oidc.provider).configuration();

const code = await ctx.oidc.provider.DeviceCode.find(ctx.oidc.params.device_code, {
Expand Down Expand Up @@ -144,7 +145,7 @@ module.exports.handler = async function deviceCodeHandler(ctx, next) {
},
}, { ctx });

if (conformIdTokenClaims) {
if (conformIdTokenClaims && userinfo.enabled) {
token.scope = 'openid';
} else {
token.scope = code.scope;
Expand Down
7 changes: 5 additions & 2 deletions lib/actions/grants/refresh_token.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ module.exports.handler = async function refreshTokenHandler(ctx, next) {
const conf = instance(ctx.oidc.provider).configuration();

const {
rotateRefreshToken, audiences, conformIdTokenClaims,
audiences,
conformIdTokenClaims,
rotateRefreshToken,
features: { userinfo },
} = conf;

const {
Expand Down Expand Up @@ -152,7 +155,7 @@ module.exports.handler = async function refreshTokenHandler(ctx, next) {
auth_time: refreshToken.authTime,
}), { ctx });

if (conformIdTokenClaims) {
if (conformIdTokenClaims && userinfo.enabled) {
token.scope = 'openid';
} else {
token.scope = scope;
Expand Down
1 change: 0 additions & 1 deletion lib/consts/client_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ const RECOGNIZED_METADATA = [
'subject_type',
'token_endpoint_auth_method',
'tos_uri',
'userinfo_signed_response_alg',
];

const DEFAULT = {
Expand Down
10 changes: 8 additions & 2 deletions lib/helpers/client_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ module.exports = function getSchema(provider) {
RECOGNIZED_METADATA.push('token_endpoint_auth_signing_alg');
}

if (features.jwtUserinfo.enabled) {
RECOGNIZED_METADATA.push('userinfo_signed_response_alg');
}

if (features.introspection.enabled) {
RECOGNIZED_METADATA.push('introspection_endpoint_auth_method');
if (configuration.introspectionEndpointAuthSigningAlgValues) {
Expand Down Expand Up @@ -122,8 +126,10 @@ module.exports = function getSchema(provider) {
if (features.encryption.enabled) {
RECOGNIZED_METADATA.push('id_token_encrypted_response_alg');
RECOGNIZED_METADATA.push('id_token_encrypted_response_enc');
RECOGNIZED_METADATA.push('userinfo_encrypted_response_alg');
RECOGNIZED_METADATA.push('userinfo_encrypted_response_enc');
if (features.jwtUserinfo.enabled) {
RECOGNIZED_METADATA.push('userinfo_encrypted_response_alg');
RECOGNIZED_METADATA.push('userinfo_encrypted_response_enc');
}
}

if (features.jwtResponseModes.enabled) {
Expand Down
Loading

0 comments on commit 3620aed

Please sign in to comment.