Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

oidc-provider

Package Overview
Dependencies
Maintainers
1
Versions
339
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

oidc-provider - npm Package Compare versions

Comparing version 0.9.0 to 0.10.0

9

CHANGELOG.md
Following semver, 1.0.0 will mark the first API stable release and commence of this file,
until then please use the compare views of github for reference.
- https://github.com/panva/node-oidc-provider/compare/v0.9.0...v0.10.0
- added: custom discovery property config
- added: returning distributed and aggregated claims
- added: Back-Channel Logout draft implementation
- added: registration.success event
- added: allow clients for introspections/revocations only (Resource Servers) with no authorization flow access
- added: draft / experimental features now warn upon provider init
- fix: introspection follows normal/pairwise subject claim of the token's client
- fix: added client_id_issued_at client property upon registration
- https://github.com/panva/node-oidc-provider/compare/v0.8.1...v0.9.0

@@ -5,0 +14,0 @@ - added: (no)cache headers according to specs

'use strict';
const _ = require('lodash');
module.exports = function discoveryAction(provider) {

@@ -63,6 +65,12 @@ const config = provider.configuration();

this.body.end_session_endpoint = this.oidc.urlFor('end_session');
if (config.features.backchannelLogout) {
this.body.backchannel_logout_supported = true;
}
}
_.defaults(this.body, config.discovery);
yield next;
};
};

@@ -79,2 +79,24 @@ 'use strict';

if (provider.configuration('features.backchannelLogout')) {
try {
const Client = provider.get('Client');
const cookieValue = this.cookies.get('_session_states', {
signed: provider.configuration('cookies.long.signed'),
});
const clientIds = Object.keys(JSON.parse(cookieValue));
const logouts = clientIds.map(visitedClientId => Client.find(visitedClientId)
.then(visitedClient => {
if (visitedClient && visitedClient.backchannelLogoutUri) {
return visitedClient.backchannelLogout();
}
return undefined;
})
);
yield logouts;
} catch (err) {}
}
yield this.oidc.session.destroy();

@@ -81,0 +103,0 @@ this.cookies.set('_session_states', null);

8

lib/actions/introspection.js

@@ -75,2 +75,9 @@ 'use strict';

if (token.clientId !== this.oidc.client.clientId) {
this.body.sub = Claims.sub(token.accountId,
(yield provider.get('Client').find(token.clientId)).sectorIdentifier);
} else {
this.body.sub = Claims.sub(token.accountId, this.oidc.client.sectorIdentifier);
}
Object.assign(this.body, {

@@ -84,3 +91,2 @@ active: token.isValid,

scope: token.scope,
sub: Claims.sub(token.accountId, this.oidc.client.sectorIdentifier),
});

@@ -87,0 +93,0 @@

@@ -26,7 +26,28 @@ 'use strict';

client_id: uuid.v4(),
client_secret: crypto.randomBytes(48).toString('base64'),
client_secret_expires_at: 0,
client_id_issued_at: Date.now() / 1000 | 0,
registration_access_token: crypto.randomBytes(48).toString('base64'),
});
let clientSecretRequired = properties.token_endpoint_auth_method === undefined ||
['private_key_jwt', 'none'].indexOf(properties.token_endpoint_auth_method) === -1;
clientSecretRequired = clientSecretRequired || [
'id_token_signed_response_alg',
'request_object_signing_alg',
'token_endpoint_auth_signing_alg',
'userinfo_signed_response_alg',
].some(prop => {
if (properties[prop] !== undefined && String(properties[prop]).startsWith('HS')) {
return true;
}
return false;
});
if (clientSecretRequired) {
Object.assign(properties, {
client_secret: crypto.randomBytes(48).toString('base64'),
client_secret_expires_at: 0,
});
}
const client = yield provider.addClient(properties);

@@ -44,2 +65,4 @@ const dumpable = _.mapKeys(client, (value, key) => _.snakeCase(key));

this.status = 201;
provider.emit('registration.success', client, this);
},

@@ -46,0 +69,0 @@ ]),

@@ -46,2 +46,3 @@ 'use strict';

