Skip to content

Commit b61cc34

Browse files
committed
maxAge: Fix logic with number + use seconds instead of ms
1 parent 6755049 commit b61cc34

File tree

3 files changed

+82
-71
lines changed

3 files changed

+82
-71
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ encoded public key for RSA and ECDSA.
119119
* `ignoreNotBefore`...
120120
* `subject`: if you want to check subject (`sub`), provide a value here
121121
* `clockTolerance`: number of seconds to tolerate when checking the `nbf` and `exp` claims, to deal with small clock differences among different servers
122-
* `maxAge`: the maximum allowed age for tokens to still be valid. Currently it is expressed in milliseconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms). Eg: `1000`, `"2 days"`, `"10h"`, `"7d"`. **We advise against using milliseconds precision, though, since JWTs can only contain seconds. The maximum precision might be reduced to seconds in the future.**
123-
* `clockTimestamp`: the time in seconds that should be used as the current time for all necessary comparisons (also against `maxAge`, so our advise is to avoid using `clockTimestamp` and a `maxAge` in milliseconds together)
122+
* `maxAge`: the maximum allowed age for tokens to still be valid. It is expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms). Eg: `1000`, `"2 days"`, `"10h"`, `"7d"`.
123+
* `clockTimestamp`: the time in seconds that should be used as the current time for all necessary comparisons.
124124

125125

