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

openid-client

Package Overview
Dependencies
Maintainers
1
Versions
189
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.6.1 to 0.7.0

lib/webfinger_normalize.js

4

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-openid-client/compare/v0.6.0...v0.7.0
- added: webfinger discovery
- added: callback parameter helper for node's http.IncomingMessage
- tested for lts/argon (4), lts/boron (6) and current stable (7)
- https://github.com/panva/node-openid-client/compare/v0.5.4...v0.6.0

@@ -5,0 +9,0 @@ - added: handling of symmetrically encrypted responses (A...GCMKW, A...KW, PBES2-HS...+A...KW)

110

lib/base_client.js

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

const assert = require('assert');
const http = require('http');
const crypto = require('crypto');
const querystring = require('querystring');
const jose = require('node-jose');

@@ -54,8 +56,17 @@ const uuid = require('node-uuid').v4;

function getFromJWT(jwt, position, claim) {
const parsed = JSON.parse(base64url.decode(jwt.split('.')[position]));
return typeof claim === 'undefined' ? parsed : parsed[claim];
}
function getSub(jwt) {
return getFromJWT(jwt, 1, 'sub');
}
function getIss(jwt) {
return JSON.parse(base64url.decode(jwt.split('.')[1])).iss;
return getFromJWT(jwt, 1, 'iss');
}
function getHeader(jwt) {
return JSON.parse(base64url.decode(jwt.split('.')[0]));
return getFromJWT(jwt, 0);
}

@@ -71,3 +82,3 @@

