Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: auth0/node-jsonwebtoken
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v7.2.1
Choose a base ref
...
head repository: auth0/node-jsonwebtoken
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v7.3.0
Choose a head ref
  • 8 commits
  • 7 files changed
  • 8 contributors

Commits on Dec 7, 2016

  1. update changelog

    jfromaniello committed Dec 7, 2016
    Copy the full SHA
    05d9978 View commit details

Commits on Feb 3, 2017

  1. rauchg/ms.js changed to zeit/ms (#303)

    * rauchg/ms.js changed to zeit/ms
    
    * Update README.md
    gswalden authored and ziluvatar committed Feb 3, 2017
    Copy the full SHA
    35d8415 View commit details
  2. Fix handling non string tokens (#305)

    Ricardo Gama authored and ziluvatar committed Feb 3, 2017
    Copy the full SHA
    1b6ec8d View commit details
  3. Fixed a simple typo (#287)

    * Fixed a simple typo
    
    The example didn't work because of a missing comma.
    
    * Fixed a simple typo
    
    The example didn't work because of a missing comma.
    EddyVerbruggen authored and ziluvatar committed Feb 3, 2017
    Copy the full SHA
    a542403 View commit details

Commits on Feb 9, 2017

  1. Raise jws.decode error to avoid confusion with "invalid token" error (#…

    …294)
    
    * Corrected indistinguishable error messages
    
    jws.decode() never throws an error. At least, in its current version. However, if it were to throw an exception, the diagnostics would be indistinguishable from a soft failure to decode a token. I had an extra trailing space on my JWT and it took me some additional debugging work to trace the actual root cause because the error message was not distinct.
    
    * Allowed an exception from inside of jws.decode to be handled by the caller. Currently, jws.decode never throws an exception. The change is made per discsussion in the original PR
    
    * Added a test case and proper forwarding of the possible exception thrown from jws.decode
    
    * Typo correction
    evolvah authored and ziluvatar committed Feb 9, 2017
    Copy the full SHA
    7f68fe0 View commit details

Commits on Feb 10, 2017

  1. Allow user to specify now. (#274)

    mborst authored and ziluvatar committed Feb 10, 2017
    Copy the full SHA
    8fdc150 View commit details

Commits on Feb 13, 2017

  1. Copy the full SHA
    1b0592e View commit details
  2. 7.3.0

    ziluvatar committed Feb 13, 2017
    Copy the full SHA
    94007b3 View commit details
Showing with 225 additions and 10 deletions.
  1. +5 −0 CHANGELOG.md
  2. +6 −4 README.md
  3. +1 −1 package.json
  4. +41 −0 test/issue_304.tests.js
  5. +15 −0 test/jwt.hs.tests.js
  6. +140 −1 test/verify.tests.js
  7. +17 −4 verify.js
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -4,6 +4,11 @@
All notable changes to this project will be documented in this file starting from version **v4.0.0**.
This project adheres to [Semantic Versioning](http://semver.org/).

## 7.2.1 - 2016-12-07

- add nsp check to find vulnerabilities on npm test ([4219c34b5346811c07f520f10516cc495bcc70dd](https://github.com/auth0/node-jsonwebtoken/commit/4219c34b5346811c07f520f10516cc495bcc70dd))
- revert to joi@^6 to keep ES5 compatibility ([51d4796c07344bf817687f7ccfeef78f00bf5b4f](https://github.com/auth0/node-jsonwebtoken/commit/51d4796c07344bf817687f7ccfeef78f00bf5b4f))

## 7.2.0 - 2016-12-06

- improve the documentation for expiration ([771e0b5f9bed90771fb79140eb38e51a3ecac8f0](https://github.com/auth0/node-jsonwebtoken/commit/771e0b5f9bed90771fb79140eb38e51a3ecac8f0))
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -29,8 +29,8 @@ encoded private key for RSA and ECDSA.
`options`:

* `algorithm` (default: `HS256`)
* `expiresIn`: expressed in seconds or a string describing a time span [rauchg/ms](https://github.com/rauchg/ms.js). Eg: `60`, `"2 days"`, `"10h"`, `"7d"`
* `notBefore`: expressed in seconds or a string describing a time span [rauchg/ms](https://github.com/rauchg/ms.js). Eg: `60`, `"2 days"`, `"10h"`, `"7d"`
* `expiresIn`: expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms). Eg: `60`, `"2 days"`, `"10h"`, `"7d"`
* `notBefore`: expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms). Eg: `60`, `"2 days"`, `"10h"`, `"7d"`
* `audience`
* `issuer`
* `jwtid`
@@ -78,7 +78,7 @@ Signing a token with 1 hour of expiration:

```javascript
jwt.sign({
exp: Math.floor(Date.now() / 1000) + (60 * 60)
exp: Math.floor(Date.now() / 1000) + (60 * 60),
data: 'foobar'
}, 'secret');
```
@@ -116,7 +116,9 @@ 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
* `clockTolerance`: number of seconds to tolerate when checking the `nbf` and `exp` claims, to deal with small clock differences among different servers
* `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.**
* `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)


```js
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jsonwebtoken",
"version": "7.2.1",
"version": "7.3.0",
"description": "JSON Web Token implementation (symmetric and asymmetric)",
"main": "index.js",
"scripts": {
41 changes: 41 additions & 0 deletions test/issue_304.tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
var jwt = require('../index');
var expect = require('chai').expect;

describe('issue 304 - verifying values other than strings', function() {

it('should fail with numbers', function (done) {
jwt.verify(123, 'foo', function (err, decoded) {
expect(err.name).to.equal('JsonWebTokenError');
done();
});
});

it('should fail with objects', function (done) {
jwt.verify({ foo: 'bar' }, 'biz', function (err, decoded) {
expect(err.name).to.equal('JsonWebTokenError');
done();
});
});

it('should fail with arrays', function (done) {
jwt.verify(['foo'], 'bar', function (err, decoded) {
expect(err.name).to.equal('JsonWebTokenError');
done();
});
});

it('should fail with functions', function (done) {
jwt.verify(function() {}, 'foo', function (err, decoded) {
expect(err.name).to.equal('JsonWebTokenError');
done();
});
});

it('should fail with booleans', function (done) {
jwt.verify(true, 'foo', function (err, decoded) {
expect(err.name).to.equal('JsonWebTokenError');
done();
});
});

});
15 changes: 15 additions & 0 deletions test/jwt.hs.tests.js
Original file line number Diff line number Diff line change
@@ -77,6 +77,21 @@ describe('HS256', function() {
done();
});
});
});

describe('should fail verification gracefully with trailing space in the jwt', function() {
var secret = 'shhhhhh';
var token = jwt.sign({ foo: 'bar' }, secret, { algorithm: 'HS256' });

it('should return the "invalid token" error', function(done) {
var malformedToken = token + ' '; // corrupt the token by adding a space
jwt.verify(malformedToken, secret, { algorithm: 'HS256', ignoreExpiration: true }, function(err, decoded) {
assert.isNotNull(err);
assert.equal('JsonWebTokenError', err.name);
assert.equal('invalid token', err.message);
done();
});
});
});

});
141 changes: 140 additions & 1 deletion test/verify.tests.js
Original file line number Diff line number Diff line change
@@ -189,6 +189,145 @@ describe('verify', function() {
});
});
});
});

describe('option: clockTimestamp', function () {
var clockTimestamp = 1000000000;
it('should verify unexpired token relative to user-provided clockTimestamp', function (done) {
var token = jwt.sign({foo: 'bar', iat: clockTimestamp, exp: clockTimestamp + 1}, key);
jwt.verify(token, key, {clockTimestamp: clockTimestamp}, function (err, p) {
assert.isNull(err);
done();
});
});
it('should error on expired token relative to user-provided clockTimestamp', function (done) {
var token = jwt.sign({foo: 'bar', iat: clockTimestamp, exp: clockTimestamp + 1}, key);
jwt.verify(token, key, {clockTimestamp: clockTimestamp + 1}, function (err, p) {
assert.equal(err.name, 'TokenExpiredError');
assert.equal(err.message, 'jwt expired');
assert.equal(err.expiredAt.constructor.name, 'Date');
assert.equal(Number(err.expiredAt), (clockTimestamp + 1) * 1000);
assert.isUndefined(p);
done();
});
});
it('should verify clockTimestamp is a number', function (done) {
var token = jwt.sign({foo: 'bar', iat: clockTimestamp, exp: clockTimestamp + 1}, key);
jwt.verify(token, key, {clockTimestamp: 'notANumber'}, function (err, p) {
assert.equal(err.name, 'JsonWebTokenError');
assert.equal(err.message,'clockTimestamp must be a number');
assert.isUndefined(p);
done();
});
});
it('should verify valid token with nbf', function (done) {
var token = jwt.sign({
foo: 'bar',
iat: clockTimestamp,
nbf: clockTimestamp + 1,
exp: clockTimestamp + 2
}, key);
jwt.verify(token, key, {clockTimestamp: clockTimestamp + 1}, function (err, p) {
assert.isNull(err);
done();
});
});
it('should error on token used before nbf', function (done) {
var token = jwt.sign({
foo: 'bar',
iat: clockTimestamp,
nbf: clockTimestamp + 1,
exp: clockTimestamp + 2
}, key);
jwt.verify(token, key, {clockTimestamp: clockTimestamp}, function (err, p) {
assert.equal(err.name, 'NotBeforeError');
assert.equal(err.date.constructor.name, 'Date');
assert.equal(Number(err.date), (clockTimestamp + 1) * 1000);
assert.isUndefined(p);
done();
});
});
});

describe('option: maxAge and clockTimestamp', function () {
// { foo: 'bar', iat: 1437018582, exp: 1437018800 }
var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODgwMH0.AVOsNC7TiT-XVSpCpkwB1240izzCIJ33Lp07gjnXVpA';
it('should error for claims issued before a certain timespan', function (done) {
var clockTimestamp = 1437018682;
var options = {algorithms: ['HS256'], clockTimestamp: clockTimestamp, maxAge: '1m'};

jwt.verify(token, key, options, function (err, p) {
assert.equal(err.name, 'TokenExpiredError');
assert.equal(err.message, 'maxAge exceeded');
assert.equal(err.expiredAt.constructor.name, 'Date');
assert.equal(Number(err.expiredAt), 1437018642000);
assert.isUndefined(p);
done();
});
});
it('should not error for claims issued before a certain timespan but still inside clockTolerance timespan', function (done) {
var clockTimestamp = 1437018582;
var options = {
algorithms: ['HS256'],
clockTimestamp: clockTimestamp,
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) {
var clockTimestamp = 1437018582;
var options = {algorithms: ['HS256'], clockTimestamp: clockTimestamp, maxAge: '600ms'};

jwt.verify(token, key, options, function (err, p) {
assert.isNull(err);
assert.equal(p.foo, 'bar');
done();
});
});
it('can be more restrictive than expiration', function (done) {
var clockTimestamp = 1437018588;
var options = {algorithms: ['HS256'], clockTimestamp: clockTimestamp, maxAge: '5s'};

jwt.verify(token, key, options, function (err, p) {
assert.equal(err.name, 'TokenExpiredError');
assert.equal(err.message, 'maxAge exceeded');
assert.equal(err.expiredAt.constructor.name, 'Date');
assert.equal(Number(err.expiredAt), 1437018587000);
assert.isUndefined(p);
done();
});
});
it('cannot be more permissive than expiration', function (done) {
var clockTimestamp = 1437018900;
var options = {algorithms: ['HS256'], clockTimestamp: clockTimestamp, maxAge: '1000y'};

jwt.verify(token, key, options, function (err, p) {
// maxAge not exceded, but still expired
assert.equal(err.name, 'TokenExpiredError');
assert.equal(err.message, 'jwt expired');
assert.equal(err.expiredAt.constructor.name, 'Date');
assert.equal(Number(err.expiredAt), 1437018800000);
assert.isUndefined(p);
done();
});
});
it('should error if maxAge is specified but there is no iat claim', function (done) {
var clockTimestamp = 1437018582;
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIifQ.0MBPd4Bru9-fK_HY3xmuDAc6N_embknmNuhdb9bKL_U';
var options = {algorithms: ['HS256'], clockTimestamp: clockTimestamp, 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');
assert.isUndefined(p);
done();
});
});
});
});
});
21 changes: 17 additions & 4 deletions verify.js
Original file line number Diff line number Diff line change
@@ -34,10 +34,20 @@ module.exports = function (jwtString, secretOrPublicKey, options, callback) {
};
}

if (options.clockTimestamp && typeof options.clockTimestamp !== 'number') {
return done(new JsonWebTokenError('clockTimestamp must be a number'));
}

var clockTimestamp = options.clockTimestamp || Math.floor(Date.now() / 1000);

if (!jwtString){
return done(new JsonWebTokenError('jwt must be provided'));
}

if (typeof jwtString !== 'string') {
return done(new JsonWebTokenError('jwt must be a string'));
}

var parts = jwtString.split('.');

if (parts.length !== 3){
@@ -72,7 +82,7 @@ module.exports = function (jwtString, secretOrPublicKey, options, callback) {
try {
decodedToken = jws.decode(jwtString);
} catch(err) {
return done(new JsonWebTokenError('invalid token'));
return done(err);
}

if (!decodedToken) {
@@ -108,7 +118,7 @@ module.exports = 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) + (options.clockTolerance || 0)) {
if (payload.nbf > clockTimestamp + (options.clockTolerance || 0)) {
return done(new NotBeforeError('jwt not active', new Date(payload.nbf * 1000)));
}
}
@@ -117,7 +127,7 @@ module.exports = 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 + (options.clockTolerance || 0)) {
if (clockTimestamp >= payload.exp + (options.clockTolerance || 0)) {
return done(new TokenExpiredError('jwt expired', new Date(payload.exp * 1000)));
}
}
@@ -159,7 +169,10 @@ module.exports = 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 + (options.clockTolerance || 0) * 1000) {
// We have to compare against either options.clockTimestamp or the currentDate _with_ millis
// to not change behaviour (version 7.2.1). Should be resolve somehow for next major.
var nowOrClockTimestamp = ((options.clockTimestamp || 0) * 1000) || Date.now();
if (nowOrClockTimestamp - (payload.iat * 1000) > maxAge + (options.clockTolerance || 0) * 1000) {
return done(new TokenExpiredError('maxAge exceeded', new Date(payload.iat * 1000 + maxAge)));
}
}