126126
```js

test/verify.tests.js

Lines changed: 75 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ describe('verify', function() {
6868
});
6969

7070
describe('expiration', function () {
71-
// { foo: 'bar', iat: 1437018582, exp: 1437018583 }
72-
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU4M30.NmMv7sXjM1dW0eALNXud8LoXknZ0mH14GtnFclwJv0s';
71+
// { foo: 'bar', iat: 1437018582, exp: 1437018592 }
72+
var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU5Mn0.3aR3vocmgRpG05rsI9MpR6z2T_BGtMQaPq2YR6QaroU';
7373
var key = 'key';
7474

7575
var clock;
@@ -78,22 +78,22 @@ describe('verify', function() {
7878
});
7979

8080
it('should error on expired token', function (done) {
81-
clock = sinon.useFakeTimers(1437018650000);
81+
clock = sinon.useFakeTimers(1437018650000); // iat + 58s, exp + 48s
8282
var options = {algorithms: ['HS256']};
8383

8484
jwt.verify(token, key, options, function (err, p) {
8585
assert.equal(err.name, 'TokenExpiredError');
8686
assert.equal(err.message, 'jwt expired');
8787
assert.equal(err.expiredAt.constructor.name, 'Date');
88-
assert.equal(Number(err.expiredAt), 1437018583000);
88+
assert.equal(Number(err.expiredAt), 1437018592000);
8989
assert.isUndefined(p);
9090
done();
9191
});
9292
});
9393

9494
it('should not error on expired token within clockTolerance interval', function (done) {
95-
clock = sinon.useFakeTimers(1437018584000);
96-
var options = {algorithms: ['HS256'], clockTolerance: 100}
95+
clock = sinon.useFakeTimers(1437018594000); // iat + 12s, exp + 2s
96+
var options = {algorithms: ['HS256'], clockTolerance: 5 }
9797

9898
jwt.verify(token, key, options, function (err, p) {
9999
assert.isNull(err);
@@ -103,8 +103,8 @@ describe('verify', function() {
103103
});
104104

105105
it('should not error if within maxAge timespan', function (done) {
106-
clock = sinon.useFakeTimers(1437018582500);
107-
var options = {algorithms: ['HS256'], maxAge: '600ms'};
106+
clock = sinon.useFakeTimers(1437018587500); // iat + 5.5s, exp - 4.5s
107+
var options = {algorithms: ['HS256'], maxAge: '6s'};
108108

109109
jwt.verify(token, key, options, function (err, p) {
110110
assert.isNull(err);
@@ -114,70 +114,83 @@ describe('verify', function() {
114114
});
115115

116116
describe('option: maxAge', function () {
117-
it('should error for claims issued before a certain timespan', function (done) {
118-
clock = sinon.useFakeTimers(1437018582500);
119-
var options = {algorithms: ['HS256'], maxAge: '321ms'};
120117

121-
jwt.verify(token, key, options, function (err, p) {
122-
assert.equal(err.name, 'TokenExpiredError');
123-
assert.equal(err.message, 'maxAge exceeded');
124-
assert.equal(err.expiredAt.constructor.name, 'Date');
125-
assert.equal(Number(err.expiredAt), 1437018582321);
126-
assert.isUndefined(p);
127-
done();
118+
['3s', 3].forEach(function(maxAge) {
119+
it(`should error for claims issued before a certain timespan (${typeof maxAge} type)`, function (done) {
120+
clock = sinon.useFakeTimers(1437018587000); // iat + 5s, exp - 5s
121+
var options = {algorithms: ['HS256'], maxAge: maxAge};
122+
123+
jwt.verify(token, key, options, function (err, p) {
124+
assert.equal(err.name, 'TokenExpiredError');
125+
assert.equal(err.message, 'maxAge exceeded');
126+
assert.equal(err.expiredAt.constructor.name, 'Date');
127+
assert.equal(Number(err.expiredAt), 1437018585000);
128+
assert.isUndefined(p);
129+
done();
130+
});
128131
});
129132
});
130133

131-
it('should not error for claims issued before a certain timespan but still inside clockTolerance timespan', function (done) {
132-
clock = sinon.useFakeTimers(1437018582500);
133-
var options = {algorithms: ['HS256'], maxAge: '321ms', clockTolerance: 100};
134+
['5s', 5].forEach(function (maxAge) {
135+
it(`should not error for claims issued before a certain timespan but still inside clockTolerance timespan (${typeof maxAge} type)`, function (done) {
136+
clock = sinon.useFakeTimers(1437018587500); // iat + 5.5s, exp - 4.5s
137+
var options = {algorithms: ['HS256'], maxAge: maxAge, clockTolerance: 1 };
134138

135-
jwt.verify(token, key, options, function (err, p) {
136-
assert.isNull(err);
137-
assert.equal(p.foo, 'bar');
138-
done();
139+
jwt.verify(token, key, options, function (err, p) {
140+
assert.isNull(err);
141+
assert.equal(p.foo, 'bar');
142+
done();
143+
});
139144
});
140145
});
141146

142-
it('should not error if within maxAge timespan', function (done) {
143-
clock = sinon.useFakeTimers(1437018582500);
144-
var options = {algorithms: ['HS256'], maxAge: '600ms'};
147+
['6s', 6].forEach(function (maxAge) {
148+
it(`should not error if within maxAge timespan (${typeof maxAge} type)`, function (done) {
149+
clock = sinon.useFakeTimers(1437018587500);// iat + 5.5s, exp - 4.5s
150+
var options = {algorithms: ['HS256'], maxAge: maxAge};
145151

146-
jwt.verify(token, key, options, function (err, p) {
147-
assert.isNull(err);
148-
assert.equal(p.foo, 'bar');
149-
done();
152+
jwt.verify(token, key, options, function (err, p) {
153+
assert.isNull(err);
154+
assert.equal(p.foo, 'bar');
155+
done();
156+
});
150157
});
151158
});
152-
it('can be more restrictive than expiration', function (done) {
153-
clock = sinon.useFakeTimers(1437018582900);
154-
var options = {algorithms: ['HS256'], maxAge: '800ms'};
155159

156-
jwt.verify(token, key, options, function (err, p) {
157-
assert.equal(err.name, 'TokenExpiredError');
158-
assert.equal(err.message, 'maxAge exceeded');
159-
assert.equal(err.expiredAt.constructor.name, 'Date');
160-
assert.equal(Number(err.expiredAt), 1437018582800);
161-
assert.isUndefined(p);
162-
done();
160+
['8s', 8].forEach(function (maxAge) {
161+
it(`can be more restrictive than expiration (${typeof maxAge} type)`, function (done) {
162+
clock = sinon.useFakeTimers(1437018591900); // iat + 9.9s, exp - 0.1s
163+
var options = {algorithms: ['HS256'], maxAge: maxAge };
164+
165+
jwt.verify(token, key, options, function (err, p) {
166+
assert.equal(err.name, 'TokenExpiredError');
167+
assert.equal(err.message, 'maxAge exceeded');
168+
assert.equal(err.expiredAt.constructor.name, 'Date');
169+
assert.equal(Number(err.expiredAt), 1437018590000);
170+
assert.isUndefined(p);
171+
done();
172+
});
163173
});
164174
});
165-
it('cannot be more permissive than expiration', function (done) {
166-
clock = sinon.useFakeTimers(1437018583100);
167-
var options = {algorithms: ['HS256'], maxAge: '1200ms'};
168175

169-
jwt.verify(token, key, options, function (err, p) {
170-
// maxAge not exceded, but still expired
171-
assert.equal(err.name, 'TokenExpiredError');
172-
assert.equal(err.message, 'jwt expired');
173-
assert.equal(err.expiredAt.constructor.name, 'Date');
174-
assert.equal(Number(err.expiredAt), 1437018583000);
175-
assert.isUndefined(p);
176-
done();
176+
['12s', 12].forEach(function (maxAge) {
177+
it(`cannot be more permissive than expiration (${typeof maxAge} type)`, function (done) {
178+
clock = sinon.useFakeTimers(1437018593000); // iat + 11s, exp + 1s
179+
var options = {algorithms: ['HS256'], maxAge: '12s'};
180+
181+
jwt.verify(token, key, options, function (err, p) {
182+
// maxAge not exceded, but still expired
183+
assert.equal(err.name, 'TokenExpiredError');
184+
assert.equal(err.message, 'jwt expired');
185+
assert.equal(err.expiredAt.constructor.name, 'Date');
186+
assert.equal(Number(err.expiredAt), 1437018592000);
187+
assert.isUndefined(p);
188+
done();
189+
});
177190
});
178191
});
192+
179193
it('should error if maxAge is specified but there is no iat claim', function (done) {
180-
clock = sinon.useFakeTimers(1437018582900);
181194
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIifQ.0MBPd4Bru9-fK_HY3xmuDAc6N_embknmNuhdb9bKL_U';
182195
var options = {algorithms: ['HS256'], maxAge: '1s'};
183196

@@ -249,7 +262,7 @@ describe('verify', function() {
249262
});
250263

251264
describe('option: maxAge and clockTimestamp', function () {
252-
// { foo: 'bar', iat: 1437018582, exp: 1437018800 }
265+
// { foo: 'bar', iat: 1437018582, exp: 1437018800 } exp = iat + 218s
253266
var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODgwMH0.AVOsNC7TiT-XVSpCpkwB1240izzCIJ33Lp07gjnXVpA';
254267
it('should error for claims issued before a certain timespan', function (done) {
255268
var clockTimestamp = 1437018682;
@@ -265,12 +278,12 @@ describe('verify', function() {
265278
});
266279
});
267280
it('should not error for claims issued before a certain timespan but still inside clockTolerance timespan', function (done) {
268-
var clockTimestamp = 1437018582;
281+
var clockTimestamp = 1437018592; // iat + 10s
269282
var options = {
270283
algorithms: ['HS256'],
271284
clockTimestamp: clockTimestamp,
272-
maxAge: '321ms',
273-
clockTolerance: 100
285+
maxAge: '3s',
286+
clockTolerance: 10
274287
};
275288

276289
jwt.verify(token, key, options, function (err, p) {
@@ -280,8 +293,8 @@ describe('verify', function() {
280293
});
281294
});
282295
it('should not error if within maxAge timespan', function (done) {
283-
var clockTimestamp = 1437018582;
284-
var options = {algorithms: ['HS256'], clockTimestamp: clockTimestamp, maxAge: '600ms'};
296+
var clockTimestamp = 1437018587; // iat + 5s
297+
var options = {algorithms: ['HS256'], clockTimestamp: clockTimestamp, maxAge: '6s'};
285298

286299
jwt.verify(token, key, options, function (err, p) {
287300
assert.isNull(err);
@@ -290,7 +303,7 @@ describe('verify', function() {
290303
});
291304
});
292305
it('can be more restrictive than expiration', function (done) {
293-
var clockTimestamp = 1437018588;
306+
var clockTimestamp = 1437018588; // iat + 6s
294307
var options = {algorithms: ['HS256'], clockTimestamp: clockTimestamp, maxAge: '5s'};
295308

296309
jwt.verify(token, key, options, function (err, p) {
@@ -303,7 +316,7 @@ describe('verify', function() {
303316
});
304317
});
305318
it('cannot be more permissive than expiration', function (done) {
306-
var clockTimestamp = 1437018900;
319+
var clockTimestamp = 1437018900; // iat + 318s (exp: iat + 218s)
307320
var options = {algorithms: ['HS256'], clockTimestamp: clockTimestamp, maxAge: '1000y'};
308321

309322
jwt.verify(token, key, options, function (err, p) {

verify.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ var JsonWebTokenError = require('./lib/JsonWebTokenError');
22
var NotBeforeError = require('./lib/NotBeforeError');
33
var TokenExpiredError = require('./lib/TokenExpiredError');
44
var decode = require('./decode');
5+
var timespan = require('./lib/timespan');
56
var jws = require('jws');
6-
var ms = require('ms');
77
var xtend = require('xtend');
88

99
module.exports = function (jwtString, secretOrPublicKey, options, callback) {
@@ -165,15 +165,13 @@ module.exports = function (jwtString, secretOrPublicKey, options, callback) {
165165
}
166166

167167
if (options.maxAge) {
168-
var maxAge = ms(options.maxAge);
169168
if (typeof payload.iat !== 'number') {
170169
return done(new JsonWebTokenError('iat required when maxAge is specified'));
171170
}
172-
// We have to compare against either options.clockTimestamp or the currentDate _with_ millis
173-
// to not change behaviour (version 7.2.1). Should be resolve somehow for next major.
174-
var nowOrClockTimestamp = ((options.clockTimestamp || 0) * 1000) || Date.now();
175-
if (nowOrClockTimestamp - (payload.iat * 1000) > maxAge + (options.clockTolerance || 0) * 1000) {
176-
return done(new TokenExpiredError('maxAge exceeded', new Date(payload.iat * 1000 + maxAge)));
171+
172+
var maxAgeTimestamp = timespan(options.maxAge, payload.iat);
173+
if (clockTimestamp >= maxAgeTimestamp + (options.clockTolerance || 0)) {
174+
return done(new TokenExpiredError('maxAge exceeded', new Date(maxAgeTimestamp * 1000)));
177175
}
178176
}
179177

0 commit comments

Comments
 (0)