mirror of
https://github.com/hanzoai/node-jsonwebtoken.git
synced 2026-09-24 17:00:48 +00:00
Merge branch 'origin/clock_skew_tolerance' of https://github.com/jacopofar/node-jsonwebtoken into jacopofar-origin/clock_skew_tolerance
This commit is contained in:
@@ -43,7 +43,7 @@ There are no default values for `expiresIn`, `notBefore`, `audience`, `subject`,
|
||||
|
||||
The header can be customized via the `option.header` object.
|
||||
|
||||
Generated JWTs will include an `iat` claim by default unless `noTimestamp` is specified.
|
||||
Generated jwts will include an `iat` (issued at) claim by default unless `noTimestamp` is specified. If `iat` is inserted in the payload, it will be used instead of the real timestamp for calculating other things like `exp` given a timespan in `options.expiresIn`.
|
||||
|
||||
Example
|
||||
|
||||
@@ -51,6 +51,8 @@ Example
|
||||
// sign with default (HMAC SHA256)
|
||||
var jwt = require('jsonwebtoken');
|
||||
var token = jwt.sign({ foo: 'bar' }, 'shhhhh');
|
||||
//backdate a jwt 30 seconds
|
||||
var older_token = jwt.sign({ foo: 'bar', iat: Math.floor(Date.now() / 1000) - 30 }, 'shhhhh');
|
||||
|
||||
// sign with RSA SHA256
|
||||
var cert = fs.readFileSync('private.key'); // get private key
|
||||
@@ -81,6 +83,8 @@ encoded public key for RSA and ECDSA.
|
||||
* `ignoreExpiration`: if `true` do not validate the expiration of the token.
|
||||
* `ignoreNotBefore`...
|
||||
* `subject`: if you want to check subject (`sub`), provide a value here
|
||||
* `clockTolerance`: number of second to tolerate when checking the `nbf` and `exp` claims, to deal with small clock differences among different servers
|
||||
|
||||
|
||||
```js
|
||||
// verify a token symmetric - synchronous
|
||||
|
||||
@@ -135,7 +135,7 @@ JWT.verify = function(jwtString, secretOrPublicKey, options, callback) {
|
||||
if (typeof payload.nbf !== 'number') {
|
||||
return done(new JsonWebTokenError('invalid nbf value'));
|
||||
}
|
||||
if (payload.nbf > Math.floor(Date.now() / 1000)) {
|
||||
if (payload.nbf > Math.floor(Date.now() / 1000) + (options.clockTolerance || 0)) {
|
||||
return done(new NotBeforeError('jwt not active', new Date(payload.nbf * 1000)));
|
||||
}
|
||||
}
|
||||
@@ -144,8 +144,9 @@ JWT.verify = function(jwtString, secretOrPublicKey, options, callback) {
|
||||
if (typeof payload.exp !== 'number') {
|
||||
return done(new JsonWebTokenError('invalid exp value'));
|
||||
}
|
||||
if (Math.floor(Date.now() / 1000) >= payload.exp)
|
||||
if (Math.floor(Date.now() / 1000) >= payload.exp + (options.clockTolerance || 0)) {
|
||||
return done(new TokenExpiredError('jwt expired', new Date(payload.exp * 1000)));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.audience) {
|
||||
@@ -185,7 +186,7 @@ JWT.verify = function(jwtString, secretOrPublicKey, options, callback) {
|
||||
if (typeof payload.iat !== 'number') {
|
||||
return done(new JsonWebTokenError('iat required when maxAge is specified'));
|
||||
}
|
||||
if (Date.now() - (payload.iat * 1000) > maxAge) {
|
||||
if (Date.now() - (payload.iat * 1000) > maxAge + (options.clockTolerance || 0) * 1000) {
|
||||
return done(new TokenExpiredError('maxAge exceeded', new Date(payload.iat * 1000 + maxAge)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,13 @@ var sign_options_schema = Joi.object().keys({
|
||||
noTimestamp: Joi.boolean()
|
||||
});
|
||||
|
||||
var registered_claims_schema = Joi.object().keys({
|
||||
iat: Joi.number(),
|
||||
exp: Joi.number(),
|
||||
nbf: Joi.number()
|
||||
}).unknown();
|
||||
|
||||
|
||||
var options_to_payload = {
|
||||
'audience': 'aud',
|
||||
'issuer': 'iss',
|
||||
@@ -44,6 +51,12 @@ module.exports = function(payload, secretOrPrivateKey, options, callback) {
|
||||
if (typeof payload === 'undefined') {
|
||||
throw new Error('payload is required');
|
||||
} else if (typeof payload === 'object') {
|
||||
var payload_validation_result = registered_claims_schema.validate(payload);
|
||||
|
||||
if (payload_validation_result.error) {
|
||||
throw payload_validation_result.error;
|
||||
}
|
||||
|
||||
payload = xtend(payload);
|
||||
} else if (typeof payload !== 'object') {
|
||||
var invalid_options = options_for_objects.filter(function (opt) {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
var jwt = require('../index');
|
||||
var expect = require('chai').expect;
|
||||
|
||||
describe('iat', function() {
|
||||
|
||||
it('should work with a numeric iat not changing the expiration date', function () {
|
||||
var token = jwt.sign({foo: 123, iat: Math.floor(Date.now() / 1000) - 30}, '123', { expiresIn: 10 });
|
||||
var result = jwt.verify(token, '123');
|
||||
expect(result.exp).to.be.closeTo(Math.floor(Date.now() / 1000) + 10, 0.2);
|
||||
});
|
||||
|
||||
|
||||
it('should throw if iat is not a number', function () {
|
||||
expect(function () {
|
||||
jwt.sign({foo: 123, iat:'hello'}, '123');
|
||||
}).to.throw(/"iat" must be a number/);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
+35
-12
@@ -15,16 +15,16 @@ describe('verify', function() {
|
||||
var payload = { iat: Math.floor(Date.now() / 1000 ) };
|
||||
|
||||
var signed = jws.sign({
|
||||
header: header,
|
||||
header: header,
|
||||
payload: payload,
|
||||
secret: priv,
|
||||
encoding: 'utf8'
|
||||
});
|
||||
|
||||
jwt.verify(signed, pub, {typ: 'JWT'}, function(err, p) {
|
||||
assert.isNull(err);
|
||||
assert.deepEqual(p, payload);
|
||||
done();
|
||||
assert.isNull(err);
|
||||
assert.deepEqual(p, payload);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('verify', function() {
|
||||
// { foo: 'bar', iat: 1437018582, exp: 1437018583 }
|
||||
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU4M30.NmMv7sXjM1dW0eALNXud8LoXknZ0mH14GtnFclwJv0s';
|
||||
var key = 'key';
|
||||
|
||||
|
||||
var clock;
|
||||
afterEach(function () {
|
||||
try { clock.restore(); } catch (e) {}
|
||||
@@ -70,9 +70,20 @@ describe('verify', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not error on unexpired token', function (done) {
|
||||
clock = sinon.useFakeTimers(1437018582000);
|
||||
var options = {algorithms: ['HS256']}
|
||||
it('should not error on expired token within clockTolerance interval', function (done) {
|
||||
clock = sinon.useFakeTimers(1437018584000);
|
||||
var options = {algorithms: ['HS256'], clockTolerance: 100}
|
||||
|
||||
jwt.verify(token, key, options, function (err, p) {
|
||||
assert.isNull(err);
|
||||
assert.equal(p.foo, 'bar');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not error if within maxAge timespan', function (done) {
|
||||
clock = sinon.useFakeTimers(1437018582500);
|
||||
var options = {algorithms: ['HS256'], maxAge: '600ms'};
|
||||
|
||||
jwt.verify(token, key, options, function (err, p) {
|
||||
assert.isNull(err);
|
||||
@@ -95,10 +106,22 @@ describe('verify', function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not error for claims issued before a certain timespan but still inside clockTolerance timespan', function (done) {
|
||||
clock = sinon.useFakeTimers(1437018582500);
|
||||
var options = {algorithms: ['HS256'], maxAge: '321ms', clockTolerance: 100};
|
||||
|
||||
jwt.verify(token, key, options, function (err, p) {
|
||||
assert.isNull(err);
|
||||
assert.equal(p.foo, 'bar');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not error if within maxAge timespan', function (done) {
|
||||
clock = sinon.useFakeTimers(1437018582500);
|
||||
var options = {algorithms: ['HS256'], maxAge: '600ms'};
|
||||
|
||||
|
||||
jwt.verify(token, key, options, function (err, p) {
|
||||
assert.isNull(err);
|
||||
assert.equal(p.foo, 'bar');
|
||||
@@ -108,7 +131,7 @@ describe('verify', function() {
|
||||
it('can be more restrictive than expiration', function (done) {
|
||||
clock = sinon.useFakeTimers(1437018582900);
|
||||
var options = {algorithms: ['HS256'], maxAge: '800ms'};
|
||||
|
||||
|
||||
jwt.verify(token, key, options, function (err, p) {
|
||||
assert.equal(err.name, 'TokenExpiredError');
|
||||
assert.equal(err.message, 'maxAge exceeded');
|
||||
@@ -121,7 +144,7 @@ describe('verify', function() {
|
||||
it('cannot be more permissive than expiration', function (done) {
|
||||
clock = sinon.useFakeTimers(1437018583100);
|
||||
var options = {algorithms: ['HS256'], maxAge: '1200ms'};
|
||||
|
||||
|
||||
jwt.verify(token, key, options, function (err, p) {
|
||||
// maxAge not exceded, but still expired
|
||||
assert.equal(err.name, 'TokenExpiredError');
|
||||
@@ -136,7 +159,7 @@ describe('verify', function() {
|
||||
clock = sinon.useFakeTimers(1437018582900);
|
||||
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIifQ.0MBPd4Bru9-fK_HY3xmuDAc6N_embknmNuhdb9bKL_U';
|
||||
var options = {algorithms: ['HS256'], maxAge: '1s'};
|
||||
|
||||
|
||||
jwt.verify(token, key, options, function (err, p) {
|
||||
assert.equal(err.name, 'JsonWebTokenError');
|
||||
assert.equal(err.message, 'iat required when maxAge is specified');
|
||||
|
||||
Reference in New Issue
Block a user