diff --git a/README.md b/README.md index 4e20dd9..65e4070 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,7 @@ As mentioned in [this comment](https://github.com/auth0/node-jsonwebtoken/issues > * rsa - ['RS256', 'RS384', 'RS512'] > * ec - ['ES256', 'ES384', 'ES512'] > * default - ['RS256', 'RS384', 'RS512'] +* `type`: if you want to check token type (`typ`), provide a string value here (e.g. 'JWT', 'at+jwt'). * `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. > Eg: `"urn:foo"`, `/urn:f[o]{2}/`, `[/urn:f[o]{2}/, "urn:bar"]` * `complete`: return an object with the decoded `{ payload, header, signature }` instead of only the usual content of the payload. @@ -191,6 +192,12 @@ jwt.verify(token, cert, function(err, decoded) { console.log(decoded.foo) // bar }); +// verify type +var cert = fs.readFileSync('public.pem'); // get public key +jwt.verify(token, cert, { type: 'at+jwt' }, function(err, decoded) { + // if typ mismatch, err == invalid type +}); + // verify audience var cert = fs.readFileSync('public.pem'); // get public key jwt.verify(token, cert, { audience: 'urn:foo' }, function(err, decoded) { @@ -314,6 +321,8 @@ Error object: * 'jwt issuer invalid. expected: [OPTIONS ISSUER]' * 'jwt id invalid. expected: [OPTIONS JWT ID]' * 'jwt subject invalid. expected: [OPTIONS SUBJECT]' + * 'jwt type invalid. expected: [OPTIONS TYPE]' + ```js jwt.verify(token, 'shhhhh', function(err, decoded) { diff --git a/test/option-complete.test.js b/test/option-complete.test.js index 29320e8..feab4d3 100644 --- a/test/option-complete.test.js +++ b/test/option-complete.test.js @@ -10,7 +10,7 @@ describe('complete option', function () { const secret = fs.readFileSync(path.join(__dirname, 'priv.pem')); const pub = fs.readFileSync(path.join(__dirname, 'pub.pem')); - const header = { alg: 'RS256' }; + const header = { alg: 'RS256', typ: 'JWT' }; const payload = { iat: Math.floor(Date.now() / 1000 ) }; const signed = jws.sign({ header, payload, secret, encoding: 'utf8' }); const signature = jws.decode(signed).signature; @@ -26,6 +26,7 @@ describe('complete option', function () { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded.header).to.have.property('alg', header.alg); + expect(decoded.header).to.have.property('typ', header.typ); expect(decoded.payload).to.have.property('iat', payload.iat); expect(decoded).to.have.property('signature', signature); }); diff --git a/test/verify.tests.js b/test/verify.tests.js index 8850075..64306f6 100644 --- a/test/verify.tests.js +++ b/test/verify.tests.js @@ -268,6 +268,32 @@ describe('verify', function() { }); }); + describe('option: type', function () { + // { "alg": "HS256", "typ": "JWT" } { "foo": "bar", "iat": 1437018582, "exp": 1437018592 } + const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU5Mn0.3aR3vocmgRpG05rsI9MpR6z2T_BGtMQaPq2YR6QaroU'; + let clock; + const key = 'key'; + + beforeEach(function () { + // iat + 1s + try { clock = sinon.useFakeTimers(1437018583000); } catch (e) {} + }); + + afterEach(function () { + try { clock.restore(); } catch (e) {} + }); + + it('should pass if token.header.typ is equal to options.type', function () { + const options = { type: 'JWT' }; + expect(function() { jwt.verify(token, key, options) }).to.not.throw(); + }); + + it('should error if token.header.typ is not equal to options.type', function () { + const options = { type: 'at+jwt' }; + expect(function() { jwt.verify(token, key, options) }).to.throw('jwt type invalid. expected: at+jwt'); + }); + }); + 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'; diff --git a/verify.js b/verify.js index cdbfdc4..a0fb72c 100644 --- a/verify.js +++ b/verify.js @@ -83,6 +83,16 @@ module.exports = function (jwtString, secretOrPublicKey, options, callback) { } const header = decodedToken.header; + + if (options.type) { + const invalid_type = + (typeof options.type === 'string' && header.typ !== options.type); + + if (invalid_type) { + return done(new JsonWebTokenError('jwt type invalid. expected: ' + options.type)); + } + } + let getSecret; if(typeof secretOrPublicKey === 'function') {