mirror of
https://github.com/hanzoai/node-jsonwebtoken.git
synced 2026-09-24 17:00:48 +00:00
* Check if node version supports asymmetricKeyDetails * Validate algorithms for ec key type * Rename variable * Rename function * Add early return for symmetric keys * Validate algorithm for RSA key type * Validate algorithm for RSA-PSS key type * Check key types for EdDSA algorithm * Rename function * Move validateKey function to module * Convert arrow to function notation * Validate key in verify function * Simplify if * Convert if to switch..case * Guard against empty key in validation * Remove empty line * Add lib to check modulus length * Add modulus length checks * Validate mgf1HashAlgorithm and saltLength * Check node version before using key details API * Use built-in modulus length getter * Fix Node version validations * Remove duplicate validateKey * Add periods to error messages * Fix validation in verify function * Make asymmetric key validation the latest validation step * Change key curve validation * Remove support for ES256K * Fix old test that was using wrong key types to sign tokens * Enable RSA-PSS for old Node versions * Add specific RSA-PSS validations on Node 16 LTS+ * Improve error message * Simplify key validation code * Fix typo * Improve error message * Change var to const in test * Change const to let to avoid reassigning problem * Improve error message * Test incorrect private key type * Rename invalid to unsupported * Test verifying of jwt token with unsupported key * Test invalid private key type * Change order of object parameters * Move validation test to separate file * Move all validation tests to separate file * Add prime256v1 ec key * Remove modulus length check * WIP: Add EC key validation tests * Fix node version checks * Fix error message check on test * Add successful tests for EC curve check * Remove only from describe * Remove `only` * Remove duplicate block of code * Move variable to a different scope and make it const * Convert allowed curves to object for faster lookup * Rename variable * Change variable assignment order * Remove unused object properties * Test RSA-PSS happy path and wrong length * Add missing tests * Pass validation if no algorithm has been provided * Test validation of invalid salt length * Test error when signing token with invalid key * Change var to const/let in verify tests * Test verifying token with invalid key * Improve test error messages * Add parameter to skip private key validation * Replace DSA key with a 4096 bit long key * Test allowInvalidPrivateKeys in key signing * Improve test message * Rename variable * Add key validation flag tests * Fix variable name in Readme * Change private to public dsa key in verify * Rename flag * Run EC validation tests conditionally * Fix tests in old node versions * Ignore block of code from test coverage * Separate EC validations tests into two different ones * Add comment * Wrap switch in if instead of having an early return * Remove unsupported algorithms from asymmetric key validation * Rename option to allowInvalidAsymmetricKeyTypes and improve Readme * 9.0.0 * adding migration notes to readme * adding changelog for version 9.0.0 Co-authored-by: julienwoll <[email protected]>
302 lines
11 KiB
JavaScript
302 lines
11 KiB
JavaScript
const jwt = require('../index');
|
|
const jws = require('jws');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const sinon = require('sinon');
|
|
const JsonWebTokenError = require('../lib/JsonWebTokenError');
|
|
|
|
const assert = require('chai').assert;
|
|
const expect = require('chai').expect;
|
|
|
|
describe('verify', function() {
|
|
const pub = fs.readFileSync(path.join(__dirname, 'pub.pem'));
|
|
const priv = fs.readFileSync(path.join(__dirname, 'priv.pem'));
|
|
|
|
it('should first assume JSON claim set', function (done) {
|
|
const header = { alg: 'RS256' };
|
|
const payload = { iat: Math.floor(Date.now() / 1000 ) };
|
|
|
|
const signed = jws.sign({
|
|
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();
|
|
});
|
|
});
|
|
|
|
it('should not be able to verify unsigned token', function () {
|
|
const header = { alg: 'none' };
|
|
const payload = { iat: Math.floor(Date.now() / 1000 ) };
|
|
|
|
const signed = jws.sign({
|
|
header: header,
|
|
payload: payload,
|
|
secret: 'secret',
|
|
encoding: 'utf8'
|
|
});
|
|
|
|
expect(function () {
|
|
jwt.verify(signed, 'secret', {typ: 'JWT'});
|
|
}).to.throw(JsonWebTokenError, /jwt signature is required/);
|
|
});
|
|
|
|
it('should not be able to verify unsigned token', function () {
|
|
const header = { alg: 'none' };
|
|
const payload = { iat: Math.floor(Date.now() / 1000 ) };
|
|
|
|
const signed = jws.sign({
|
|
header: header,
|
|
payload: payload,
|
|
secret: 'secret',
|
|
encoding: 'utf8'
|
|
});
|
|
|
|
expect(function () {
|
|
jwt.verify(signed, undefined, {typ: 'JWT'});
|
|
}).to.throw(JsonWebTokenError, /please specify "none" in "algorithms" to verify unsigned tokens/);
|
|
});
|
|
|
|
it('should be able to verify unsigned token when none is specified', function (done) {
|
|
const header = { alg: 'none' };
|
|
const payload = { iat: Math.floor(Date.now() / 1000 ) };
|
|
|
|
const signed = jws.sign({
|
|
header: header,
|
|
payload: payload,
|
|
secret: 'secret',
|
|
encoding: 'utf8'
|
|
});
|
|
|
|
jwt.verify(signed, null, {typ: 'JWT', algorithms: ['none']}, function(err, p) {
|
|
assert.isNull(err);
|
|
assert.deepEqual(p, payload);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should not mutate options', function (done) {
|
|
const header = { alg: 'HS256' };
|
|
const payload = { iat: Math.floor(Date.now() / 1000 ) };
|
|
const options = { typ: 'JWT' };
|
|
const signed = jws.sign({
|
|
header: header,
|
|
payload: payload,
|
|
secret: 'secret',
|
|
encoding: 'utf8'
|
|
});
|
|
|
|
jwt.verify(signed, 'secret', options, function(err) {
|
|
assert.isNull(err);
|
|
assert.deepEqual(Object.keys(options).length, 1);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('secret or token as callback', function () {
|
|
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU5Mn0.3aR3vocmgRpG05rsI9MpR6z2T_BGtMQaPq2YR6QaroU';
|
|
const key = 'key';
|
|
|
|
const payload = { foo: 'bar', iat: 1437018582, exp: 1437018592 };
|
|
const options = {algorithms: ['HS256'], ignoreExpiration: true};
|
|
|
|
it('without callback', function (done) {
|
|
jwt.verify(token, key, options, function (err, p) {
|
|
assert.isNull(err);
|
|
assert.deepEqual(p, payload);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('simple callback', function (done) {
|
|
const keyFunc = function(header, callback) {
|
|
assert.deepEqual(header, { alg: 'HS256', typ: 'JWT' });
|
|
|
|
callback(undefined, key);
|
|
};
|
|
|
|
jwt.verify(token, keyFunc, options, function (err, p) {
|
|
assert.isNull(err);
|
|
assert.deepEqual(p, payload);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should error if called synchronously', function (done) {
|
|
const keyFunc = function(header, callback) {
|
|
callback(undefined, key);
|
|
};
|
|
|
|
expect(function () {
|
|
jwt.verify(token, keyFunc, options);
|
|
}).to.throw(JsonWebTokenError, /verify must be called asynchronous if secret or public key is provided as a callback/);
|
|
|
|
done();
|
|
});
|
|
|
|
it('simple error', function (done) {
|
|
const keyFunc = function(header, callback) {
|
|
callback(new Error('key not found'));
|
|
};
|
|
|
|
jwt.verify(token, keyFunc, options, function (err, p) {
|
|
assert.equal(err.name, 'JsonWebTokenError');
|
|
assert.match(err.message, /error in secret or public key callback/);
|
|
assert.isUndefined(p);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('delayed callback', function (done) {
|
|
const keyFunc = function(header, callback) {
|
|
setTimeout(function() {
|
|
callback(undefined, key);
|
|
}, 25);
|
|
};
|
|
|
|
jwt.verify(token, keyFunc, options, function (err, p) {
|
|
assert.isNull(err);
|
|
assert.deepEqual(p, payload);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('delayed error', function (done) {
|
|
const keyFunc = function(header, callback) {
|
|
setTimeout(function() {
|
|
callback(new Error('key not found'));
|
|
}, 25);
|
|
};
|
|
|
|
jwt.verify(token, keyFunc, options, function (err, p) {
|
|
assert.equal(err.name, 'JsonWebTokenError');
|
|
assert.match(err.message, /error in secret or public key callback/);
|
|
assert.isUndefined(p);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('expiration', function () {
|
|
// { foo: 'bar', iat: 1437018582, exp: 1437018592 }
|
|
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU5Mn0.3aR3vocmgRpG05rsI9MpR6z2T_BGtMQaPq2YR6QaroU';
|
|
const key = 'key';
|
|
|
|
let clock;
|
|
afterEach(function () {
|
|
try { clock.restore(); } catch (e) {}
|
|
});
|
|
|
|
it('should error on expired token', function (done) {
|
|
clock = sinon.useFakeTimers(1437018650000); // iat + 58s, exp + 48s
|
|
const options = {algorithms: ['HS256']};
|
|
|
|
jwt.verify(token, key, options, 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), 1437018592000);
|
|
assert.isUndefined(p);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should not error on expired token within clockTolerance interval', function (done) {
|
|
clock = sinon.useFakeTimers(1437018594000); // iat + 12s, exp + 2s
|
|
const options = {algorithms: ['HS256'], clockTolerance: 5 }
|
|
|
|
jwt.verify(token, key, options, function (err, p) {
|
|
assert.isNull(err);
|
|
assert.equal(p.foo, 'bar');
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('option: clockTimestamp', function () {
|
|
const clockTimestamp = 1000000000;
|
|
it('should verify unexpired token relative to user-provided clockTimestamp', function (done) {
|
|
const token = jwt.sign({foo: 'bar', iat: clockTimestamp, exp: clockTimestamp + 1}, key);
|
|
jwt.verify(token, key, {clockTimestamp: clockTimestamp}, function (err) {
|
|
assert.isNull(err);
|
|
done();
|
|
});
|
|
});
|
|
it('should error on expired token relative to user-provided clockTimestamp', function (done) {
|
|
const 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) {
|
|
const 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();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('option: maxAge and clockTimestamp', function () {
|
|
// { foo: 'bar', iat: 1437018582, exp: 1437018800 } exp = iat + 218s
|
|
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODgwMH0.AVOsNC7TiT-XVSpCpkwB1240izzCIJ33Lp07gjnXVpA';
|
|
it('cannot be more permissive than expiration', function (done) {
|
|
const clockTimestamp = 1437018900; // iat + 318s (exp: iat + 218s)
|
|
const 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();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when verifying a token with an unsupported public key type', function () {
|
|
it('should throw an error', function() {
|
|
const token = 'eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2Njk5OTAwMDN9.YdjFWJtPg_9nccMnTfQyesWQ0UX-GsWrfCGit_HqjeIkNjoV6dkAJ8AtbnVEhA4oxwqSXx6ilMOfHEjmMlPtyyyVKkWKQHcIWYnqPbNSEv8a7Men8KhJTIWb4sf5YbhgSCpNvU_VIZjLO1Z0PzzgmEikp0vYbxZFAbCAlZCvUlcIc-kdjIRCnDJe0BBrYRxNLEJtYsf7D1yFIFIqw8-VP87yZdExA4eHsTaE84SgnL24ZK5h5UooDx-IRNd_rrMyio8kNy63grVxCWOtkXZ26iZk6v-HMsnBqxvUwR6-8wfaWrcpADkyUO1q3SNsoTdwtflbvfwgjo3uve0IvIzHMw';
|
|
const key = fs.readFileSync(path.join(__dirname, 'dsa-public.pem'));
|
|
|
|
expect(function() {
|
|
jwt.verify(token, key);
|
|
}).to.throw('Unknown key type "dsa".');
|
|
});
|
|
});
|
|
|
|
describe('when verifying a token with an incorrect public key type', function () {
|
|
it('should throw a validation error if key validation is enabled', function() {
|
|
const token = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXkiOiJsb2FkIiwiaWF0IjoxNjcwMjMwNDE2fQ.7TYP8SB_9Tw1fNIfuG60b4tvoLPpDAVBQpV1oepnuKwjUz8GOw4fRLzclo0Q2YAXisJ3zIYMEFsHpYrflfoZJQ';
|
|
const key = fs.readFileSync(path.join(__dirname, 'rsa-public.pem'));
|
|
|
|
expect(function() {
|
|
jwt.verify(token, key, { algorithms: ['ES256'] });
|
|
}).to.throw('"alg" parameter for "rsa" key type must be one of: RS256, PS256, RS384, PS384, RS512, PS512.');
|
|
});
|
|
|
|
it('should throw an unknown error if key validation is disabled', function() {
|
|
const token = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXkiOiJsb2FkIiwiaWF0IjoxNjcwMjMwNDE2fQ.7TYP8SB_9Tw1fNIfuG60b4tvoLPpDAVBQpV1oepnuKwjUz8GOw4fRLzclo0Q2YAXisJ3zIYMEFsHpYrflfoZJQ';
|
|
const key = fs.readFileSync(path.join(__dirname, 'rsa-public.pem'));
|
|
|
|
expect(function() {
|
|
jwt.verify(token, key, { algorithms: ['ES256'], allowInvalidAsymmetricKeyTypes: true });
|
|
}).to.not.throw('"alg" parameter for "rsa" key type must be one of: RS256, PS256, RS384, PS384, RS512, PS512.');
|
|
});
|
|
});
|
|
});
|