openid-client
Advanced tools
Comparing version 1.4.0 to 1.5.0
@@ -8,2 +8,3 @@ # openid-client CHANGELOG | ||
<!-- TOC START min:2 max:2 link:true update:true --> | ||
- [Version 1.5.0](#version-150) | ||
- [Version 1.4.0](#version-140) | ||
@@ -19,2 +20,15 @@ - [Version 1.3.0](#version-130) | ||
## Version 1.5.0 | ||
- [DIFF](https://github.com/panva/node-openid-client/compare/v1.4.0...v1.5.0) | ||
- added a passport.js strategy | ||
- added missing max_age, default_max_age related functionality | ||
- authorizationCallback now supports max_age check | ||
- clients with default_max_age use this default value automatically | ||
- when max_age is checked auth_time claim is mandatory and must be a number | ||
- added missing require_auth_time related functionality | ||
- clients with require_auth_time = true have the presence and format of auth_time claim validated | ||
- authorizationUrl and authorizationPost now removes null and undefined values and ensures parameters | ||
are stringified before passed to url.format | ||
- added client.CLOCK_TOLERANCE property, to allow for clock skew (in seconds) | ||
## Version 1.4.0 | ||
@@ -21,0 +35,0 @@ - [DIFF](https://github.com/panva/node-openid-client/compare/v1.3.1...v1.4.0) |
@@ -20,2 +20,3 @@ 'use strict'; | ||
const OpenIdConnectError = require('./open_id_connect_error'); | ||
const now = require('./unix_timestamp'); | ||
@@ -93,7 +94,15 @@ const CALLBACK_PROPERTIES = require('./consts').CALLBACK_PROPERTIES; | ||
const authParams = _.defaults(params, { | ||
const authParams = _.chain(params).defaults({ | ||
client_id: this.client_id, | ||
scope: 'openid', | ||
response_type: 'code', | ||
}); | ||
}).forEach((value, key, object) => { | ||
if (value === null || value === undefined) { | ||
delete object[key]; | ||
} else if (key === 'claims' && typeof value === 'object') { | ||
object[key] = JSON.stringify(value); | ||
} else if (typeof value !== 'string') { | ||
object[key] = String(value); | ||
} | ||
}).value(); | ||
@@ -103,6 +112,2 @@ assert(authParams.response_type === 'code' || authParams.nonce, | ||
if (typeof authParams.claims === 'object') { | ||
authParams.claims = JSON.stringify(authParams.claims); | ||
} | ||
return authParams; | ||
@@ -157,2 +162,4 @@ } | ||
} | ||
this.CLOCK_TOLERANCE = 0; | ||
} | ||
@@ -220,3 +227,3 @@ | ||
return url.parse(uri, true).query; | ||
return _.pick(url.parse(uri, true).query, CALLBACK_PROPERTIES); | ||
} | ||
@@ -228,2 +235,4 @@ | ||
if (this.default_max_age && !toCheck.max_age) toCheck.max_age = this.default_max_age; | ||
if (toCheck.state !== parameters.state) { | ||
@@ -242,3 +251,3 @@ return Promise.reject(new Error('state mismatch')); | ||
.then(tokenset => this.decryptIdToken(tokenset, 'id_token')) | ||
.then(tokenset => this.validateIdToken(tokenset, toCheck.nonce, 'id_token')); | ||
.then(tokenset => this.validateIdToken(tokenset, toCheck.nonce, 'id_token', toCheck.max_age)); | ||
} | ||
@@ -253,3 +262,3 @@ | ||
.then(tokenset => this.decryptIdToken(tokenset, 'id_token')) | ||
.then(tokenset => this.validateIdToken(tokenset, toCheck.nonce, 'id_token')) | ||
.then(tokenset => this.validateIdToken(tokenset, toCheck.nonce, 'id_token', toCheck.max_age)) | ||
.then((tokenset) => { | ||
@@ -319,3 +328,3 @@ if (params.session_state) tokenset.session_state = params.session_state; | ||
validateIdToken(token, nonce, intent) { | ||
validateIdToken(token, nonce, intent, maxAge) { | ||
let idToken = token; | ||
@@ -350,3 +359,3 @@ | ||
const now = Math.ceil(Date.now() / 1000); | ||
const timestamp = now(); | ||
const parts = idToken.split('.'); | ||
@@ -376,3 +385,3 @@ const header = parts[0]; | ||
assert.equal(typeof payloadObject.iat, 'number', 'iat is not a number'); | ||
assert(payloadObject.iat <= now, 'id_token issued in the future'); | ||
assert(payloadObject.iat <= timestamp + this.CLOCK_TOLERANCE, 'id_token issued in the future'); | ||
} | ||
@@ -382,5 +391,14 @@ | ||
assert.equal(typeof payloadObject.nbf, 'number', 'nbf is not a number'); | ||
assert(payloadObject.nbf <= now, 'id_token not active yet'); | ||
assert(payloadObject.nbf <= timestamp + this.CLOCK_TOLERANCE, 'id_token not active yet'); | ||
} | ||
if (maxAge || (maxAge !== null && this.require_auth_time)) { | ||
assert(payloadObject.auth_time, 'missing required JWT property auth_time'); | ||
assert.equal(typeof payloadObject.auth_time, 'number', 'auth_time is not a number'); | ||
} | ||
if (maxAge) { | ||
assert(payloadObject.auth_time + maxAge >= timestamp - this.CLOCK_TOLERANCE, 'too much time has elapsed since the last End-User authentication'); | ||
} | ||
if (nonce !== null && (payloadObject.nonce || nonce !== undefined)) { | ||
@@ -392,3 +410,3 @@ assert.equal(payloadObject.nonce, nonce, 'nonce mismatch'); | ||
assert.equal(typeof payloadObject.exp, 'number', 'exp is not a number'); | ||
assert(now < payloadObject.exp, 'id_token expired'); | ||
assert(timestamp - this.CLOCK_TOLERANCE < payloadObject.exp, 'id_token expired'); | ||
} | ||
@@ -450,3 +468,3 @@ | ||
return this.decryptIdToken(tokenset, 'id_token') | ||
.then(() => this.validateIdToken(tokenset, null, 'id_token')); | ||
.then(() => this.validateIdToken(tokenset, null, 'id_token', null)); | ||
}); | ||
@@ -493,3 +511,3 @@ } | ||
if (!this.userinfo_signed_response_alg) return JSON.parse(jwt); | ||
return this.validateIdToken(jwt, null, 'userinfo') | ||
return this.validateIdToken(jwt, null, 'userinfo', null) | ||
.then(valid => JSON.parse(base64url.decode(valid.split('.')[1]))); | ||
@@ -664,6 +682,6 @@ }); | ||
case 'client_secret_jwt' : { | ||
const now = Math.floor(Date.now() / 1000); | ||
const timestamp = now(); | ||
return this.createSign().then(sign => sign.update(JSON.stringify({ | ||
iat: now, | ||
exp: now + 60, | ||
iat: timestamp, | ||
exp: timestamp + 60, | ||
jti: uuid(), | ||
@@ -670,0 +688,0 @@ iss: this.client_id, |
@@ -5,2 +5,3 @@ 'use strict'; | ||
const Registry = require('./issuer_registry'); | ||
const Strategy = require('./passport_strategy'); | ||
@@ -10,2 +11,3 @@ module.exports = { | ||
Registry, | ||
Strategy, | ||
}; |
{ | ||
"name": "openid-client", | ||
"version": "1.4.0", | ||
"description": "OpenID Connect Relying Party (RP, Client) implementation for Node.js", | ||
"version": "1.5.0", | ||
"description": "OpenID Connect Relying Party (RP, Client) implementation for Node.js servers, supports passportjs", | ||
"main": "lib/index.js", | ||
@@ -28,2 +28,5 @@ "scripts": { | ||
"oauth", | ||
"passport", | ||
"passportjs", | ||
"strategy", | ||
"certified", | ||
@@ -30,0 +33,0 @@ "dynamic", |
@@ -6,3 +6,3 @@ # openid-client | ||
openid-client is a server side [OpenID][openid-connect] Relying Party (RP, Client) implementation for | ||
Node.js | ||
Node.js, supports [passport][passport-url]. | ||
@@ -17,2 +17,3 @@ **Table of Contents** | ||
- [Usage](#usage) | ||
- [Usage with passport](#usage-with-passport) | ||
- [Configuration](#configuration) | ||
@@ -146,3 +147,3 @@ | ||
### Processing callback with state or nonce check | ||
### Processing callback with state, nonce or max_age check | ||
```js | ||
@@ -152,3 +153,3 @@ const state = session.state; | ||
client.authorizationCallback('https://client.example.com/callback', request.query, { state, nonce }) // => Promise | ||
client.authorizationCallback('https://client.example.com/callback', request.query, { state, nonce, max_age }) // => Promise | ||
.then(function (tokenSet) { | ||
@@ -161,3 +162,3 @@ console.log('received and validated tokens %j', tokenSet); | ||
### Handling multiple response modes | ||
When handling multiple response modes with one single pass you can use `#authorizationParams` | ||
When handling multiple response modes with one single pass you can use `#callbackParams` | ||
to get the params object from the koa/express/node request object or a url string. | ||
@@ -167,4 +168,4 @@ (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' }; | ||
client.callbackParams('https://client.example.com/cb?code=code'); // => { code: 'code' }; | ||
client.callbackParams('/cb?code=code'); // => { code: 'code' }; | ||
@@ -174,3 +175,3 @@ // koa v1.x w/ koa-body | ||
app.use(function* (next) { | ||
const params = client.authorizationParams(this.request.req); // => parsed url query, url fragment or body object | ||
const params = client.callbackParams(this.request.req); // => parsed url query, url fragment or body object | ||
// ... | ||
@@ -182,3 +183,3 @@ }); | ||
app.use(function (req, res, next) { | ||
const params = client.authorizationParams(req); // => parsed url query, url fragment or body object | ||
const params = client.callbackParams(req); // => parsed url query, url fragment or body object | ||
// ... | ||
@@ -193,3 +194,3 @@ }); | ||
console.log('refreshed and validated tokens %j', tokenSet); | ||
console.log('refreshed id_token claims %j' tokenSet.claims); | ||
console.log('refreshed id_token claims %j', tokenSet.claims); | ||
}); | ||
@@ -358,4 +359,49 @@ ``` | ||
## Usage with passport | ||
Once you have a Client instance, just pass it to the Strategy. Issuer is best discovered, Client | ||
passed properties manually or via an uri (see [get-started](#get-started)). | ||
Verify function is invoked with a TokenSet, userinfo only when requested, last argument is always | ||
the done function which you invoke once you found your user. | ||
```js | ||
const Strategy = require('openid-client').Strategy; | ||
const params = { | ||
// ... any authorization params | ||
// client_id defaults to client.client_id | ||
// redirect_uri defaults to redirect_uris[0] | ||
// response type defaults to client.response_types[0], then 'code' | ||
// scope defaults to 'openid' | ||
} | ||
passport.use('oidc', new Strategy({ client, [params] }, (tokenset, userinfo, done) => { | ||
console.log('tokenset', tokenset); | ||
console.log('access_token', tokenset.access_token); | ||
console.log('id_token', tokenset.id_token); | ||
console.log('claims', tokenset.claims); | ||
console.log('userinfo', userinfo); | ||
User.findOne({ id: tokenset.claims.sub }, function (err, user) { | ||
if (err) return done(err); | ||
return done(null, user); | ||
}); | ||
})); | ||
// start authentication request | ||
// options [optional], extra authentication parameters | ||
app.get('/auth', passport.authenticate('oidc', [options])); | ||
// authentication callback | ||
app.get('/auth/cb', passport.authenticate('oidc', { successRedirect: '/', failureRedirect: '/login' })); | ||
``` | ||
## Configuration | ||
### Allow for system clock skew | ||
It is possible the RP or OP environment has a system clock skew, to set a clock tolerance (in seconds) | ||
```js | ||
client.CLOCK_TOLERANCE = 5; // to allow a 5 second skew | ||
``` | ||
### Changing HTTP request defaults | ||
@@ -404,1 +450,2 @@ Setting `defaultHttpOptions` on `Issuer` always merges your passed options with the default. | ||
[openid-certified-logo]: https://cdn.rawgit.com/panva/node-openid-client/master/OpenID_Certified.png | ||
[passport-url]: http://passportjs.org |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
66970
17
1207
441