result() {
const available = this.available;
const include = _.chain(this.filter)

@@ -75,4 +76,16 @@ .pickBy((value) => {

const claims = _.chain(this.available).pick(include).value();
/* eslint-disable no-underscore-dangle */
const claims = _.pick(available, include);
if (available._claim_names && available._claim_sources) {
claims._claim_names = _.pick(available._claim_names, include);
claims._claim_sources = _.pick(available._claim_sources, _.values(claims._claim_names));
if (_.isEmpty(claims._claim_names)) {
delete claims._claim_names;
delete claims._claim_sources;
}
}
/* eslint-enable no-underscore-dangle */
if (this.sector && claims.sub) {

@@ -79,0 +92,0 @@ claims.sub = this.constructor.sub(claims.sub, this.sector);

@@ -10,3 +10,5 @@ 'use strict';

'application_type',
'backchannel_logout_uri',
'client_id',
'client_id_issued_at',
'client_name',

@@ -56,3 +58,3 @@ 'client_secret',

'client_id',
'client_secret',
// 'client_secret', => validated elsewhere and only needed somewhen
'redirect_uris',

@@ -77,2 +79,3 @@ ];

'application_type',
'backchannel_logout_uri',
'client_id',

@@ -124,2 +127,3 @@ 'client_name',

'sector_identifier_uri',
'backchannel_logout_uri',

@@ -205,11 +209,16 @@ // in arrays

if (_.includes(rts, 'code')) {
if (this.grant_types.indexOf('authorization_code') === -1) {
if (this.token_endpoint_auth_method === 'none') {
if (_.includes(this.grant_types, 'authorization_code')) {
throw new errors.InvalidClientMetadata(
'grant_types must contain authorization_code when code is amongst response_types');
'grant_types must not use token endpoint when token_endpoint_auth_method is none');
}
}
if (_.includes(rts, 'code') && !_.includes(this.grant_types, 'authorization_code')) {
throw new errors.InvalidClientMetadata(
'grant_types must contain authorization_code when code is amongst response_types');
}
if (_.includes(rts, 'token') || _.includes(rts, 'id_token')) {
if (this.grant_types.indexOf('implicit') === -1) {
if (!_.includes(this.grant_types, 'implicit')) {
throw new errors.InvalidClientMetadata(

@@ -231,2 +240,9 @@ 'grant_types must contain implicit when id_token or token are amongst response_types');

const validateSecretPresence = validateSecretLength ||
['private_key_jwt', 'none'].indexOf(this.token_endpoint_auth_method) === -1;
if (validateSecretPresence && !this.client_secret) {
throw new errors.InvalidClientMetadata('client_secret is mandatory property');
}
if (validateSecretLength) {

@@ -297,3 +313,4 @@ if (this.client_secret.length < validateSecretLength) {

throw new errors.InvalidClientMetadata(
isAry ? `${prop} must only contain strings` : `${prop} must be a string`);
isAry ? `${prop} must only contain strings` :
`${prop} must be a non-empty string if provided`);
}

@@ -331,2 +348,5 @@ });

lengths() {
if (LENGTH.every(prop => this[prop] && this[prop].length === 0)) {
return true;
}
LENGTH.forEach((prop) => {

@@ -333,0 +353,0 @@ if (this[prop] !== undefined && !this[prop].length) {

@@ -6,2 +6,15 @@ 'use strict';

const STABLE_FLAGS = [
'claimsParameter',
'clientCredentials',
'discovery',
'encryption',
'introspection',
'refreshToken',
'registration',
'request',
'requestUri',
'revocation',
];
class Configuration {

@@ -24,2 +37,17 @@ constructor(config) {

if (this.features.backchannelLogout && !this.features.sessionManagement) {
throw new Error('backchannelLogout is only available in conjuction with sessionManagement');
}
/* eslint-disable no-restricted-syntax, no-console */
if (process.env.NODE_ENV !== 'test') {
for (const flag in this.features) {
if (this.features[flag] && STABLE_FLAGS.indexOf(flag) === -1) {
console.warn(`WARNING: a draft/experimental feature (${flag}) enabled, future updates to \
this feature will be released as MINOR releases`);
}
}
}
/* eslint-enable */
if (!this.adapter) this.adapter = MemoryAdapter;

@@ -26,0 +54,0 @@ if (!this.findById) {

@@ -25,3 +25,13 @@ 'use strict';

},
discovery: {
claim_types_supported: ['normal'],
claims_locales_supported: undefined,
display_values_supported: undefined,
op_policy_uri: undefined,
op_tos_uri: undefined,
service_documentation: undefined,
ui_locales_supported: undefined,
},
features: {
backchannelLogout: false,
claimsParameter: false,

@@ -43,2 +53,3 @@ clientCredentials: false,

jwks_uri: 1500,
backchannel_logout_uri: 1500,
},

@@ -45,0 +56,0 @@ interactionUrl: ctx => `/interaction/${ctx.oidc.uuid}`,

@@ -10,2 +10,3 @@ /* eslint-disable newline-per-chained-call */

const got = require('got');
const uuid = require('uuid').v4;

@@ -15,2 +16,3 @@ const errors = require('../helpers/errors');

const NOOP = () => {};
const KEY_ATTRIBUTES = ['crv', 'e', 'kid', 'kty', 'n', 'use', 'x', 'y'];

@@ -26,2 +28,3 @@ const KEY_TYPES = ['RSA', 'EC'];

const cache = new Map();
const IdToken = provider.get('IdToken');

@@ -159,7 +162,9 @@ function schemaValidate(client, metadata) {

return Promise.all(promises).then(() => {
client.keystore.add({
k: base64url(new Buffer(client.clientSecret)),
kid: 'clientSecret',
kty: 'oct',
});
if (client.clientSecret !== undefined) {
client.keystore.add({
k: base64url(new Buffer(client.clientSecret)),
kid: 'clientSecret',
kty: 'oct',
});
}
})

@@ -184,2 +189,18 @@ .then(() => client);

backchannelLogout(sub) {
const logoutToken = new IdToken({ sub }, this.sectorIdentifier);
logoutToken.mask = { sub: null };
logoutToken.set('logout_only', true);
logoutToken.set('jti', uuid());
return logoutToken.sign(this, { expiresIn: 120 })
.then(token => got.post(this.backchannelLogoutUri, {
headers: { 'User-Agent': provider.userAgent() },
timeout: provider.configuration('timeouts.backchannel_logout_uri'),
retries: 0,
followRedirect: false,
body: { logout_token: token },
}).then(NOOP).catch(NOOP));
}
responseTypeAllowed(type) {

@@ -186,0 +207,0 @@ return this.responseTypes.indexOf(type) !== -1;

@@ -43,2 +43,3 @@ 'use strict';

opts.expiresAt = 'expiresAt' in opts ? opts.expiresAt : null;
opts.expiresIn = 'expiresIn' in opts ? opts.expiresIn : null;

@@ -49,2 +50,4 @@ let expiresIn;

expiresIn = opts.expiresAt - (Date.now() / 1000 | 0);
} else if (opts.expiresIn) {
expiresIn = opts.expiresIn;
}

@@ -51,0 +54,0 @@

2

package.json

@@ -60,3 +60,3 @@ {

},
"version": "0.9.0",
"version": "0.10.0",
"files": [

@@ -63,0 +63,0 @@ "lib"

@@ -28,2 +28,3 @@ # oidc-provider

* [Custom Grant Types](#custom-grant-types)
* [Custom Discovery Properties](#custom-discovery-properties)
* [Events](#events)

@@ -50,4 +51,5 @@ * [Certification](#certification)

- Claims
- Standard-defined Claims
- Custom Claims
- Normal Claims
- Aggregated Claims
- Distributed Claims
- UserInfo Endpoint including

@@ -71,3 +73,2 @@ - Signing (Asymmetric and Symmetric Signatures)

- [OpenID Connect Dynamic Client Registration 1.0 incorporating errata set 1][feature-registration]
- [OpenID Connect Session Management 1.0 - draft26][feature-session-management]
- [OAuth 2.0 Form Post Response mode][feature-form-post]

@@ -77,2 +78,8 @@ - [RFC7009 - OAuth 2.0 Token revocation][feature-revocation]

The following drafts/experimental specifications are implemented by oidc-provider.
- [OpenID Connect Session Management 1.0 - draft 26][feature-session-management]
- [OpenID Connect Back-Channel Logout 1.0 - draft 02][feature-backchannel-logout]
Updates to drafts and experimental specifications are released as MINOR versions.
## Get started

@@ -183,8 +190,13 @@ To run and experiment with an example server, clone the oidc-provider repo and install the dependencies:

Enables the use of Introspection endpoint as described in [RFC7662][feature-introspection] for
tokens of type AccessToken, ClientCredentials and RefreshToken. When enabled
token_introspection_endpoint property of the discovery endpoint is `true`, otherwise the property
tokens of type AccessToken, ClientCredentials and RefreshToken. When enabled the
token_introspection_endpoint property of the discovery endpoint is published, otherwise the property
is not sent. The use of this endpoint is covered by the same authz mechanism as the regular token
endpoint.
This feature is a recommended way for Resource Servers to validate presented Bearer tokens, since
the token endpoint access must be authorized it is recommended to setup a client for the RS to
use. This client should be unusable for standard authorization flow, to set up such a client provide
grant_types, response_types and redirect_uris as empty arrays.
**Revocation endpoint**

@@ -195,4 +207,4 @@ ```js

Enables the use of Revocation endpoint as described in [RFC7009][feature-revocation] for tokens of
type AccessToken, ClientCredentials and RefreshToken. When enabled
token_revocation_endpoint property of the discovery endpoint is `true`, otherwise the property
type AccessToken, ClientCredentials and RefreshToken. When enabled the
token_revocation_endpoint property of the discovery endpoint is published, otherwise the property
is not sent. The use of this endpoint is covered by the same authz mechanism as the regular token

@@ -206,5 +218,12 @@ endpoint.

```
Enables features described in [Session Management 1.0 - draft26][feature-session-management].
Enables features described in [Session Management 1.0 - draft 26][feature-session-management].
**Back-Channel Logout features**
```js
const configuration = { features: { sessionManagement: true, backchannelLogout: Boolean[false] } };
```
Enables features described in [Back-Channel Logout 1.0 - draft 02][feature-backchannel-logout].
**Dynamic registration features**

@@ -281,2 +300,20 @@ ```js

**Aggregated and Distributed claims**
Returning aggregated and distributed claims is as easy as having your Account#claims method return
the two necessary members `_claim_sources` and `_claim_names` with the
[expected][feature-aggregated-distributed-claims] properties. oidc-provider will include only the
sources for claims that are part of the request scope, omitting the ones that the RP did not request
and leaving out the entire `_claim_sources` and `_claim_sources` if they bear no requested claims.
Note: to make sure the RPs can expect these claims you should configure your discovery to return
the respective claim types via the `claim_types_supported` property.
```js
const oidc = new Provider('http://localhost:3000', {
discovery: {
claim_types_supported: ['normal', 'aggregated', 'distributed']
}
});
```
### Interaction

@@ -369,2 +406,13 @@ Since oidc-provider comes with no views and interaction handlers what so ever it's up to you to fill

### Custom Discovery Properties
You can extend the returned discovery properties beyond the defaults
```js
const oidc = new Provider('http://localhost:3000', {
discovery: {
service_documentation: 'http://server.example.com/connect/service_documentation.html',
ui_locales_supported: ['en-US', 'en-GB', 'en-CA', 'fr-FR', 'fr-CA']
}
});
```
## Events

@@ -414,2 +462,6 @@ The oidc-provider instance is an event emitter, `this` is always the instance. In events where `ctx`(koa

**registration.success**
oidc.on(`'registration.success', function (client, ctx) { }`)
Emitted with every successful client registration request.
**registration.error**

@@ -478,3 +530,3 @@ oidc.on(`'registration.error', function (error, ctx) { }`)

[feature-registration]: http://openid.net/specs/openid-connect-registration-1_0.html
[feature-session-management]: http://openid.net/specs/openid-connect-session-1_0.html
[feature-session-management]: http://openid.net/specs/openid-connect-session-1_0-26.html
[feature-form-post]: http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html

@@ -492,1 +544,3 @@ [feature-revocation]: https://tools.ietf.org/html/rfc7009

[password-grant]: https://tools.ietf.org/html/rfc6749#section-4.3
[feature-aggregated-distributed-claims]: http://openid.net/specs/openid-connect-core-1_0.html#AggregatedDistributedClaims
[feature-backchannel-logout]: http://openid.net/specs/openid-connect-backchannel-1_0-02.html
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc