Skip to content

Commit 1a95784

Browse files
committed
fix(sign&verify)!: Remove default none support from sign and verify methods, and require it to be explicitly configured
BREAKING CHANGE: Removes fallback for none algorithm for the verify method.
1 parent 7e6a86b commit 1a95784

18 files changed

+145
-137
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ encoded private key for RSA and ECDSA. In case of a private key with passphrase
3838
`options`:
3939

4040
* `algorithm` (default: `HS256`)
41+
* * `none` MUST be configured in order to create unsigned tokens.
4142
* `expiresIn`: expressed in seconds or a string describing a time span [vercel/ms](https://github.com/vercel/ms).
4243
> Eg: `60`, `"2 days"`, `"10h"`, `"7d"`. A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), otherwise milliseconds unit is used by default (`"120"` is equal to `"120ms"`).
4344
* `notBefore`: expressed in seconds or a string describing a time span [vercel/ms](https://github.com/vercel/ms).
@@ -138,6 +139,7 @@ As mentioned in [this comment](https://github.com/auth0/node-jsonwebtoken/issues
138139
`options`
139140

140141
* `algorithms`: List of strings with the names of the allowed algorithms. For instance, `["HS256", "HS384"]`.
142+
* `none` MUST be configured in order to verify unsigned tokens.
141143
* `audience`: if you want to check audience (`aud`), provide a value here. The audience can be checked against a string, a regular expression or a list of strings and/or regular expressions.
142144
> Eg: `"urn:foo"`, `/urn:f[o]{2}/`, `[/urn:f[o]{2}/, "urn:bar"]`
143145
* `complete`: return an object with the decoded `{ payload, header, signature }` instead of only the usual content of the payload.

test/async_sign.tests.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,6 @@ describe('signing a token asynchronously', function() {
4141
});
4242
});
4343

