Socket
Socket
Sign inDemoInstall

openid-client

Package Overview
Dependencies
Maintainers
1
Versions
181
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

openid-client - npm Package Compare versions

Comparing version 0.0.1 to 0.1.0

lib/got_error_handler.js

271

lib/base_client.js

@@ -6,20 +6,23 @@ 'use strict';

const jose = require('node-jose');
const uuid = require('node-uuid').v4;
const gotErrorHandler = require('./got_error_handler');
const base64url = require('base64url');
const url = require('url');
const { merge, defaults, pick, forEach, get } = require('lodash');
const _ = require('lodash');
const TokenSet = require('./token_set');
const tokenHash = require('./token_hash');
const OpenIdConnectError = require('./open_id_connect_error');
const {
USER_AGENT,
CLIENT_METADATA,
CLIENT_DEFAULTS,
} = require('./consts');
const CALLBACK_PROPERTIES = require('./consts').CALLBACK_PROPERTIES;
const CLIENT_METADATA = require('./consts').CLIENT_METADATA;
const CLIENT_DEFAULTS = require('./consts').CLIENT_DEFAULTS;
const debug = require('debug')('oidc:client');
const got = require('got');
const map = new WeakMap();
function bearer(token) {
return `Bearer ${token}`;
}
function instance(ctx) {

@@ -31,6 +34,16 @@ if (!map.has(ctx)) map.set(ctx, {});

class BaseClient {
constructor(metadata) {
forEach(defaults(pick(metadata, CLIENT_METADATA), CLIENT_DEFAULTS), (value, key) => {
constructor(metadata, keystore) {
_.forEach(_.defaults(_.pick(metadata, CLIENT_METADATA), CLIENT_DEFAULTS), (value, key) => {
instance(this)[key] = value;
});
if (keystore !== undefined) {
assert.ok(jose.JWK.isKeyStore(keystore), 'keystore must be an instance of jose.JWK.KeyStore');
instance(this).keystore = keystore;
}
if (this.token_endpoint_auth_method.endsWith('_jwt')) {
assert.ok(this.issuer.token_endpoint_auth_signing_alg_values_supported,
'token_endpoint_auth_signing_alg_values_supported must be provided on the issuer');
}
}

@@ -41,3 +54,3 @@

const query = defaults(params, {
const query = _.defaults(params, {
client_id: this.client_id,

@@ -52,3 +65,3 @@ scope: 'openid',

return url.format(defaults({
return url.format(_.defaults({
search: null,

@@ -59,7 +72,14 @@ query,

authorizationCallback(redirectUri, params) {
authorizationCallback(redirectUri, parameters, checks) {
const params = _.pick(parameters, CALLBACK_PROPERTIES);
const toCheck = checks || {};
if (params.error) {
return Promise.reject(pick(params, 'error', 'error_description', 'state'));
return Promise.reject(new OpenIdConnectError(params));
}
if (toCheck.state !== parameters.state) {
return Promise.reject(new Error('state mismatch'));
}
return this.grant({

@@ -69,6 +89,6 @@ grant_type: 'authorization_code',

redirect_uri: redirectUri,
}).then(tokenset => this.validateIdToken(tokenset));
}).then(tokenset => this.validateIdToken(tokenset, toCheck.nonce));
}
validateIdToken(token) {
validateIdToken(token, nonce) {
let idToken = token;

@@ -84,2 +104,4 @@

idToken = String(idToken);
const now = Math.ceil(Date.now() / 1000);

@@ -111,2 +133,6 @@ const parts = idToken.split('.');

if (payloadObject.nonce || nonce !== undefined) {
assert.equal(payloadObject.nonce, nonce, 'nonce mismatch');
}
assert.ok(typeof payloadObject.exp === 'number', 'exp is not a number');

@@ -133,7 +159,7 @@ assert.ok(now < payloadObject.exp, 'id_token expired');

if (payloadObject.c_hash && token.code) {
assert.equal(payloadObject.at_hash, tokenHash(token.code, headerObject.alg),
assert.equal(payloadObject.c_hash, tokenHash(token.code, headerObject.alg),
'c_hash mismatch');
}
return this.issuer.key(headerObject)
return (headerObject.alg.startsWith('HS') ? this.joseSecret() : this.issuer.key(headerObject))
.then(key => jose.JWS.createVerify(key).verify(idToken))

@@ -143,8 +169,2 @@ .then(() => token);

// implicitCallback(params, verify) {
// if (params.error) {
// return Promise.reject(pick(params, 'error', 'error_description', 'state'));
// }
// }
refresh(refreshToken) {

@@ -166,4 +186,8 @@ let token = refreshToken;

userinfo(accessToken) {
userinfo(accessToken, options) {
let token = accessToken;
const opts = _.merge({
verb: 'get',
via: 'header',
}, options);

@@ -177,13 +201,34 @@ if (token instanceof TokenSet) {

return got.get(this.issuer.userinfo_endpoint, {
retries: 0,
followRedirect: false,
headers: {
'User-Agent': USER_AGENT,
Authorization: `Bearer ${token}`,
},
}).then(response => JSON.parse(response.body), err => {
debug('userinfo request failed (%s > %s)',
err.name, err.message, get(err, 'response.body'));
throw err;
const verb = String(opts.verb).toLowerCase();
let httpOptions;
switch (opts.via) {
case 'query':
assert.equal(verb, 'get', 'providers should only parse query strings for GET requests');
httpOptions = { query: { access_token: token } };
break;
case 'body':
assert.equal(verb, 'post', 'can only send body on POST');
httpOptions = { body: { access_token: token } };
break;
default:
httpOptions = { headers: { Authorization: bearer(token) } };
}
return got[verb](this.issuer.userinfo_endpoint, this.issuer.httpOptions(
httpOptions
)).then(response => JSON.parse(response.body), gotErrorHandler);
}
joseSecret() {
if (instance(this).jose_secret) {
return Promise.resolve(instance(this).jose_secret);
}
return jose.JWK.asKey({
k: base64url(new Buffer(this.client_secret)),
kty: 'oct',
}).then(key => {
instance(this).jose_secret = key;
return key;
});

@@ -193,22 +238,68 @@ }

grant(body) {
const auth = this.grantAuth();
debug('client %s %s grant request started',
this.client_id, body.grant_type);
return this.authenticatedPost(this.issuer.token_endpoint, { body },
response => new TokenSet(JSON.parse(response.body)));
}
return got.post(this.issuer.token_endpoint, merge({
body,
retries: 0,
followRedirect: false,
headers: {
'User-Agent': USER_AGENT,
},
}, auth)).then(response => new TokenSet(JSON.parse(response.body)), err => {
debug('client %s grant request failed (%s > %s)',
this.client_id, err.name, err.message, get(err, 'response.body'));
throw err;
});
revoke(token) {
assert.ok(this.issuer.revocation_endpoint || this.issuer.token_revocation_endpoint,
'issuer must be configured with revocation endpoint');
const endpoint = this.issuer.revocation_endpoint || this.issuer.token_revocation_endpoint;
return this.authenticatedPost(endpoint, { body: { token } },
response => JSON.parse(response.body));
}
introspect(token) {
assert.ok(this.issuer.introspection_endpoint || this.issuer.token_introspection_endpoint,
'issuer must be configured with introspection endpoint');
const endpoint = this.issuer.introspection_endpoint || this.issuer.token_introspection_endpoint;
return this.authenticatedPost(endpoint, { body: { token } },
response => JSON.parse(response.body));
}
authenticatedPost(endpoint, httpOptions, success) {
return Promise.resolve(this.grantAuth())
.then(auth => got.post(endpoint, this.issuer.httpOptions(_.merge(httpOptions, auth)))
.then(success, gotErrorHandler));
}
createSign() {
let alg = this.token_endpoint_auth_signing_alg;
switch (this.token_endpoint_auth_method) {
case 'client_secret_jwt':
return this.joseSecret().then(key => {
if (!alg) {
alg = _.find(this.issuer.token_endpoint_auth_signing_alg_values_supported,
(signAlg) => key.algorithms('sign').indexOf(signAlg) !== -1);
}
return jose.JWS.createSign({
fields: { alg, typ: 'JWT' },
format: 'compact',
}, { key, reference: false });
});
case 'private_key_jwt': {
if (!alg) {
const algz = _.uniq(_.flatten(_.map(this.keystore.all(), key => key.algorithms('sign'))));
alg = _.find(this.issuer.token_endpoint_auth_signing_alg_values_supported,
(signAlg) => algz.indexOf(signAlg) !== -1);
}
const key = this.keystore.get({ alg });
assert.ok(key, 'no valid key found');
return Promise.resolve(jose.JWS.createSign({
fields: { alg, typ: 'JWT' },
format: 'compact',
}, { key, reference: true }));
}
/* istanbul ignore next */
default:
throw new Error('createSign only works for _jwt token auth methods');
}
}
grantAuth() {
switch (this.token_endpoint_auth_method) {
case 'none' :
throw new Error('client not supposed to use grant authz');
case 'client_secret_post':

@@ -221,9 +312,22 @@ return {

};
case 'private_key_jwt' :
case 'client_secret_jwt' : {
const now = Math.floor(Date.now() / 1000);
return this.createSign().then(sign => sign.update(JSON.stringify({
iat: now,
exp: now + 60,
jti: uuid(),
iss: this.client_id,
sub: this.client_id,
aud: this.issuer.token_endpoint,
})).final().then(client_assertion => { // eslint-disable-line camelcase, arrow-body-style
return { body: {
client_assertion,
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
} };
}));
}
default: {
const value = new Buffer(`${this.client_id}:${this.client_secret}`).toString('base64');
return {
headers: {
Authorization: `Basic ${value}`,
},
};
return { headers: { Authorization: `Basic ${value}` } };
}

@@ -237,42 +341,35 @@ }

static register(body) {
debug('attempting client registration');
static register(body, keystore) {
assert.ok(this.issuer.registration_endpoint, 'issuer does not support dynamic registration');
return got.post(this.issuer.registration_endpoint, {
if (keystore !== undefined && !(body.jwks || body.jwks_uri)) {
assert.ok(jose.JWK.isKeyStore(keystore), 'keystore must be an instance of jose.JWK.KeyStore');
assert.ok(keystore.all().every(key => {
if (key.kty === 'RSA' || key.kty === 'EC') {
try { key.toPEM(true); } catch (err) { return false; }
return true;
}
return false;
}), 'keystore must only contain private EC or RSA keys');
body.jwks = keystore.toJSON();
}
return got.post(this.issuer.registration_endpoint, this.issuer.httpOptions({
body: JSON.stringify(body),
retries: 0,
followRedirect: false,
headers: {
'Content-Type': 'application/json',
'User-Agent': USER_AGENT,
},
}).then(response => new this(JSON.parse(response.body)), err => {
debug('registration failed (%s > %s)',
err.name, err.message, get(err, 'response.body'));
throw err;
});
headers: { 'Content-Type': 'application/json' },
})).then(response => new this(JSON.parse(response.body), keystore), gotErrorHandler);
}
get keystore() {
return instance(this).keystore;
}
get metadata() {
return pick(this, CLIENT_METADATA);
return _.omitBy(_.pick(this, CLIENT_METADATA), _.isUndefined);
}
static fromUri(uri, token) {
debug('fetching client from %s',
uri);
return got.get(uri, {
retries: 0,
followRedirect: false,
headers: {
Authorization: `Bearer ${token}`,
'User-Agent': USER_AGENT,
},
}).then(response => new this(JSON.parse(response.body)), err => {
debug('%s request failed (%s > %s)',
uri, err.name, err.message, get(err, 'response.body'));
throw err;
});
return got.get(uri, this.issuer.httpOptions({
headers: { Authorization: bearer(token) },
})).then(response => new this(JSON.parse(response.body)), gotErrorHandler);
}

@@ -279,0 +376,0 @@ }

@@ -10,5 +10,9 @@ const pkg = require('../package.json');

'authorization_endpoint',
'check_session_iframe',
'claims_parameter_supported',
'claims_supported',
'end_session_endpoint',
'grant_types_supported',
'id_token_encryption_alg_values_supported',
'id_token_encryption_enc_values_supported',
'id_token_signing_alg_values_supported',

@@ -18,5 +22,8 @@ 'issuer',

'registration_endpoint',
'request_object_encryption_alg_values_supported',
'request_object_encryption_enc_values_supported',
'request_object_signing_alg_values_supported',
'request_parameter_supported',
'request_uri_parameter_supported',
'require_request_uri_registration',
'response_modes_supported',

@@ -30,13 +37,9 @@ 'response_types_supported',

'token_introspection_endpoint',
'introspection_endpoint',
'token_revocation_endpoint',
'revocation_endpoint',
'userinfo_encryption_alg_values_supported',
'userinfo_encryption_enc_values_supported',
'userinfo_endpoint',
'userinfo_signing_alg_values_supported',
'id_token_encryption_alg_values_supported',
'id_token_encryption_enc_values_supported',
'userinfo_encryption_alg_values_supported',
'userinfo_encryption_enc_values_supported',
'request_object_encryption_alg_values_supported',
'request_object_encryption_enc_values_supported',
'check_session_iframe',
'end_session_endpoint',
];

@@ -84,24 +87,36 @@

const ISSUER_DEFAULTS = {
response_modes_supported: ['query', 'fragment'],
claims_parameter_supported: false,
grant_types_supported: ['authorization_code', 'implicit'],
token_endpoint_auth_methods_supported: ['client_secret_basic'],
claims_parameter_supported: false,
request_parameter_supported: false,
request_uri_parameter_supported: true,
require_request_uri_registration: false,
response_modes_supported: ['query', 'fragment'],
token_endpoint_auth_methods_supported: ['client_secret_basic'],
};
const CLIENT_DEFAULTS = {
response_types: ['code'],
application_type: ['web'],
grant_types: ['authorization_code'],
application_type: ['web'],
id_token_signed_response_alg: 'RS256',
response_types: ['code'],
token_endpoint_auth_method: 'client_secret_basic',
};
module.exports.WELL_KNOWN = WELL_KNOWN;
const CALLBACK_PROPERTIES = [
'access_token',
'code',
'error',
'error_description',
'expires_in',
'id_token',
'state',
'token_type',
];
module.exports.CALLBACK_PROPERTIES = CALLBACK_PROPERTIES;
module.exports.CLIENT_DEFAULTS = CLIENT_DEFAULTS;
module.exports.CLIENT_METADATA = CLIENT_METADATA;
module.exports.ISSUER_DEFAULTS = ISSUER_DEFAULTS;
module.exports.ISSUER_METADATA = ISSUER_METADATA;
module.exports.CLIENT_DEFAULTS = CLIENT_DEFAULTS;
module.exports.CLIENT_METADATA = CLIENT_METADATA;
module.exports.USER_AGENT = USER_AGENT;
module.exports.WELL_KNOWN = WELL_KNOWN;

@@ -5,4 +5,2 @@ 'use strict';

module.exports = {
Issuer,
};
module.exports = { Issuer };

@@ -6,18 +6,26 @@ 'use strict';

const util = require('util');
const { get, defaults, pick, forEach } = require('lodash');
const _ = require('lodash');
const {
USER_AGENT,
WELL_KNOWN,
ISSUER_METADATA,
ISSUER_DEFAULTS,
} = require('./consts');
const gotErrorHandler = require('./got_error_handler');
const USER_AGENT = require('./consts').USER_AGENT;
const WELL_KNOWN = require('./consts').WELL_KNOWN;
const ISSUER_METADATA = require('./consts').ISSUER_METADATA;
const ISSUER_DEFAULTS = require('./consts').ISSUER_DEFAULTS;
const BaseClient = require('./base_client');
const debug = require('debug')('oidc:issuer');
const got = require('got');
const map = new WeakMap();
const DEFAULT_HTTP_OPTIONS = {
followRedirect: false,
headers: { 'User-Agent': USER_AGENT },
retries: 0,
timeout: 1500,
};
Object.freeze(DEFAULT_HTTP_OPTIONS);
let defaultHttpOptions = _.clone(DEFAULT_HTTP_OPTIONS);
function instance(ctx) {

@@ -30,3 +38,3 @@ if (!map.has(ctx)) map.set(ctx, {});

constructor(metadata) {
forEach(defaults(pick(metadata, ISSUER_METADATA), ISSUER_DEFAULTS), (value, key) => {
_.forEach(_.defaults(_.pick(metadata, ISSUER_METADATA), ISSUER_DEFAULTS), (value, key) => {
instance(this)[key] = value;

@@ -54,21 +62,14 @@ });

keyStore() {
debug('%s request started',
this.jwks_uri);
return got.get(this.jwks_uri)
.then(response => JSON.parse(response.body), err => {
debug('%s request failed (%s > %s)',
this.issuer.jwks_uri, err.name, err.message, get(err, 'response.body'));
throw err;
})
.then(jwks => jose.JWK.asKeyStore(jwks));
keystore() {
return got.get(this.jwks_uri, this.httpOptions())
.then(response => JSON.parse(response.body), gotErrorHandler)
.then(jwks => jose.JWK.asKeyStore(jwks));
}
key(def) {
return this.keyStore().then(store => store.get(def));
return this.keystore().then(store => store.get(def));
}
get metadata() {
return pick(this, ISSUER_METADATA);
return _.omitBy(_.pick(this, ISSUER_METADATA), _.isUndefined);
}

@@ -80,17 +81,22 @@

debug('discovering configuration from %s',
wellKnownUri);
return got.get(wellKnownUri, this.httpOptions())
.then(response => new this(JSON.parse(response.body)), gotErrorHandler);
}
return got.get(wellKnownUri, {
retries: 0,
followRedirect: false,
headers: {
'User-Agent': USER_AGENT,
},
}).then(response => new this(JSON.parse(response.body)), err => {
debug('%s discovery failed (%s > %s)',
wellKnownUri, err.name, err.message, get(err, 'response.body'));
throw err;
});
httpOptions() {
return this.constructor.httpOptions.apply(this.constructor, arguments); // eslint-disable-line prefer-rest-params, max-len
}
static httpOptions(values) {
return _.merge({}, this.defaultHttpOptions, values);
}
static get defaultHttpOptions() {
return defaultHttpOptions;
}
static set defaultHttpOptions(value) {
defaultHttpOptions = _.merge({}, DEFAULT_HTTP_OPTIONS, value);
}
}

@@ -97,0 +103,0 @@

@@ -11,5 +11,7 @@ 'use strict';

switch (size) {
/* istanbul ignore next */
case '512':
hashingAlg = 'sha512';
break;
/* istanbul ignore next */
case '384':

@@ -16,0 +18,0 @@ hashingAlg = 'sha384';

{
"name": "openid-client",
"version": "0.0.1",
"version": "0.1.0",
"description": "OpenID Connect Relying Party (RP, Client) implementation for Node.js",

@@ -13,3 +13,3 @@ "main": "lib/index.js",

"engines": {
"node": ">=6"
"node": ">=4"
},

@@ -48,6 +48,5 @@ "homepage": "https://github.com/panva/node-openid-client",

"base64url": "^1.0.6",
"debug": "^2.2.0",
"create-error-class": "^3.0.2",
"got": "^6.3.0",
"lodash": "^4.13.1",
"lru-cache": "^4.0.1",
"node-jose": "^0.8.0",

@@ -54,0 +53,0 @@ "node-uuid": "^1.4.7"

@@ -17,3 +17,3 @@ # openid-client

### via Discovery
### via Discovery (recommended)
```js

@@ -42,3 +42,3 @@ const Issuer = require('openid-client').Issuer;

### manually
### manually (recommended)
You should provide the following metadata; `client_id, client_secret`. You can also provide

@@ -52,8 +52,8 @@ `id_token_signed_response_alg` (defaults to `RS256`) and `token_endpoint_auth_method` (defaults to

client_secret: 'TQV5U29k1gHibH5bx1layBo0OSAvAbRT3UYW3EWrSYBB5swxjVfWUa1BS8lqzxG/0v9wruMcrGadany3'
}) // => Client
}); // => Client
```
### via Dynamic Registration
Should your provider support Dynamic Registration and/or provided you with a registration client uri
and registration access token you can also have the Client discovered.
### via registration client uri
Should your oidc provider have provided you with a registration client uri and registration access
token you can also have the Client discovered.
```js

@@ -63,3 +63,3 @@ new googleIssuer.Client.fromUri(registration_client_uri, registration_access_token) // => Promise

console.log('Discovered client %s', client);
})
});
```

@@ -93,2 +93,18 @@

### Revoke a token
```js
client.revoke(token) // => Promise
.then(function () {
console.log('revoked token %s', token);
});
```
### Introspect a token
```js
client.introspect(token) // => Promise
.then(function (details) {
console.log('token details %j', details);
});
```
### Fetching userinfo

@@ -102,2 +118,17 @@ ```js

via POST
```js
client.userinfo(accessToken, { verb: 'post' }); // => Promise
```
auth via query
```js
client.userinfo(accessToken, { via: 'query' }); // => Promise
```
auth via body
```js
client.userinfo(accessToken, { verb: 'post', via: 'body' }); // => Promise
```
### Custom token endpoint grants

@@ -118,2 +149,10 @@ Use when the token endpoint also supports client_credentials or password grants;

### Registering new client (via Dynamic Registration)
```js
issuer.Client.register(metadata, [keystore]) // => Promise
.then(function (client) {
console.log('Registered client %s, %j', client, client.metadata);
});
```
[travis-image]: https://img.shields.io/travis/panva/node-openid-client/master.svg?style=flat-square&maxAge=7200

@@ -120,0 +159,0 @@ [travis-url]: https://travis-ci.org/panva/node-openid-client

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