Skip to content

Commit a3c5c23

Browse files
committed
Merge branch 'master' of https://github.com/TATDK/node-jsonwebtoken into TATDK-master
Conflicts: README.md index.js
2 parents 7bb5aa6 + f26ba4e commit a3c5c23

File tree

4 files changed

+157
-2
lines changed

4 files changed

+157
-2
lines changed

Diff for: README.md

+21-2
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,19 @@ encoded private key for RSA and ECDSA.
2828

2929
* `algorithm` (default: `HS256`)
3030
* `expiresIn`: expressed in seconds or an string describing a time span [rauchg/ms](https://github.com/rauchg/ms.js). Eg: `60`, `"2 days"`, `"10h"`, `"7d"`
31+
* `notBeforeMinutes` or `notBeforeSeconds`
3132
* `audience`
3233
* `subject`
3334
* `issuer`
35+
* `jwtid`
36+
* `subject`
3437
* `noTimestamp`
3538
* `headers`
3639

3740
If `payload` is not a buffer or a string, it will be coerced into a string
3841
using `JSON.stringify`.
3942

40-
If any `expiresIn`, `audience`, `subject`, `issuer` are not provided, there is no default. The jwt generated won't include those properties in the payload.
43+
If any `expiresIn`, `notBeforeMinutes`, `audience`, `subject`, `issuer` are not provided, there is no default. The jwt generated won't include those properties in the payload.
4144

4245
Additional headers can be provided via the `headers` object.
4346

@@ -77,7 +80,9 @@ encoded public key for RSA and ECDSA.
7780
* `audience`: if you want to check audience (`aud`), provide a value here
7881
* `issuer`: if you want to check issuer (`iss`), provide a value here
7982
* `ignoreExpiration`: if `true` do not validate the expiration of the token.
80-
* `maxAge`: optional sets an expiration based on the `iat` field. Eg `2h`
83+
* `ignoreNotBefore`...
84+
* `jwtid`: if you want to check JWT ID (`jti`), provide a value here
85+
* `subject`: if you want to check subject (`sub`), provide a value here
8186

8287
```js
8388
// verify a token symmetric - synchronous
@@ -120,6 +125,18 @@ jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer' }, function(
120125
// if issuer mismatch, err == invalid issuer
121126
});
122127

128+
// verify jwt id
129+
var cert = fs.readFileSync('public.pem'); // get public key
130+
jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer', jwtid: 'jwtid' }, function(err, decoded) {
131+
// if jwt id mismatch, err == invalid jwt id
132+
});
133+
134+
// verify subject
135+
var cert = fs.readFileSync('public.pem'); // get public key
136+
jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer', jwtid: 'jwtid', subject: 'subject' }, function(err, decoded) {
137+
// if subject mismatch, err == invalid subject
138+
});
139+
123140
// alg mismatch
124141
var cert = fs.readFileSync('public.pem'); // get public key
125142
jwt.verify(token, cert, { algorithms: ['RS256'] }, function (err, payload) {
@@ -191,6 +208,8 @@ Error object:
191208
* 'invalid signature'
192209
* 'jwt audience invalid. expected: [OPTIONS AUDIENCE]'
193210
* 'jwt issuer invalid. expected: [OPTIONS ISSUER]'
211+
* 'jwt id invalid. expected: [OPTIONS JWT ID]'
212+
* 'jwt subject invalid. expected: [OPTIONS SUBJECT]'
194213

195214
```js
196215
jwt.verify(token, 'shhhhh', function(err, decoded) {

Diff for: index.js

+20
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var ms = require('ms');
44
var JWT = module.exports;
55

66
var JsonWebTokenError = JWT.JsonWebTokenError = require('./lib/JsonWebTokenError');
7+
var NotBeforeError = module.exports.NotBeforeError = require('./lib/NotBeforeError');
78
var TokenExpiredError = JWT.TokenExpiredError = require('./lib/TokenExpiredError');
89

910
JWT.decode = function (jwt, options) {
@@ -56,6 +57,14 @@ JWT.sign = function(payload, secretOrPrivateKey, options, callback) {
5657
if (!options.noTimestamp) {
5758
payload.iat = payload.iat || timestamp;
5859
}
60+
61+
var notBeforeSeconds = options.notBeforeMinutes ?
62+
options.notBeforeMinutes * 60 :
63+
options.notBeforeSeconds;
64+
65+
if (notBeforeSeconds) {
66+
payload.nbf = timestamp + notBeforeSeconds;
67+
}
5968

6069
if (options.expiresInSeconds || options.expiresInMinutes) {
6170
var deprecated_line;
@@ -96,6 +105,9 @@ JWT.sign = function(payload, secretOrPrivateKey, options, callback) {
96105
if (options.subject)
97106
payload.sub = options.subject;
98107

108+
if (options.jwtid)
109+
payload.jti = options.jwtid;
110+
99111
var encoding = 'utf8';
100112
if (options.encoding) {
101113
encoding = options.encoding;
@@ -199,6 +211,14 @@ JWT.verify = function(jwtString, secretOrPublicKey, options, callback) {
199211
} catch(err) {
200212
return done(err);
201213
}
214+
215+
if (typeof payload.nbf !== 'undefined' && !options.ignoreNotBefore) {
216+
if (typeof payload.nbf !== 'number') {
217+
return done(new JsonWebTokenError('invalid nbf value'));
218+
}
219+
if (payload.nbf >= Math.floor(Date.now() / 1000))
220+
return done(new NotBeforeError('jwt not active', new Date(payload.nbf * 1000)));
221+
}
202222

203223
if (typeof payload.exp !== 'undefined' && !options.ignoreExpiration) {
204224
if (typeof payload.exp !== 'number') {

Diff for: lib/NotBeforeError.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
var JsonWebTokenError = require('./JsonWebTokenError');
2+
3+
var NotBeforeError = function (message, date) {
4+
JsonWebTokenError.call(this, message);
5+
this.name = 'NotBeforeError';
6+
this.date = date;
7+
};
8+
9+
NotBeforeError.prototype = Object.create(JsonWebTokenError.prototype);
10+
11+
NotBeforeError.prototype.constructor = NotBeforeError;
12+
13+
module.exports = NotBeforeError;

Diff for: test/jwt.rs.tests.js

+103
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,43 @@ describe('RS256', function() {
8888
});
8989
});
9090

91+
describe('when signing a token with not before', function() {
92+
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', notBeforeMinutes: -10 });
93+
94+
it('should be valid expiration', function(done) {
95+
jwt.verify(token, pub, function(err, decoded) {
96+
assert.isNotNull(decoded);
97+
assert.isNull(err);
98+
done();
99+
});
100+
});
101+
102+
it('should be invalid', function(done) {
103+
// not active token
104+
token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', notBeforeMinutes: 10 });
105+
106+
jwt.verify(token, pub, function(err, decoded) {
107+
assert.isUndefined(decoded);
108+
assert.isNotNull(err);
109+
assert.equal(err.name, 'NotBeforeError');
110+
assert.instanceOf(err.date, Date);
111+
assert.instanceOf(err, jwt.NotBeforeError);
112+
done();
113+
});
114+
});
115+
116+
it('should NOT be invalid', function(done) {
117+
// not active token
118+
token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', notBeforeMinutes: 10 });
119+
120+
jwt.verify(token, pub, { ignoreNotBefore: true }, function(err, decoded) {
121+
assert.ok(decoded.foo);
122+
assert.equal('bar', decoded.foo);
123+
done();
124+
});
125+
});
126+
});
127+
91128
describe('when signing a token with audience', function() {
92129
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', audience: 'urn:foo' });
93130

@@ -236,6 +273,72 @@ describe('RS256', function() {
236273
});
237274
});
238275

276+
describe('when signing a token with subject', function() {
277+
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', subject: 'subject' });
278+
279+
it('should check subject', function() {
280+
jwt.verify(token, pub, { subject: 'subject' }, function(err, decoded) {
281+
assert.isNotNull(decoded);
282+
assert.isNull(err);
283+
});
284+
});
285+
286+
it('should throw when invalid subject', function() {
287+
jwt.verify(token, pub, { issuer: 'wrongSubject' }, function(err, decoded) {
288+
assert.isUndefined(decoded);
289+
assert.isNotNull(err);
290+
assert.equal(err.name, 'JsonWebTokenError');
291+
assert.instanceOf(err, jwt.JsonWebTokenError);
292+
});
293+
});
294+
});
295+
296+
describe('when signing a token without subject', function() {
297+
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256' });
298+
299+
it('should check subject', function() {
300+
jwt.verify(token, pub, { subject: 'subject' }, function(err, decoded) {
301+
assert.isUndefined(decoded);
302+
assert.isNotNull(err);
303+
assert.equal(err.name, 'JsonWebTokenError');
304+
assert.instanceOf(err, jwt.JsonWebTokenError);
305+
});
306+
});
307+
});
308+
309+
describe('when signing a token with jwt id', function() {
310+
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', jwtid: 'jwtid' });
311+
312+
it('should check jwt id', function() {
313+
jwt.verify(token, pub, { jwtid: 'jwtid' }, function(err, decoded) {
314+
assert.isNotNull(decoded);
315+
assert.isNull(err);
316+
});
317+
});
318+
319+
it('should throw when invalid jwt id', function() {
320+
jwt.verify(token, pub, { jwtid: 'wrongJwtid' }, function(err, decoded) {
321+
assert.isUndefined(decoded);
322+
assert.isNotNull(err);
323+
assert.equal(err.name, 'JsonWebTokenError');
324+
assert.instanceOf(err, jwt.JsonWebTokenError);
325+
});
326+
});
327+
});
328+
329+
describe('when signing a token without jwt id', function() {
330+
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256' });
331+
332+
it('should check jwt id', function() {
333+
jwt.verify(token, pub, { jwtid: 'jwtid' }, function(err, decoded) {
334+
assert.isUndefined(decoded);
335+
assert.isNotNull(err);
336+
assert.equal(err.name, 'JsonWebTokenError');
337+
assert.instanceOf(err, jwt.JsonWebTokenError);
338+
});
339+
});
340+
});
341+
239342
describe('when verifying a malformed token', function() {
240343
it('should throw', function(done) {
241344
jwt.verify('fruit.fruit.fruit', pub, function(err, decoded) {

0 commit comments

Comments
 (0)