function authorizationParams(params) {
assert.ok(typeof params === 'object', 'you must provide an object');
assert.equal(typeof params, 'object', 'you must provide an object');

@@ -80,2 +91,5 @@ const authParams = _.defaults(params, {

assert(authParams.response_type === 'code' || authParams.nonce,
'nonce MUST be provided for implicit and hybrid flows');
if (typeof authParams.claims === 'object') {

@@ -95,3 +109,3 @@ authParams.claims = JSON.stringify(authParams.claims);

if (keystore !== undefined) {
assert.ok(jose.JWK.isKeyStore(keystore), 'keystore must be an instance of jose.JWK.KeyStore');
assert(jose.JWK.isKeyStore(keystore), 'keystore must be an instance of jose.JWK.KeyStore');
instance(this).keystore = keystore;

@@ -101,3 +115,3 @@ }

if (this.token_endpoint_auth_method.endsWith('_jwt')) {
assert.ok(this.issuer.token_endpoint_auth_signing_alg_values_supported,
assert(this.issuer.token_endpoint_auth_signing_alg_values_supported,
'token_endpoint_auth_signing_alg_values_supported must be provided on the issuer');

@@ -131,2 +145,41 @@ }

callbackParams(input) { // eslint-disable-line
const isIncomingMessage = input instanceof http.IncomingMessage;
const isString = typeof input === 'string';
assert(isString || isIncomingMessage, '#callbackParams only accepts string urls or http.IncomingMessage');
let uri;
if (isIncomingMessage) {
const msg = input;
switch (msg.method) {
case 'GET':
uri = msg.url;
break;
case 'POST':
assert(msg.body, 'incoming message body missing, include a body parser prior to this call');
switch (typeof msg.body) {
case 'object':
case 'string':
if (Buffer.isBuffer(msg.body)) {
return querystring.parse(msg.body.toString('utf-8'));
} else if (typeof msg.body === 'string') {
return querystring.parse(msg.body);
}
return msg.body;
default:
throw new Error('invalid IncomingMessage body object');
}
default:
throw new Error('invalid IncomingMessage method');
}
} else {
uri = input;
}
return url.parse(uri, true).query;
}
authorizationCallback(redirectUri, parameters, checks) {

@@ -264,8 +317,8 @@ const params = _.pick(parameters, CALLBACK_PROPERTIES);

assert.ok(typeof payloadObject.iat === 'number', 'iat is not a number');
assert.ok(payloadObject.iat <= now, 'id_token issued in the future');
assert.equal(typeof payloadObject.iat, 'number', 'iat is not a number');
assert(payloadObject.iat <= now, 'id_token issued in the future');
if (payloadObject.nbf !== undefined) {
assert.ok(typeof payloadObject.nbf === 'number', 'nbf is not a number');
assert.ok(payloadObject.nbf <= now, 'id_token not active yet');
assert.equal(typeof payloadObject.nbf, 'number', 'nbf is not a number');
assert(payloadObject.nbf <= now, 'id_token not active yet');
}

@@ -277,4 +330,4 @@

assert.ok(typeof payloadObject.exp === 'number', 'exp is not a number');
assert.ok(now < payloadObject.exp, 'id_token expired');
assert.equal(typeof payloadObject.exp, 'number', 'exp is not a number');
assert(now < payloadObject.exp, 'id_token expired');

@@ -291,10 +344,10 @@ if (!Array.isArray(payloadObject.aud)) {

assert.ok(payloadObject.aud.indexOf(this.client_id) !== -1, 'aud is missing the client_id');
assert(payloadObject.aud.indexOf(this.client_id) !== -1, 'aud is missing the client_id');
if (isTokenSet && payloadObject.at_hash) {
assert.ok(tokenHash(payloadObject.at_hash, token.access_token), 'at_hash mismatch');
assert(tokenHash(payloadObject.at_hash, token.access_token), 'at_hash mismatch');
}
if (isTokenSet && payloadObject.c_hash) {
assert.ok(tokenHash(payloadObject.c_hash, token.code), 'c_hash mismatch');
assert(tokenHash(payloadObject.c_hash, token.code), 'c_hash mismatch');
}

@@ -307,3 +360,5 @@

return (headerObject.alg.startsWith('HS') ? this.joseSecret() : this.issuer.key(headerObject))
.then(key => jose.JWS.createVerify(key).verify(idToken))
.then(key => jose.JWS.createVerify(key).verify(idToken).catch(() => {
throw new Error('invalid signature');
}))
.then(() => token);

@@ -378,3 +433,10 @@ }

return JSON.parse(response.body);
}, gotErrorHandler);
}, gotErrorHandler)
.then((parsed) => {
if (accessToken.id_token) {
assert.equal(getSub(accessToken.id_token), parsed.sub, 'userinfo sub mismatch');
}
return parsed;
});
}

@@ -420,5 +482,5 @@

revoke(token, hint) {
assert.ok(this.issuer.revocation_endpoint || this.issuer.token_revocation_endpoint,
assert(this.issuer.revocation_endpoint || this.issuer.token_revocation_endpoint,
'issuer must be configured with revocation endpoint');
assert.ok(!hint || typeof hint === 'string', 'hint must be a string');
assert(!hint || typeof hint === 'string', 'hint must be a string');
const endpoint = this.issuer.revocation_endpoint || this.issuer.token_revocation_endpoint;

@@ -433,5 +495,5 @@

introspect(token, hint) {
assert.ok(this.issuer.introspection_endpoint || this.issuer.token_introspection_endpoint,
assert(this.issuer.introspection_endpoint || this.issuer.token_introspection_endpoint,
'issuer must be configured with introspection endpoint');
assert.ok(!hint || typeof hint === 'string', 'hint must be a string');
assert(!hint || typeof hint === 'string', 'hint must be a string');
const endpoint = this.issuer.introspection_endpoint || this.issuer.token_introspection_endpoint;

@@ -521,3 +583,3 @@

const key = instance(this).keystore.get({ alg });
assert.ok(key, 'no valid key found');
assert(key, 'no valid key found');

@@ -575,7 +637,7 @@ return Promise.resolve(jose.JWS.createSign({

static register(body, keystore) {
assert.ok(this.issuer.registration_endpoint, 'issuer does not support dynamic registration');
assert(this.issuer.registration_endpoint, 'issuer does not support dynamic registration');
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) => {
assert(jose.JWK.isKeyStore(keystore), 'keystore must be an instance of jose.JWK.KeyStore');
assert(keystore.all().every((key) => {
if (key.kty === 'RSA' || key.kty === 'EC') {

@@ -582,0 +644,0 @@ try { key.toPEM(true); } catch (err) { return false; }

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

const WELL_KNOWN = '/.well-known/openid-configuration';
const DISCOVERY = '/.well-known/openid-configuration';
const WEBFINGER = '/.well-known/webfinger';
const REL = 'http://openid.net/specs/connect/1.0/issuer';

@@ -14,2 +16,3 @@ const ISSUER_METADATA = [

'claims_supported',
'claim_types_supported',
'end_session_endpoint',

@@ -132,2 +135,4 @@ 'grant_types_supported',

module.exports.USER_AGENT = USER_AGENT;
module.exports.WELL_KNOWN = WELL_KNOWN;
module.exports.DISCOVERY = DISCOVERY;
module.exports.REL = REL;
module.exports.WEBFINGER = WEBFINGER;
'use strict';
const jose = require('node-jose');
const assert = require('assert');
const util = require('util');
const url = require('url');
const _ = require('lodash');

@@ -12,3 +14,5 @@ const LRU = require('lru-cache');

const ISSUER_METADATA = require('./consts').ISSUER_METADATA;
const WELL_KNOWN = require('./consts').WELL_KNOWN;
const DISCOVERY = require('./consts').DISCOVERY;
const WEBFINGER = require('./consts').WEBFINGER;
const REL = require('./consts').REL;

@@ -18,2 +22,3 @@ const gotErrorHandler = require('./got_error_handler');

const registry = require('./issuer_registry');
const webfingerNormalize = require('./webfinger_normalize');

@@ -61,6 +66,2 @@ const privateProps = new WeakMap();

static get registry() {
return registry;
}
inspect() {

@@ -96,6 +97,8 @@ return util.format('Issuer <%s>', this.issuer);

return this.keystore(!freshJwksUri)
.then(store => store.get(def))
.then((key) => {
.then(store => store.all(def))
.then((keys) => {
assert(keys.length, 'no valid key found');
assert.equal(keys.length, 1, 'multiple matching keys, kid must be provided');
lookupCache.set(def, true);
return key;
return keys[0];
});

@@ -108,6 +111,32 @@ }

static webfinger(input) {
const resource = webfingerNormalize(input);
const host = url.parse(resource).host;
const query = { resource, rel: REL };
const opts = { query, followRedirect: true };
return got.get(`https://${host}${WEBFINGER}`, this.httpOptions(opts))
.then(response => JSON.parse(response.body))
.then((body) => {
const foo = _.find(body.links, link => typeof link === 'object' && link.rel === REL && link.href);
assert(foo, 'no issuer found in webfinger');
const expectedIssuer = foo.href;
if (registry.has(expectedIssuer)) return registry.get(expectedIssuer);
return this.discover(expectedIssuer).then((issuer) => {
try {
assert.equal(issuer.issuer, expectedIssuer, 'discovered issuer mismatch');
} catch (err) {
registry.delete(issuer.issuer);
throw err;
}
return issuer;
});
});
}
static discover(uri) {
uri = stripTrailingSlash(uri); // eslint-disable-line no-param-reassign
const isWellKnown = uri.endsWith(WELL_KNOWN);
const wellKnownUri = isWellKnown ? uri : `${uri}${WELL_KNOWN}`;
const isWellKnown = uri.endsWith(DISCOVERY);
const wellKnownUri = isWellKnown ? uri : `${uri}${DISCOVERY}`;

@@ -114,0 +143,0 @@ return got.get(wellKnownUri, this.httpOptions())

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

@@ -36,4 +36,4 @@ "main": "lib/index.js",

"eslint": "^3.0.0",
"eslint-config-airbnb-base": "^8.0.0",
"eslint-plugin-import": "^1.0.0",
"eslint-config-airbnb-base": "^9.0.0",
"eslint-plugin-import": "^2.0.1",
"istanbul": "^0.4.4",

@@ -46,5 +46,6 @@ "koa": "^1.2.0",

"mocha": "^3.0.0",
"nock": "^8.0.0",
"nock": "^9.0.0",
"readable-mock-req": "^0.2.2",
"sinon": "^1.17.4",
"timekeeper": "^0.1.1"
"timekeeper": "^1.0.0"
},

@@ -51,0 +52,0 @@ "dependencies": {

@@ -37,2 +37,3 @@ # openid-client

- Discovery of OpenID Provider (Issuer) Metadata
- Discovery of OpenID Provider (Issuer) Metadata via user provided inputs (see #WebFinger)
- [OpenID Connect Dynamic Client Registration 1.0 incorporating errata set 1][feature-registration]

@@ -139,2 +140,26 @@ - Dynamic Client Registration request

### Handling multiple response modes
When handling multiple response modes with one single pass you can use `#authorizationParams`
to get the params object from the koa/express/node request object or a url string.
(http.IncomingMessage). If form_post is your response_type you need to include a body parser prior.
```js
client.authorizationParams('https://client.example.com/cb?code=code'); // => { code: 'code' };
client.authorizationParams('/cb?code=code'); // => { code: 'code' };
// koa v1.x w/ koa-body
app.use(bodyParser({ patchNode: true }));
app.use(function* (next) {
const params = client.authorizationParams(this.request.req); // => parsed url query, url fragment or body object
// ...
});
// express w/ bodyParser
app.use(bodyParser.urlencoded({ extended: false }));
app.use(function (req, res, next) {
const params = client.authorizationParams(req); // => parsed url query, url fragment or body object
// ...
});
```
### Refreshing a token

@@ -265,6 +290,19 @@ ```js

## WebFinger discovery
```js
Issuer.webfinger(userInput) // => Promise
.then(function (issuer) {
console.log('Discovered issuer %s', issuer);
});
```
Accepts, normalizes, discovers and validates the discovery of User Input using E-Mail, URL, acct,
Hostname and Port syntaxes as described in [Discovery 1.0][feature-discovery].
Uses already discovered (cached) issuers where applicable.
## Configuration
### Changing HTTP request defaults
Setting `defaultHttpOptions` on `Issuer` always merges your passed options with the default. openid-client uses [got][got-library] for http requests with the following default request options
Setting `defaultHttpOptions` on `Issuer` always merges your passed options with the default.
openid-client uses [got][got-library] for http requests with the following default request options

@@ -271,0 +309,0 @@ ```js

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