44-
//Known bug: https://github.com/brianloveswords/node-jws/issues/62
45-
//If you need this use case, you need to go for the non-callback-ish code style.
46-
it.skip('should work with none algorithm where secret is falsy', function(done) {
47-
jwt.sign({ foo: 'bar' }, undefined, { algorithm: 'none' }, function(err, token) {
48-
expect(token).to.be.a('string');
49-
expect(token.split('.')).to.have.length(3);
50-
done();
51-
});
52-
});
53-
5444
it('should return error when secret is not a cert for RS256', function(done) {
5545
//this throw an error because the secret is not a cert and RS256 requires a cert.
5646
jwt.sign({ foo: 'bar' }, secret, { algorithm: 'RS256' }, function (err) {

test/claim-aud.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const util = require('util');
66
const testUtils = require('./test-utils');
77

88
function signWithAudience(audience, payload, callback) {
9-
const options = {algorithm: 'none'};
9+
const options = {algorithm: 'HS256'};
1010
if (audience !== undefined) {
1111
options.audience = audience;
1212
}
@@ -15,7 +15,7 @@ function signWithAudience(audience, payload, callback) {
1515
}
1616

1717
function verifyWithAudience(token, audience, callback) {
18-
testUtils.verifyJWTHelper(token, undefined, {audience}, callback);
18+
testUtils.verifyJWTHelper(token, 'secret', {audience}, callback);
1919
}
2020

2121
describe('audience', function() {
@@ -47,7 +47,7 @@ describe('audience', function() {
4747

4848
// undefined needs special treatment because {} is not the same as {aud: undefined}
4949
it('should error with with value undefined', function (done) {
50-
testUtils.signJWTHelper({}, 'secret', {audience: undefined, algorithm: 'none'}, (err) => {
50+
testUtils.signJWTHelper({}, 'secret', {audience: undefined, algorithm: 'HS256'}, (err) => {
5151
testUtils.asyncCheck(done, () => {
5252
expect(err).to.be.instanceOf(Error);
5353
expect(err).to.have.property('message', '"audience" must be a string or array');

test/claim-exp.test.js

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@ const expect = require('chai').expect;
55
const sinon = require('sinon');
66
const util = require('util');
77
const testUtils = require('./test-utils');
8-
9-
const base64UrlEncode = testUtils.base64UrlEncode;
10-
const noneAlgorithmHeader = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0';
8+
const jws = require('jws');
119

1210
function signWithExpiresIn(expiresIn, payload, callback) {
13-
const options = {algorithm: 'none'};
11+
const options = {algorithm: 'HS256'};
1412
if (expiresIn !== undefined) {
1513
options.expiresIn = expiresIn;
1614
}
@@ -49,7 +47,7 @@ describe('expires', function() {
4947

5048
// undefined needs special treatment because {} is not the same as {expiresIn: undefined}
5149
it('should error with with value undefined', function (done) {
52-
testUtils.signJWTHelper({}, undefined, {expiresIn: undefined, algorithm: 'none'}, (err) => {
50+
testUtils.signJWTHelper({}, 'secret', {expiresIn: undefined, algorithm: 'HS256'}, (err) => {
5351
testUtils.asyncCheck(done, () => {
5452
expect(err).to.be.instanceOf(Error);
5553
expect(err).to.have.property(
@@ -133,9 +131,10 @@ describe('expires', function() {
133131
{foo: 'bar'},
134132
].forEach((exp) => {
135133
it(`should error with with value ${util.inspect(exp)}`, function (done) {
136-
const encodedPayload = base64UrlEncode(JSON.stringify({exp}));
137-
const token = `${noneAlgorithmHeader}.${encodedPayload}.`;
138-
testUtils.verifyJWTHelper(token, undefined, {exp}, (err) => {
134+
const header = { alg: 'HS256' };
135+
const payload = { exp };
136+
const token = jws.sign({ header, payload, secret: 'secret', encoding: 'utf8' });
137+
testUtils.verifyJWTHelper(token, 'secret', { exp }, (err) => {
139138
testUtils.asyncCheck(done, () => {
140139
expect(err).to.be.instanceOf(jwt.JsonWebTokenError);
141140
expect(err).to.have.property('message', 'invalid exp value');
@@ -158,7 +157,7 @@ describe('expires', function() {
158157
it('should set correct "exp" with negative number of seconds', function(done) {
159158
signWithExpiresIn(-10, {}, (e1, token) => {
160159
fakeClock.tick(-10001);
161-
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
160+
testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => {
162161
testUtils.asyncCheck(done, () => {
163162
expect(e1).to.be.null;
164163
expect(e2).to.be.null;
@@ -170,7 +169,7 @@ describe('expires', function() {
170169

171170
it('should set correct "exp" with positive number of seconds', function(done) {
172171
signWithExpiresIn(10, {}, (e1, token) => {
173-
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
172+
testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => {
174173
testUtils.asyncCheck(done, () => {
175174
expect(e1).to.be.null;
176175
expect(e2).to.be.null;
@@ -183,7 +182,7 @@ describe('expires', function() {
183182
it('should set correct "exp" with zero seconds', function(done) {
184183
signWithExpiresIn(0, {}, (e1, token) => {
185184
fakeClock.tick(-1);
186-
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
185+
testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => {
187186
testUtils.asyncCheck(done, () => {
188187
expect(e1).to.be.null;
189188
expect(e2).to.be.null;
@@ -196,7 +195,7 @@ describe('expires', function() {
196195
it('should set correct "exp" with negative string timespan', function(done) {
197196
signWithExpiresIn('-10 s', {}, (e1, token) => {
198197
fakeClock.tick(-10001);
199-
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
198+
testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => {
200199
testUtils.asyncCheck(done, () => {
201200
expect(e1).to.be.null;
202201
expect(e2).to.be.null;
@@ -209,7 +208,7 @@ describe('expires', function() {
209208
it('should set correct "exp" with positive string timespan', function(done) {
210209
signWithExpiresIn('10 s', {}, (e1, token) => {
211210
fakeClock.tick(-10001);
212-
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
211+
testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => {
213212
testUtils.asyncCheck(done, () => {
214213
expect(e1).to.be.null;
215214
expect(e2).to.be.null;
@@ -222,7 +221,7 @@ describe('expires', function() {
222221
it('should set correct "exp" with zero string timespan', function(done) {
223222
signWithExpiresIn('0 s', {}, (e1, token) => {
224223
fakeClock.tick(-1);
225-
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
224+
testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => {
226225
testUtils.asyncCheck(done, () => {
227226
expect(e1).to.be.null;
228227
expect(e2).to.be.null;
@@ -267,7 +266,7 @@ describe('expires', function() {
267266

268267
it('should set correct "exp" when "iat" is passed', function (done) {
269268
signWithExpiresIn(-10, {iat: 80}, (e1, token) => {
270-
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
269+
testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => {
271270
testUtils.asyncCheck(done, () => {
272271
expect(e1).to.be.null;
273272
expect(e2).to.be.null;
@@ -279,7 +278,7 @@ describe('expires', function() {
279278

280279
it('should verify "exp" using "clockTimestamp"', function (done) {
281280
signWithExpiresIn(10, {}, (e1, token) => {
282-
testUtils.verifyJWTHelper(token, undefined, {clockTimestamp: 69}, (e2, decoded) => {
281+
testUtils.verifyJWTHelper(token, 'secret', {clockTimestamp: 69}, (e2, decoded) => {
283282
testUtils.asyncCheck(done, () => {
284283
expect(e1).to.be.null;
285284
expect(e2).to.be.null;
@@ -293,7 +292,7 @@ describe('expires', function() {
293292
it('should verify "exp" using "clockTolerance"', function (done) {
294293
signWithExpiresIn(5, {}, (e1, token) => {
295294
fakeClock.tick(10000);
296-
testUtils.verifyJWTHelper(token, undefined, {clockTimestamp: 6}, (e2, decoded) => {
295+
testUtils.verifyJWTHelper(token, 'secret', {clockTimestamp: 6}, (e2, decoded) => {
297296
testUtils.asyncCheck(done, () => {
298297
expect(e1).to.be.null;
299298
expect(e2).to.be.null;
@@ -306,7 +305,7 @@ describe('expires', function() {
306305

307306
it('should ignore a expired token when "ignoreExpiration" is true', function (done) {
308307
signWithExpiresIn('-10 s', {}, (e1, token) => {
309-
testUtils.verifyJWTHelper(token, undefined, {ignoreExpiration: true}, (e2, decoded) => {
308+
testUtils.verifyJWTHelper(token, 'secret', {ignoreExpiration: true}, (e2, decoded) => {
310309
testUtils.asyncCheck(done, () => {
311310
expect(e1).to.be.null;
312311
expect(e2).to.be.null;
@@ -319,7 +318,7 @@ describe('expires', function() {
319318

320319
it('should error on verify if "exp" is at current time', function(done) {
321320
signWithExpiresIn(undefined, {exp: 60}, (e1, token) => {
322-
testUtils.verifyJWTHelper(token, undefined, {}, (e2) => {
321+
testUtils.verifyJWTHelper(token, 'secret', {}, (e2) => {
323322
testUtils.asyncCheck(done, () => {
324323
expect(e1).to.be.null;
325324
expect(e2).to.be.instanceOf(jwt.TokenExpiredError);
@@ -331,7 +330,7 @@ describe('expires', function() {
331330

332331
it('should error on verify if "exp" is before current time using clockTolerance', function (done) {
333332
signWithExpiresIn(-5, {}, (e1, token) => {
334-
testUtils.verifyJWTHelper(token, undefined, {clockTolerance: 5}, (e2) => {
333+
testUtils.verifyJWTHelper(token, 'secret', {clockTolerance: 5}, (e2) => {
335334
testUtils.asyncCheck(done, () => {
336335
expect(e1).to.be.null;
337336
expect(e2).to.be.instanceOf(jwt.TokenExpiredError);

test/claim-iat.test.js

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,22 @@ const expect = require('chai').expect;
55
const sinon = require('sinon');
66
const util = require('util');
77
const testUtils = require('./test-utils');
8-
9-
const base64UrlEncode = testUtils.base64UrlEncode;
10-
const noneAlgorithmHeader = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0';
8+
const jws = require('jws');
119

1210
function signWithIssueAt(issueAt, options, callback) {
1311
const payload = {};
1412
if (issueAt !== undefined) {
1513
payload.iat = issueAt;
1614
}
17-
const opts = Object.assign({algorithm: 'none'}, options);
15+
const opts = Object.assign({algorithm: 'HS256'}, options);
1816
// async calls require a truthy secret
1917
// see: https://github.com/brianloveswords/node-jws/issues/62
2018
testUtils.signJWTHelper(payload, 'secret', opts, callback);
2119
}
2220

23-
function verifyWithIssueAt(token, maxAge, options, callback) {
21+
function verifyWithIssueAt(token, maxAge, options, secret, callback) {
2422
const opts = Object.assign({maxAge}, options);
25-
testUtils.verifyJWTHelper(token, undefined, opts, callback);
23+
testUtils.verifyJWTHelper(token, secret, opts, callback);
2624
}
2725

2826
describe('issue at', function() {
@@ -50,7 +48,7 @@ describe('issue at', function() {
5048

5149
// undefined needs special treatment because {} is not the same as {iat: undefined}
5250
it('should error with iat of undefined', function (done) {
53-
testUtils.signJWTHelper({iat: undefined}, 'secret', {algorithm: 'none'}, (err) => {
51+
testUtils.signJWTHelper({iat: undefined}, 'secret', {algorithm: 'HS256'}, (err) => {
5452
testUtils.asyncCheck(done, () => {
5553
expect(err).to.be.instanceOf(Error);
5654
expect(err.message).to.equal('"iat" should be a number of seconds');
@@ -76,9 +74,10 @@ describe('issue at', function() {
7674
{foo: 'bar'},
7775
].forEach((iat) => {
7876
it(`should error with iat of ${util.inspect(iat)}`, function (done) {
79-
const encodedPayload = base64UrlEncode(JSON.stringify({iat}));
80-
const token = `${noneAlgorithmHeader}.${encodedPayload}.`;
81-
verifyWithIssueAt(token, '1 min', {}, (err) => {
77+
const header = { alg: 'HS256' };
78+
const payload = { iat };
79+
const token = jws.sign({ header, payload, secret: 'secret', encoding: 'utf8' });
80+
verifyWithIssueAt(token, '1 min', {}, 'secret', (err) => {
8281
testUtils.asyncCheck(done, () => {
8382
expect(err).to.be.instanceOf(jwt.JsonWebTokenError);
8483
expect(err.message).to.equal('iat required when maxAge is specified');
@@ -188,9 +187,9 @@ describe('issue at', function() {
188187
},
189188
].forEach((testCase) => {
190189
it(testCase.description, function (done) {
191-
const token = jwt.sign({}, 'secret', {algorithm: 'none'});
190+
const token = jwt.sign({}, 'secret', {algorithm: 'HS256'});
192191
fakeClock.tick(testCase.clockAdvance);
193-
verifyWithIssueAt(token, testCase.maxAge, testCase.options, (err, token) => {
192+
verifyWithIssueAt(token, testCase.maxAge, testCase.options, 'secret', (err, token) => {
194193
testUtils.asyncCheck(done, () => {
195194
expect(err).to.be.null;
196195
expect(token).to.be.a('object');
@@ -235,10 +234,10 @@ describe('issue at', function() {
235234
].forEach((testCase) => {
236235
it(testCase.description, function(done) {
237236
const expectedExpiresAtDate = new Date(testCase.expectedExpiresAt);
238-
const token = jwt.sign({}, 'secret', {algorithm: 'none'});
237+
const token = jwt.sign({}, 'secret', {algorithm: 'HS256'});
239238
fakeClock.tick(testCase.clockAdvance);
240239

241-
verifyWithIssueAt(token, testCase.maxAge, testCase.options, (err) => {
240+
verifyWithIssueAt(token, testCase.maxAge, testCase.options, 'secret', (err) => {
242241
testUtils.asyncCheck(done, () => {
243242
expect(err).to.be.instanceOf(jwt.JsonWebTokenError);
244243
expect(err.message).to.equal(testCase.expectedError);
@@ -252,7 +251,7 @@ describe('issue at', function() {
252251
describe('with string payload', function () {
253252
it('should not add iat to string', function (done) {
254253
const payload = 'string payload';
255-
const options = {algorithm: 'none'};
254+
const options = {algorithm: 'HS256'};
256255
testUtils.signJWTHelper(payload, 'secret', options, (err, token) => {
257256
const decoded = jwt.decode(token);
258257
testUtils.asyncCheck(done, () => {
@@ -264,7 +263,7 @@ describe('issue at', function() {
264263

265264
it('should not add iat to stringified object', function (done) {
266265
const payload = '{}';
267-
const options = {algorithm: 'none', header: {typ: 'JWT'}};
266+
const options = {algorithm: 'HS256', header: {typ: 'JWT'}};
268267
testUtils.signJWTHelper(payload, 'secret', options, (err, token) => {
269268
const decoded = jwt.decode(token);
270269
testUtils.asyncCheck(done, () => {

0 commit comments

Comments
 (0)