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: v8.3.0
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: v8.4.0
Choose a head ref

Commits on Jun 16, 2018

  1. Added Istanbul to check test-coverage (#468)

    * Added Istanbul to check test-coverage
    
    * node_modules_bak is generated and automatically removed by cost-of-modules and isn't needed in the ignore.
    JacoKoster authored and ziluvatar committed Jun 16, 2018
    Copy the full SHA
    9676a83 View commit details
  2. Use lolex for faking date in tests (#491)

    Sinon.JS provides a project called lolex to handle faking dates. This
    change replaces the fakeDate utility with the equivalent Sinon.JS code.
    MitMaro authored and ziluvatar committed Jun 16, 2018
    Copy the full SHA
    677ead6 View commit details

Commits on Jun 25, 2018

  1. Complete ESLint conversion and cleanup (#490)

    * Add extension to ESLint config file
    
    The .eslintrc file without an extension was deprecated a few years ago,
    so this change renames the file to add the required extension.
    
    See: eslint/eslint@c9a8883
    
    * Add ESLint to package.json
    
    This change adds ESLint as a dev-dependency and adds a lint script that
    will run ESLint.
    
    * Complete switch from JSHint to ESLint
    
    Convert all the JSHint rules to the ESLint equivalents where possible.
    The no-undef rule in ESLint caught a few cases of undefined usages in
    the tests, so they were also fixed.
    
    * Add a .eslintignore file
    
    The HTML coverage report is currently being linted, which causes a lot
    if invalid linting errors. This change adds a ignore file to ensure these
    files are properly skipped during linting.
    MitMaro authored and ziluvatar committed Jun 25, 2018
    Copy the full SHA
    cb1d2e1 View commit details
  2. Make code-coverage mandatory when running tests (#495)

    * Made code-coverage mandatory when running the tests.
    
    * Missed the trailing-comma...
    JacoKoster authored and ziluvatar committed Jun 25, 2018
    Copy the full SHA
    fb0084a View commit details

Commits on Jun 27, 2018

  1. Refactor tests related to notBefore and nbf (#497)

    This change extracts all tests in the current files related to notBefore
    and nbf into a single test file. It also adds several missing related
    tests.
    MitMaro authored and ziluvatar committed Jun 27, 2018
    Copy the full SHA
    39adf87 View commit details

Commits on Jul 6, 2018

  1. Refactor tests related to expiresIn and exp (#501)

    This change extracts all tests in the current test files related
    to expiresIn and exp into a single test file. It also adds several
    missing tests.
    MitMaro authored and ziluvatar committed Jul 6, 2018
    Copy the full SHA
    72f0d9e View commit details

Commits on Jul 12, 2018

  1. Refactor tests related to audience and aud (#503)

    This change extracts all tests in the existing test files related to
    audience and aud into a single test file. Several other tests are also
    added that were missing from the existing files.
    MitMaro authored and ziluvatar committed Jul 12, 2018
    Copy the full SHA
    53d405e View commit details

Commits on Jul 20, 2018

  1. Minor test refactoring for recently added tests (#504)

    * Prefix claim- to claim related test files
    
    * Fix typo of "signWithNoBfore" in notBefore tests
    MitMaro authored and ziluvatar committed Jul 20, 2018
    Copy the full SHA
    e2860a9 View commit details
  2. Refactor tests related to subject and sub (#505)

    This change extracts all tests in the existing files related to the
    subject option and sub claim into a single test file. Several other
    tests are also added that were missing from the existing files.
    MitMaro authored and ziluvatar committed Jul 20, 2018
    Copy the full SHA
    5a7fa23 View commit details

Commits on Aug 26, 2018

  1. Refactor tests related to iat and maxAge (#507)

    This change extracts all tests related to the iat claim and the maxAge
    verify option into two test files. Several additional tests are added
    that were missing from the existing tests.
    MitMaro authored and ziluvatar committed Aug 26, 2018
    Copy the full SHA
    877bd57 View commit details

Commits on Sep 10, 2018

  1. Update dependencies used for running tests (#518)

    Update chai and mocha to the latest versions and update nyc to the
    latest version that supports Node 4.
    MitMaro authored and ziluvatar committed Sep 10, 2018
    Copy the full SHA
    5498bdc View commit details

Commits on Oct 10, 2018

  1. Update README.md (#527)

    Kundan28 authored and ziluvatar committed Oct 10, 2018
    Copy the full SHA
    b76f2a8 View commit details
  2. Create and implement async/sync test helpers (#523)

    It is difficult to write tests that ensure that both the asynchronous
    and synchronous calls to the sign and verify functions had the same
    result. These helpers ensure that the calls are the same and return the
    common result.
    
    As a proof of concept, the iat claim tests have been updated to use the
    new helpers.
    MitMaro authored and ziluvatar committed Oct 10, 2018
    Copy the full SHA
    683d8a9 View commit details
  3. Document NotBeforeError (#529)

    bploetz authored and ziluvatar committed Oct 10, 2018
    Copy the full SHA
    29cd654 View commit details

Commits on Oct 16, 2018

  1. Implement async/sync tests for sub claim (#534)

    Refactor existing tests for the sub claim to use the async/sync test
    helpers.
    MitMaro authored and ziluvatar committed Oct 16, 2018
    Copy the full SHA
    342b07b View commit details
  2. Implement async/sync tests for exp claim (#536)

    Refactor existing tests for the exp claim to use the async/sync test
    helpers. This required fixing a case where an error was improperly
    handled in the async case.
    MitMaro authored and ziluvatar committed Oct 16, 2018
    Copy the full SHA
    9ae3f20 View commit details
  3. Implement async/sync tests for nbf claim (#537)

    Refactor the existing tests for the nbf claim to use the async/sync test
    helpers. This required fixing a case where an error was improperly
    handled for an async call.
    MitMaro authored and ziluvatar committed Oct 16, 2018
    Copy the full SHA
    88bc965 View commit details

Commits on Oct 17, 2018

  1. Implement async/sync tests for the aud claim (#535)

    Refactor existing tests for the aud claim to use the async/sync test
    helpers.
    MitMaro authored and ziluvatar committed Oct 17, 2018
    Copy the full SHA
    1c8ff5a View commit details

Commits on Oct 22, 2018

  1. Updating Node version in Engines spec in package.json (#528)

    * Updating Node version in package.json
    
    Updated Node version from 0.12 to 1.4 in package.json engines
    
    * Updated node version
    
    Updated Node version from 0.12 to 4 in package.json Engines Spec
    
    Fixes Issue #509
    aakash2602 authored and ziluvatar committed Oct 22, 2018
    Copy the full SHA
    cfd1079 View commit details
  2. Fixed error message when empty string passed as expiresIn or notBefor…

    …e option (#531)
    
    * Fixed error message when empty string passed as expiresIn or notBefore option
    
    * Moved tests to option validation block
    andrewnester authored and ziluvatar committed Oct 22, 2018
    Copy the full SHA
    7f9604a View commit details

Commits on Nov 1, 2018

  1. Update README.md (#538)

    mr-yamraj authored and ziluvatar committed Nov 1, 2018
    Copy the full SHA
    1956c40 View commit details

Commits on Nov 2, 2018

  1. Refactor tests related to iss and issuer (#543)

    This change extracts all tests related to the iss claim and the issuer
    option into a single test file. Additional tests were added that were
    missing.
    MitMaro authored and ziluvatar committed Nov 2, 2018
    Copy the full SHA
    0906a3f View commit details
  2. Refactor tests related to kid and keyid (#545)

    Thie change extracts the tests related to the kid header and the keyid
    option into a single file. Additonal tests were added that were missing.
    MitMaro authored and ziluvatar committed Nov 2, 2018
    Copy the full SHA
    8864542 View commit details
  3. Edited the README.md to make certain parts of the document for the ap…

    …i easier to read, emphasizing the examples. (#548)
    rhyuen authored and ziluvatar committed Nov 2, 2018
    Copy the full SHA
    dc89a64 View commit details
  4. devDeps: atob@2.1.2 (#539)

    gkwang authored and ziluvatar committed Nov 2, 2018
    Copy the full SHA
    0268813 View commit details

Commits on Nov 14, 2018

  1. Add verify option for nonce validation (#540)

    * Add verify option for nonce validation
    
    * Update README.md
    
    Co-Authored-By: kazuki229 <tsuzuku.k@gmail.com>
    
    * Refactor option-nonce test
    
    * Add nonce option validation
    kazuki229 authored and ziluvatar committed Nov 14, 2018
    Copy the full SHA
    e7938f0 View commit details
  2. 8.4.0

    ziluvatar committed Nov 14, 2018
    Copy the full SHA
    78ac95c View commit details
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.nyc_output/
coverage/
8 changes: 0 additions & 8 deletions .eslintrc

This file was deleted.

23 changes: 23 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"root": true,
"parserOptions": {
"ecmaVersion": 6
},
"env": {
"es6": true,
"node": true
},
"rules": {
"comma-style": "error",
"dot-notation": "error",
"indent": ["error", 2],
"no-control-regex": "error",
"no-div-regex": "error",
"no-eval": "error",
"no-implied-eval": "error",
"no-invalid-regexp": "error",
"no-trailing-spaces": "error",
"no-undef": "error",
"no-unused-vars": "error"
}
}
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
node_modules
.DS_Store
.DS_Store
.nyc_output
coverage
22 changes: 0 additions & 22 deletions .jshintrc

This file was deleted.

60 changes: 47 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# jsonwebtoken

[![Build Status](https://secure.travis-ci.org/auth0/node-jsonwebtoken.svg?branch=master)](http://travis-ci.org/auth0/node-jsonwebtoken)[![Dependency Status](https://david-dm.org/auth0/node-jsonwebtoken.svg)](https://david-dm.org/auth0/node-jsonwebtoken)
| **Build** | **Dependency** |
|-----------|---------------|
| [![Build Status](https://secure.travis-ci.org/auth0/node-jsonwebtoken.svg?branch=master)](http://travis-ci.org/auth0/node-jsonwebtoken) | [![Dependency Status](https://david-dm.org/auth0/node-jsonwebtoken.svg)](https://david-dm.org/auth0/node-jsonwebtoken) |


An implementation of [JSON Web Tokens](https://tools.ietf.org/html/rfc7519).
@@ -25,16 +27,21 @@ $ npm install jsonwebtoken

(Synchronous) Returns the JsonWebToken as string

`payload` could be an object literal, buffer or string representing valid JSON. *Please note that* `exp` or any other claim is only set if the payload is an object literal. Buffer or string payloads are not checked for JSON validity.
`payload` could be an object literal, buffer or string representing valid JSON.
> **Please _note_ that** `exp` or any other claim is only set if the payload is an object literal. Buffer or string payloads are not checked for JSON validity.
> If `payload` is not a buffer or a string, it will be coerced into a string using `JSON.stringify`.
`secretOrPrivateKey` is a string, buffer, or object containing either the secret for HMAC algorithms or the PEM
encoded private key for RSA and ECDSA. In case of a private key with passphrase an object `{ key, passphrase }` can be used (based on [crypto documentation](https://nodejs.org/api/crypto.html#crypto_sign_sign_private_key_output_format)), in this case be sure you pass the `algorithm` option.

`options`:

* `algorithm` (default: `HS256`)
* `expiresIn`: expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms). 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"`).
* `notBefore`: expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms). 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"`).
* `expiresIn`: expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms).
> 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"`).
* `notBefore`: expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms).
> 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"`).
* `audience`
* `issuer`
* `jwtid`
@@ -44,9 +51,9 @@ encoded private key for RSA and ECDSA. In case of a private key with passphrase
* `keyid`
* `mutatePayload`: if true, the sign function will modify the payload object directly. This is useful if you need a raw reference to the payload after claims have been applied to it but before it has been encoded into a token.

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

There are no default values for `expiresIn`, `notBefore`, `audience`, `subject`, `issuer`. These claims can also be provided in the payload directly with `exp`, `nbf`, `aud`, `sub` and `iss` respectively, but you can't include in both places.

> There are no default values for `expiresIn`, `notBefore`, `audience`, `subject`, `issuer`. These claims can also be provided in the payload directly with `exp`, `nbf`, `aud`, `sub` and `iss` respectively, but you **_can't_** include in both places.
Remember that `exp`, `nbf` and `iat` are **NumericDate**, see related [Token Expiration (exp claim)](#token-expiration-exp-claim)

@@ -55,14 +62,14 @@ The header can be customized via the `options.header` object.

Generated jwts will include an `iat` (issued at) claim by default unless `noTimestamp` is specified. If `iat` is inserted in the payload, it will be used instead of the real timestamp for calculating other things like `exp` given a timespan in `options.expiresIn`.

Sign with default (HMAC SHA256)
Synchronous Sign with default (HMAC SHA256)

```js
var jwt = require('jsonwebtoken');
var token = jwt.sign({ foo: 'bar' }, 'shhhhh');
```

Sign with RSA SHA256
Synchronous Sign with RSA SHA256
```js
// sign with RSA SHA256
var cert = fs.readFileSync('private.key');
@@ -129,14 +136,17 @@ As mentioned in [this comment](https://github.com/auth0/node-jsonwebtoken/issues
`options`

* `algorithms`: List of strings with the names of the allowed algorithms. For instance, `["HS256", "HS384"]`.
* `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"]`
* `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"]`
* `issuer` (optional): string or array of strings of valid values for the `iss` field.
* `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 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. It is expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms). Eg: `1000`, `"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"`).
* `maxAge`: the maximum allowed age for tokens to still be valid. It is expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms).
> Eg: `1000`, `"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"`).
* `clockTimestamp`: the time in seconds that should be used as the current time for all necessary comparisons.
* `nonce`: if you want to check `nonce` claim, provide a string value here. It is used on Open ID for the ID Tokens. ([Open ID implementation notes](https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes))


```js
@@ -221,7 +231,7 @@ jwt.verify(token, getKey, options, function(err, decoded) {

(Synchronous) Returns the decoded payload without verifying if the signature is valid.

__Warning:__ This will __not__ verify whether the signature is valid. You should __not__ use this for untrusted messages. You most likely want to use `jwt.verify` instead.
> __Warning:__ This will __not__ verify whether the signature is valid. You should __not__ use this for untrusted messages. You most likely want to use `jwt.verify` instead.
`token` is the JsonWebToken string

@@ -296,6 +306,30 @@ jwt.verify(token, 'shhhhh', function(err, decoded) {
});
```

### NotBeforeError
Thrown if current time is before the nbf claim.

Error object:

* name: 'NotBeforeError'
* message: 'jwt not active'
* date: 2018-10-04T16:10:44.000Z

```js
jwt.verify(token, 'shhhhh', function(err, decoded) {
if (err) {
/*
err = {
name: 'NotBeforeError',
message: 'jwt not active',
date: 2018-10-04T16:10:44.000Z
}
*/
}
});
```


## Algorithms supported

Array of supported algorithms. The following algorithms are currently supported.
@@ -315,9 +349,9 @@ none | No digital signature or MAC value included

## Refreshing JWTs

First of all, we recommend to think carefully if auto-refreshing a JWT will not introduce any vulnerability in your system.
First of all, we recommend you to think carefully if auto-refreshing a JWT will not introduce any vulnerability in your system.

We are not comfortable including this as part of the library, however, you can take a look to [this example](https://gist.github.com/ziluvatar/a3feb505c4c0ec37059054537b38fc48) to show how this could be accomplished.
We are not comfortable including this as part of the library, however, you can take a look at [this example](https://gist.github.com/ziluvatar/a3feb505c4c0ec37059054537b38fc48) to show how this could be accomplished.
Apart from that example there are [an issue](https://github.com/auth0/node-jsonwebtoken/issues/122) and [a pull request](https://github.com/auth0/node-jsonwebtoken/pull/172) to get more knowledge about this topic.

# TODO
33 changes: 26 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
{
"name": "jsonwebtoken",
"version": "8.3.0",
"version": "8.4.0",
"description": "JSON Web Token implementation (symmetric and asymmetric)",
"main": "index.js",
"nyc": {
"check-coverage": true,
"lines": 95,
"statements": 95,
"functions": 100,
"branches": 95,
"exclude": [
"./test/**"
],
"reporter": [
"json",
"lcov",
"text-summary"
]
},
"scripts": {
"test": "mocha --require test/util/fakeDate && nsp check && cost-of-modules"
"lint": "eslint .",
"coverage": "nyc mocha",
"test": "npm run lint && npm run coverage && nsp check && cost-of-modules"
},
"repository": {
"type": "git",
@@ -30,17 +47,19 @@
"ms": "^2.1.1"
},
"devDependencies": {
"atob": "^1.1.2",
"chai": "^1.10.0",
"atob": "^2.1.2",
"chai": "^4.1.2",
"conventional-changelog": "~1.1.0",
"cost-of-modules": "^1.0.1",
"mocha": "^2.1.0",
"eslint": "^4.19.1",
"mocha": "^5.2.0",
"nsp": "^2.6.2",
"sinon": "^1.15.4"
"nyc": "^11.9.0",
"sinon": "^6.0.0"
},
"engines": {
"npm": ">=1.4.28",
"node": ">=0.12"
"node": ">=4"
},
"files": [
"lib",
18 changes: 14 additions & 4 deletions sign.js
Original file line number Diff line number Diff line change
@@ -9,8 +9,8 @@ var isString = require('lodash.isstring');
var once = require('lodash.once');

var sign_options_schema = {
expiresIn: { isValid: function(value) { return isInteger(value) || isString(value); }, message: '"expiresIn" should be a number of seconds or string representing a timespan' },
notBefore: { isValid: function(value) { return isInteger(value) || isString(value); }, message: '"notBefore" should be a number of seconds or string representing a timespan' },
expiresIn: { isValid: function(value) { return isInteger(value) || (isString(value) && value); }, message: '"expiresIn" should be a number of seconds or string representing a timespan' },
notBefore: { isValid: function(value) { return isInteger(value) || (isString(value) && value); }, message: '"notBefore" should be a number of seconds or string representing a timespan' },
audience: { isValid: function(value) { return isString(value) || Array.isArray(value); }, message: '"audience" must be a string or array' },
algorithm: { isValid: includes.bind(null, ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'none']), message: '"algorithm" must be a valid string enum value' },
header: { isValid: isPlainObject, message: '"header" must be an object' },
@@ -147,14 +147,24 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) {
}

if (typeof options.notBefore !== 'undefined') {
payload.nbf = timespan(options.notBefore, timestamp);
try {
payload.nbf = timespan(options.notBefore, timestamp);
}
catch (err) {
return failure(err);
}
if (typeof payload.nbf === 'undefined') {
return failure(new Error('"notBefore" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'));
}
}

if (typeof options.expiresIn !== 'undefined' && typeof payload === 'object') {
payload.exp = timespan(options.expiresIn, timestamp);
try {
payload.exp = timespan(options.expiresIn, timestamp);
}
catch (err) {
return failure(err);
}
if (typeof payload.exp === 'undefined') {
return failure(new Error('"expiresIn" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'));
}
5 changes: 5 additions & 0 deletions test/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"env": {
"mocha": true
}
}
16 changes: 8 additions & 8 deletions test/async_sign.tests.js
Original file line number Diff line number Diff line change
@@ -19,15 +19,15 @@ describe('signing a token asynchronously', function() {
});

it('should work with empty options', function (done) {
jwt.sign({abc: 1}, "secret", {}, function (err, res) {
expect(err).to.be.null();
jwt.sign({abc: 1}, "secret", {}, function (err) {
expect(err).to.be.null;
done();
});
});

it('should work without options object at all', function (done) {
jwt.sign({abc: 1}, "secret", function (err, res) {
expect(err).to.be.null();
jwt.sign({abc: 1}, "secret", function (err) {
expect(err).to.be.null;
done();
});
});
@@ -53,22 +53,22 @@ describe('signing a token asynchronously', function() {
it('should return error when secret is not a cert for RS256', function(done) {
//this throw an error because the secret is not a cert and RS256 requires a cert.
jwt.sign({ foo: 'bar' }, secret, { algorithm: 'RS256' }, function (err) {
expect(err).to.be.ok();
expect(err).to.be.ok;
done();
});
});

it('should return error on wrong arguments', function(done) {
//this throw an error because the secret is not a cert and RS256 requires a cert.
jwt.sign({ foo: 'bar' }, secret, { notBefore: {} }, function (err) {
expect(err).to.be.ok();
expect(err).to.be.ok;
done();
});
});

it('should return error on wrong arguments (2)', function(done) {
jwt.sign('string', 'secret', {noTimestamp: true}, function (err) {
expect(err).to.be.ok();
expect(err).to.be.ok;
expect(err).to.be.instanceof(Error);
done();
});
@@ -111,7 +111,7 @@ describe('signing a token asynchronously', function() {
it('should return an error if the secret is falsy and algorithm is not set to none: ' + (typeof secret === 'string' ? '(empty string)' : secret), function(done) {
// This is needed since jws will not answer for falsy secrets
jwt.sign('string', secret, {}, function(err, token) {
expect(err).to.be.exist();
expect(err).to.exist;
expect(err.message).to.equal('secretOrPrivateKey must have a value');
expect(token).to.not.exist;
done();
436 changes: 436 additions & 0 deletions test/claim-aud.test.js

Large diffs are not rendered by default.

344 changes: 344 additions & 0 deletions test/claim-exp.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,344 @@
'use strict';

const jwt = require('../');
const expect = require('chai').expect;
const sinon = require('sinon');
const util = require('util');
const testUtils = require('./test-utils');

const base64UrlEncode = testUtils.base64UrlEncode;
const noneAlgorithmHeader = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0';

function signWithExpiresIn(expiresIn, payload, callback) {
const options = {algorithm: 'none'};
if (expiresIn !== undefined) {
options.expiresIn = expiresIn;
}
testUtils.signJWTHelper(payload, 'secret', options, callback);
}

describe('expires', function() {
describe('`jwt.sign` "expiresIn" option validation', function () {
[
true,
false,
null,
-1.1,
1.1,
-Infinity,
Infinity,
NaN,
' ',
'',
'invalid',
[],
['foo'],
{},
{foo: 'bar'},
].forEach((expiresIn) => {
it(`should error with with value ${util.inspect(expiresIn)}`, function (done) {
signWithExpiresIn(expiresIn, {}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property('message')
.match(/"expiresIn" should be a number of seconds or string representing a timespan/);
});
});
});
});

// undefined needs special treatment because {} is not the same as {expiresIn: undefined}
it('should error with with value undefined', function (done) {
testUtils.signJWTHelper({}, undefined, {expiresIn: undefined, algorithm: 'none'}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property(
'message',
'"expiresIn" should be a number of seconds or string representing a timespan'
);
});
});
});

it ('should error when "exp" is in payload', function(done) {
signWithExpiresIn(100, {exp: 100}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property(
'message',
'Bad "options.expiresIn" option the payload already has an "exp" property.'
);
});
});
});

it('should error with a string payload', function(done) {
signWithExpiresIn(100, 'a string payload', (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property('message', 'invalid expiresIn option for string payload');
});
});
});

it('should error with a Buffer payload', function(done) {
signWithExpiresIn(100, Buffer.from('a Buffer payload'), (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property('message', 'invalid expiresIn option for object payload');
});
});
});
});

describe('`jwt.sign` "exp" claim validation', function () {
[
true,
false,
null,
undefined,
'',
' ',
'invalid',
[],
['foo'],
{},
{foo: 'bar'},
].forEach((exp) => {
it(`should error with with value ${util.inspect(exp)}`, function (done) {
signWithExpiresIn(undefined, {exp}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property('message', '"exp" should be a number of seconds');
});
});
});
});
});

describe('"exp" in payload validation', function () {
[
true,
false,
null,
-Infinity,
Infinity,
NaN,
'',
' ',
'invalid',
[],
['foo'],
{},
{foo: 'bar'},
].forEach((exp) => {
it(`should error with with value ${util.inspect(exp)}`, function (done) {
const encodedPayload = base64UrlEncode(JSON.stringify({exp}));
const token = `${noneAlgorithmHeader}.${encodedPayload}.`;
testUtils.verifyJWTHelper(token, undefined, {exp}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(jwt.JsonWebTokenError);
expect(err).to.have.property('message', 'invalid exp value');
});
});
});
})
});

describe('when signing and verifying a token with expires option', function () {
let fakeClock;
beforeEach(function() {
fakeClock = sinon.useFakeTimers({now: 60000});
});

afterEach(function() {
fakeClock.uninstall();
});

it('should set correct "exp" with negative number of seconds', function(done) {
signWithExpiresIn(-10, {}, (e1, token) => {
fakeClock.tick(-10001);
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('exp', 50);
});
})
});
});

it('should set correct "exp" with positive number of seconds', function(done) {
signWithExpiresIn(10, {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('exp', 70);
});
})
});
});

it('should set correct "exp" with zero seconds', function(done) {
signWithExpiresIn(0, {}, (e1, token) => {
fakeClock.tick(-1);
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('exp', 60);
});
})
});
});

it('should set correct "exp" with negative string timespan', function(done) {
signWithExpiresIn('-10 s', {}, (e1, token) => {
fakeClock.tick(-10001);
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('exp', 50);
});
})
});
});

it('should set correct "exp" with positive string timespan', function(done) {
signWithExpiresIn('10 s', {}, (e1, token) => {
fakeClock.tick(-10001);
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('exp', 70);
});
})
});
});

it('should set correct "exp" with zero string timespan', function(done) {
signWithExpiresIn('0 s', {}, (e1, token) => {
fakeClock.tick(-1);
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('exp', 60);
});
})
});
});

// TODO an exp of -Infinity should fail validation
it('should set null "exp" when given -Infinity', function (done) {
signWithExpiresIn(undefined, {exp: -Infinity}, (err, token) => {
const decoded = jwt.decode(token);
testUtils.asyncCheck(done, () => {
expect(err).to.be.null;
expect(decoded).to.have.property('exp', null);
});
});
});

// TODO an exp of Infinity should fail validation
it('should set null "exp" when given value Infinity', function (done) {
signWithExpiresIn(undefined, {exp: Infinity}, (err, token) => {
const decoded = jwt.decode(token);
testUtils.asyncCheck(done, () => {
expect(err).to.be.null;
expect(decoded).to.have.property('exp', null);
});
});
});

// TODO an exp of NaN should fail validation
it('should set null "exp" when given value NaN', function (done) {
signWithExpiresIn(undefined, {exp: NaN}, (err, token) => {
const decoded = jwt.decode(token);
testUtils.asyncCheck(done, () => {
expect(err).to.be.null;
expect(decoded).to.have.property('exp', null);
});
});
});

it('should set correct "exp" when "iat" is passed', function (done) {
signWithExpiresIn(-10, {iat: 80}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('exp', 70);
});
})
});
});

it('should verify "exp" using "clockTimestamp"', function (done) {
signWithExpiresIn(10, {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {clockTimestamp: 69}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('iat', 60);
expect(decoded).to.have.property('exp', 70);
});
})
});
});

it('should verify "exp" using "clockTolerance"', function (done) {
signWithExpiresIn(5, {}, (e1, token) => {
fakeClock.tick(10000);
testUtils.verifyJWTHelper(token, undefined, {clockTimestamp: 6}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('iat', 60);
expect(decoded).to.have.property('exp', 65);
});
})
});
});

it('should ignore a expired token when "ignoreExpiration" is true', function (done) {
signWithExpiresIn('-10 s', {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {ignoreExpiration: true}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('iat', 60);
expect(decoded).to.have.property('exp', 50);
});
})
});
});

it('should error on verify if "exp" is at current time', function(done) {
signWithExpiresIn(undefined, {exp: 60}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {}, (e2) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.instanceOf(jwt.TokenExpiredError);
expect(e2).to.have.property('message', 'jwt expired');
});
});
});
});

it('should error on verify if "exp" is before current time using clockTolerance', function (done) {
signWithExpiresIn(-5, {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {clockTolerance: 5}, (e2) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.instanceOf(jwt.TokenExpiredError);
expect(e2).to.have.property('message', 'jwt expired');
});
});
});
});
});
});
251 changes: 251 additions & 0 deletions test/claim-iat.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
'use strict';

const jwt = require('../');
const expect = require('chai').expect;
const sinon = require('sinon');
const util = require('util');
const testUtils = require('./test-utils');

const base64UrlEncode = testUtils.base64UrlEncode;
const noneAlgorithmHeader = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0';

function signWithIssueAt(issueAt, options, callback) {
const payload = {};
if (issueAt !== undefined) {
payload.iat = issueAt;
}
const opts = Object.assign({algorithm: 'none'}, options);
// async calls require a truthy secret
// see: https://github.com/brianloveswords/node-jws/issues/62
testUtils.signJWTHelper(payload, 'secret', opts, callback);
}

function verifyWithIssueAt(token, maxAge, options, callback) {
const opts = Object.assign({maxAge}, options);
testUtils.verifyJWTHelper(token, undefined, opts, callback);
}

describe('issue at', function() {
describe('`jwt.sign` "iat" claim validation', function () {
[
true,
false,
null,
'',
'invalid',
[],
['foo'],
{},
{foo: 'bar'},
].forEach((iat) => {
it(`should error with iat of ${util.inspect(iat)}`, function (done) {
signWithIssueAt(iat, {}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err.message).to.equal('"iat" should be a number of seconds');
});
});
});
});

// undefined needs special treatment because {} is not the same as {iat: undefined}
it('should error with iat of undefined', function (done) {
testUtils.signJWTHelper({iat: undefined}, 'secret', {algorithm: 'none'}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err.message).to.equal('"iat" should be a number of seconds');
});
});
});
});

describe('"iat" in payload with "maxAge" option validation', function () {
[
true,
false,
null,
undefined,
-Infinity,
Infinity,
NaN,
'',
'invalid',
[],
['foo'],
{},
{foo: 'bar'},
].forEach((iat) => {
it(`should error with iat of ${util.inspect(iat)}`, function (done) {
const encodedPayload = base64UrlEncode(JSON.stringify({iat}));
const token = `${noneAlgorithmHeader}.${encodedPayload}.`;
verifyWithIssueAt(token, '1 min', {}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(jwt.JsonWebTokenError);
expect(err.message).to.equal('iat required when maxAge is specified');
});
});
});
})
});

describe('when signing a token', function () {
let fakeClock;
beforeEach(function () {
fakeClock = sinon.useFakeTimers({now: 60000});
});

afterEach(function () {
fakeClock.uninstall();
});

[
{
description: 'should default to current time for "iat"',
iat: undefined,
expectedIssueAt: 60,
options: {}
},
{
description: 'should sign with provided time for "iat"',
iat: 100,
expectedIssueAt: 100,
options: {}
},
// TODO an iat of -Infinity should fail validation
{
description: 'should set null "iat" when given -Infinity',
iat: -Infinity,
expectedIssueAt: null,
options: {}
},
// TODO an iat of Infinity should fail validation
{
description: 'should set null "iat" when given Infinity',
iat: Infinity,
expectedIssueAt: null,
options: {}
},
// TODO an iat of NaN should fail validation
{
description: 'should set to current time for "iat" when given value NaN',
iat: NaN,
expectedIssueAt: 60,
options: {}
},
{
description: 'should remove default "iat" with "noTimestamp" option',
iat: undefined,
expectedIssueAt: undefined,
options: {noTimestamp: true}
},
{
description: 'should remove provided "iat" with "noTimestamp" option',
iat: 10,
expectedIssueAt: undefined,
options: {noTimestamp: true}
},
].forEach((testCase) => {
it(testCase.description, function (done) {
signWithIssueAt(testCase.iat, testCase.options, (err, token) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.null;
expect(jwt.decode(token).iat).to.equal(testCase.expectedIssueAt);
});
});
});
});
});

describe('when verifying a token', function() {
let fakeClock;

beforeEach(function() {
fakeClock = sinon.useFakeTimers({now: 60000});
});

afterEach(function () {
fakeClock.uninstall();
});

[
{
description: 'should verify using "iat" before the "maxAge"',
clockAdvance: 10000,
maxAge: 11,
options: {},
},
{
description: 'should verify using "iat" before the "maxAge" with a provided "clockTimestamp',
clockAdvance: 60000,
maxAge: 11,
options: {clockTimestamp: 70},
},
{
description: 'should verify using "iat" after the "maxAge" but within "clockTolerance"',
clockAdvance: 10000,
maxAge: 9,
options: {clockTimestamp: 2},
},
].forEach((testCase) => {
it(testCase.description, function (done) {
const token = jwt.sign({}, 'secret', {algorithm: 'none'});
fakeClock.tick(testCase.clockAdvance);
verifyWithIssueAt(token, testCase.maxAge, testCase.options, (err, token) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.null;
expect(token).to.be.a('object');
});
});
});
});

[
{
description: 'should throw using "iat" equal to the "maxAge"',
clockAdvance: 10000,
maxAge: 10,
options: {},
expectedError: 'maxAge exceeded',
expectedExpiresAt: 70000,
},
{
description: 'should throw using "iat" after the "maxAge"',
clockAdvance: 10000,
maxAge: 9,
options: {},
expectedError: 'maxAge exceeded',
expectedExpiresAt: 69000,
},
{
description: 'should throw using "iat" after the "maxAge" with a provided "clockTimestamp',
clockAdvance: 60000,
maxAge: 10,
options: {clockTimestamp: 70},
expectedError: 'maxAge exceeded',
expectedExpiresAt: 70000,
},
{
description: 'should throw using "iat" after the "maxAge" and "clockTolerance',
clockAdvance: 10000,
maxAge: 8,
options: {clockTolerance: 2},
expectedError: 'maxAge exceeded',
expectedExpiresAt: 68000,
},
].forEach((testCase) => {
it(testCase.description, function(done) {
const expectedExpiresAtDate = new Date(testCase.expectedExpiresAt);
const token = jwt.sign({}, 'secret', {algorithm: 'none'});
fakeClock.tick(testCase.clockAdvance);

verifyWithIssueAt(token, testCase.maxAge, testCase.options, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(jwt.JsonWebTokenError);
expect(err.message).to.equal(testCase.expectedError);
expect(err.expiredAt).to.deep.equal(expectedExpiresAtDate);
});
});
});
});
});
});
205 changes: 205 additions & 0 deletions test/claim-iss.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
'use strict';

const jwt = require('../');
const expect = require('chai').expect;
const util = require('util');
const testUtils = require('./test-utils');

function signWithIssuer(issuer, payload, callback) {
const options = {algorithm: 'none'};
if (issuer !== undefined) {
options.issuer = issuer;
}
testUtils.signJWTHelper(payload, 'secret', options, callback);
}

describe('issuer', function() {
describe('`jwt.sign` "issuer" option validation', function () {
[
true,
false,
null,
-1,
0,
1,
-1.1,
1.1,
-Infinity,
Infinity,
NaN,
[],
['foo'],
{},
{foo: 'bar'},
].forEach((issuer) => {
it(`should error with with value ${util.inspect(issuer)}`, function (done) {
signWithIssuer(issuer, {}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property('message', '"issuer" must be a string');
});
});
});
});

// undefined needs special treatment because {} is not the same as {issuer: undefined}
it('should error with with value undefined', function (done) {
testUtils.signJWTHelper({}, undefined, {issuer: undefined, algorithm: 'none'}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property('message', '"issuer" must be a string');
});
});
});

it('should error when "iss" is in payload', function (done) {
signWithIssuer('foo', {iss: 'bar'}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property(
'message',
'Bad "options.issuer" option. The payload already has an "iss" property.'
);
});
});
});

it('should error with a string payload', function (done) {
signWithIssuer('foo', 'a string payload', (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property(
'message',
'invalid issuer option for string payload'
);
});
});
});

it('should error with a Buffer payload', function (done) {
signWithIssuer('foo', new Buffer('a Buffer payload'), (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property(
'message',
'invalid issuer option for object payload'
);
});
});
});
});

describe('when signing and verifying a token', function () {
it('should not verify "iss" if verify "issuer" option not provided', function(done) {
signWithIssuer(undefined, {iss: 'foo'}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('iss', 'foo');
});
})
});
});

describe('with string "issuer" option', function () {
it('should verify with a string "issuer"', function (done) {
signWithIssuer('foo', {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {issuer: 'foo'}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('iss', 'foo');
});
})
});
});

it('should verify with a string "iss"', function (done) {
signWithIssuer(undefined, {iss: 'foo'}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {issuer: 'foo'}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('iss', 'foo');
});
})
});
});

it('should error if "iss" does not match verify "issuer" option', function(done) {
signWithIssuer(undefined, {iss: 'foobar'}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {issuer: 'foo'}, (e2) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.instanceOf(jwt.JsonWebTokenError);
expect(e2).to.have.property('message', 'jwt issuer invalid. expected: foo');
});
})
});
});

it('should error without "iss" and with verify "issuer" option', function(done) {
signWithIssuer(undefined, {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {issuer: 'foo'}, (e2) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.instanceOf(jwt.JsonWebTokenError);
expect(e2).to.have.property('message', 'jwt issuer invalid. expected: foo');
});
})
});
});
});

describe('with array "issuer" option', function () {
it('should verify with a string "issuer"', function (done) {
signWithIssuer('bar', {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {issuer: ['foo', 'bar']}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('iss', 'bar');
});
})
});
});

it('should verify with a string "iss"', function (done) {
signWithIssuer(undefined, {iss: 'foo'}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {issuer: ['foo', 'bar']}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('iss', 'foo');
});
})
});
});

it('should error if "iss" does not match verify "issuer" option', function(done) {
signWithIssuer(undefined, {iss: 'foobar'}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {issuer: ['foo', 'bar']}, (e2) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.instanceOf(jwt.JsonWebTokenError);
expect(e2).to.have.property('message', 'jwt issuer invalid. expected: foo,bar');
});
})
});
});

it('should error without "iss" and with verify "issuer" option', function(done) {
signWithIssuer(undefined, {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {issuer: ['foo', 'bar']}, (e2) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.instanceOf(jwt.JsonWebTokenError);
expect(e2).to.have.property('message', 'jwt issuer invalid. expected: foo,bar');
});
})
});
});
});
});
});
340 changes: 340 additions & 0 deletions test/claim-nbf.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,340 @@
'use strict';

const jwt = require('../');
const expect = require('chai').expect;
const sinon = require('sinon');
const util = require('util');
const testUtils = require('./test-utils');

const base64UrlEncode = testUtils.base64UrlEncode;
const noneAlgorithmHeader = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0';

function signWithNotBefore(notBefore, payload, callback) {
const options = {algorithm: 'none'};
if (notBefore !== undefined) {
options.notBefore = notBefore;
}
testUtils.signJWTHelper(payload, 'secret', options, callback);
}

describe('not before', function() {
describe('`jwt.sign` "notBefore" option validation', function () {
[
true,
false,
null,
-1.1,
1.1,
-Infinity,
Infinity,
NaN,
'',
' ',
'invalid',
[],
['foo'],
{},
{foo: 'bar'},
].forEach((notBefore) => {
it(`should error with with value ${util.inspect(notBefore)}`, function (done) {
signWithNotBefore(notBefore, {}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property('message')
.match(/"notBefore" should be a number of seconds or string representing a timespan/);
});
});
});
});

// undefined needs special treatment because {} is not the same as {notBefore: undefined}
it('should error with with value undefined', function (done) {
testUtils.signJWTHelper({}, undefined, {notBefore: undefined, algorithm: 'none'}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property(
'message',
'"notBefore" should be a number of seconds or string representing a timespan'
);
});
});
});

it('should error when "nbf" is in payload', function (done) {
signWithNotBefore(100, {nbf: 100}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property(
'message',
'Bad "options.notBefore" option the payload already has an "nbf" property.'
);
});
});
});

it('should error with a string payload', function (done) {
signWithNotBefore(100, 'a string payload', (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property('message', 'invalid notBefore option for string payload');
});
});
});

it('should error with a Buffer payload', function (done) {
signWithNotBefore(100, new Buffer('a Buffer payload'), (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property('message', 'invalid notBefore option for object payload');
});
});
});
});

describe('`jwt.sign` "nbf" claim validation', function () {
[
true,
false,
null,
undefined,
'',
' ',
'invalid',
[],
['foo'],
{},
{foo: 'bar'},
].forEach((nbf) => {
it(`should error with with value ${util.inspect(nbf)}`, function (done) {
signWithNotBefore(undefined, {nbf}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property('message', '"nbf" should be a number of seconds');
});
});
});
});
});

describe('"nbf" in payload validation', function () {
[
true,
false,
null,
-Infinity,
Infinity,
NaN,
'',
' ',
'invalid',
[],
['foo'],
{},
{foo: 'bar'},
].forEach((nbf) => {
it(`should error with with value ${util.inspect(nbf)}`, function (done) {
const encodedPayload = base64UrlEncode(JSON.stringify({nbf}));
const token = `${noneAlgorithmHeader}.${encodedPayload}.`;
testUtils.verifyJWTHelper(token, undefined, {nbf}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(jwt.JsonWebTokenError);
expect(err).to.have.property('message', 'invalid nbf value');
});
});
});
})
});

describe('when signing and verifying a token with "notBefore" option', function () {
let fakeClock;
beforeEach(function () {
fakeClock = sinon.useFakeTimers({now: 60000});
});

afterEach(function () {
fakeClock.uninstall();
});

it('should set correct "nbf" with negative number of seconds', function (done) {
signWithNotBefore(-10, {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('nbf', 50);
});
})
});
});

it('should set correct "nbf" with positive number of seconds', function (done) {
signWithNotBefore(10, {}, (e1, token) => {
fakeClock.tick(10000);
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('nbf', 70);
});
})
});
});

it('should set correct "nbf" with zero seconds', function (done) {
signWithNotBefore(0, {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('nbf', 60);
});
})
});
});

it('should set correct "nbf" with negative string timespan', function (done) {
signWithNotBefore('-10 s', {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('nbf', 50);
});
})
});
});

it('should set correct "nbf" with positive string timespan', function (done) {
signWithNotBefore('10 s', {}, (e1, token) => {
fakeClock.tick(10000);
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('nbf', 70);
});
})
});
});

it('should set correct "nbf" with zero string timespan', function (done) {
signWithNotBefore('0 s', {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('nbf', 60);
});
})
});
});

// TODO an nbf of -Infinity should fail validation
it('should set null "nbf" when given -Infinity', function (done) {
signWithNotBefore(undefined, {nbf: -Infinity}, (err, token) => {
const decoded = jwt.decode(token);
testUtils.asyncCheck(done, () => {
expect(err).to.be.null;
expect(decoded).to.have.property('nbf', null);
});
});
});

// TODO an nbf of Infinity should fail validation
it('should set null "nbf" when given value Infinity', function (done) {
signWithNotBefore(undefined, {nbf: Infinity}, (err, token) => {
const decoded = jwt.decode(token);
testUtils.asyncCheck(done, () => {
expect(err).to.be.null;
expect(decoded).to.have.property('nbf', null);
});
});
});

// TODO an nbf of NaN should fail validation
it('should set null "nbf" when given value NaN', function (done) {
signWithNotBefore(undefined, {nbf: NaN}, (err, token) => {
const decoded = jwt.decode(token);
testUtils.asyncCheck(done, () => {
expect(err).to.be.null;
expect(decoded).to.have.property('nbf', null);
});
});
});

it('should set correct "nbf" when "iat" is passed', function (done) {
signWithNotBefore(-10, {iat: 40}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('nbf', 30);
});
})
});
});

it('should verify "nbf" using "clockTimestamp"', function (done) {
signWithNotBefore(10, {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {clockTimestamp: 70}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('iat', 60);
expect(decoded).to.have.property('nbf', 70);
});
})
});
});

it('should verify "nbf" using "clockTolerance"', function (done) {
signWithNotBefore(5, {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {clockTolerance: 6}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('iat', 60);
expect(decoded).to.have.property('nbf', 65);
});
})
});
});

it('should ignore a not active token when "ignoreNotBefore" is true', function (done) {
signWithNotBefore('10 s', {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {ignoreNotBefore: true}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('iat', 60);
expect(decoded).to.have.property('nbf', 70);
});
})
});
});

it('should error on verify if "nbf" is after current time', function (done) {
signWithNotBefore(undefined, {nbf: 61}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {}, (e2) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.instanceOf(jwt.NotBeforeError);
expect(e2).to.have.property('message', 'jwt not active');
});
})
});
});

it('should error on verify if "nbf" is after current time using clockTolerance', function (done) {
signWithNotBefore(5, {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {clockTolerance: 4}, (e2) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.instanceOf(jwt.NotBeforeError);
expect(e2).to.have.property('message', 'jwt not active');
});
})
});
});
});
});
153 changes: 153 additions & 0 deletions test/claim-sub.tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
'use strict';

const jwt = require('../');
const expect = require('chai').expect;
const util = require('util');
const testUtils = require('./test-utils');

function signWithSubject(subject, payload, callback) {
const options = {algorithm: 'none'};
if (subject !== undefined) {
options.subject = subject;
}
testUtils.signJWTHelper(payload, 'secret', options, callback);
}

describe('subject', function() {
describe('`jwt.sign` "subject" option validation', function () {
[
true,
false,
null,
-1,
0,
1,
-1.1,
1.1,
-Infinity,
Infinity,
NaN,
[],
['foo'],
{},
{foo: 'bar'},
].forEach((subject) => {
it(`should error with with value ${util.inspect(subject)}`, function (done) {
signWithSubject(subject, {}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property('message', '"subject" must be a string');
});
});
});
});

// undefined needs special treatment because {} is not the same as {subject: undefined}
it('should error with with value undefined', function (done) {
testUtils.signJWTHelper({}, undefined, {subject: undefined, algorithm: 'none'}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property('message', '"subject" must be a string');
});
});
});

it('should error when "sub" is in payload', function (done) {
signWithSubject('foo', {sub: 'bar'}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property(
'message',
'Bad "options.subject" option. The payload already has an "sub" property.'
);
});
});
});

it('should error with a string payload', function (done) {
signWithSubject('foo', 'a string payload', (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property(
'message',
'invalid subject option for string payload'
);
});
});
});

it('should error with a Buffer payload', function (done) {
signWithSubject('foo', new Buffer('a Buffer payload'), (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property(
'message',
'invalid subject option for object payload'
);
});
});
});
});

describe('when signing and verifying a token with "subject" option', function () {
it('should verify with a string "subject"', function (done) {
signWithSubject('foo', {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {subject: 'foo'}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('sub', 'foo');
});
})
});
});

it('should verify with a string "sub"', function (done) {
signWithSubject(undefined, {sub: 'foo'}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {subject: 'foo'}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('sub', 'foo');
});
})
});
});

it('should not verify "sub" if verify "subject" option not provided', function(done) {
signWithSubject(undefined, {sub: 'foo'}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {}, (e2, decoded) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.null;
expect(decoded).to.have.property('sub', 'foo');
});
})
});
});

it('should error if "sub" does not match verify "subject" option', function(done) {
signWithSubject(undefined, {sub: 'foo'}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {subject: 'bar'}, (e2) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.instanceOf(jwt.JsonWebTokenError);
expect(e2).to.have.property('message', 'jwt subject invalid. expected: bar');
});
})
});
});

it('should error without "sub" and with verify "subject" option', function(done) {
signWithSubject(undefined, {}, (e1, token) => {
testUtils.verifyJWTHelper(token, undefined, {subject: 'foo'}, (e2) => {
testUtils.asyncCheck(done, () => {
expect(e1).to.be.null;
expect(e2).to.be.instanceOf(jwt.JsonWebTokenError);
expect(e2).to.have.property('message', 'jwt subject invalid. expected: foo');
});
})
});
});
});
});
1 change: 0 additions & 1 deletion test/decoding.tests.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
var jwt = require('../index');
var expect = require('chai').expect;
var atob = require('atob');

describe('decoding', function() {

41 changes: 0 additions & 41 deletions test/expires_format.tests.js
Original file line number Diff line number Diff line change
@@ -3,51 +3,10 @@ var expect = require('chai').expect;

describe('expires option', function() {

it('should work with a number of seconds', function () {
var token = jwt.sign({foo: 123}, '123', { expiresIn: 10 });
var result = jwt.verify(token, '123');
expect(result.exp).to.be.closeTo(Math.floor(Date.now() / 1000) + 10, 0.2);
});

it('should work with a string', function () {
var token = jwt.sign({foo: 123}, '123', { expiresIn: '2d' });
var result = jwt.verify(token, '123');
var two_days_in_secs = 2 * 24 * 60 * 60;
expect(result.exp).to.be.closeTo(Math.floor(Date.now() / 1000) + two_days_in_secs, 0.2);
});

it('should work with a string second example', function () {
var token = jwt.sign({foo: 123}, '123', { expiresIn: '36h' });
var result = jwt.verify(token, '123');
var day_and_a_half_in_secs = 1.5 * 24 * 60 * 60;
expect(result.exp).to.be.closeTo(Math.floor(Date.now() / 1000) + day_and_a_half_in_secs, 0.2);
});


it('should throw if expires has a bad string format', function () {
expect(function () {
jwt.sign({foo: 123}, '123', { expiresIn: '1 monkey' });
}).to.throw(/"expiresIn" should be a number of seconds or string representing a timespan/);
});

it('should throw if expires is not an string or number', function () {
expect(function () {
jwt.sign({foo: 123}, '123', { expiresIn: { crazy : 213 } });
}).to.throw(/"expiresIn" should be a number of seconds or string representing a timespan/);
});

it('should throw an error if expiresIn and exp are provided', function () {
expect(function () {
jwt.sign({ foo: 123, exp: 839218392183 }, '123', { expiresIn: '5h' });
}).to.throw(/Bad "options.expiresIn" option the payload already has an "exp" property./);
});


it('should throw on deprecated expiresInSeconds option', function () {
expect(function () {
jwt.sign({foo: 123}, '123', { expiresInSeconds: 5 });
}).to.throw('"expiresInSeconds" is not allowed');
});


});
97 changes: 97 additions & 0 deletions test/header-kid.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
'use strict';

const jwt = require('../');
const expect = require('chai').expect;
const util = require('util');
const testUtils = require('./test-utils');

function signWithKeyId(keyid, payload, callback) {
const options = {algorithm: 'none'};
if (keyid !== undefined) {
options.keyid = keyid;
}
testUtils.signJWTHelper(payload, 'secret', options, callback);
}

describe('keyid', function() {
describe('`jwt.sign` "keyid" option validation', function () {
[
true,
false,
null,
-1,
0,
1,
-1.1,
1.1,
-Infinity,
Infinity,
NaN,
[],
['foo'],
{},
{foo: 'bar'},
].forEach((keyid) => {
it(`should error with with value ${util.inspect(keyid)}`, function (done) {
signWithKeyId(keyid, {}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property('message', '"keyid" must be a string');
});
});
});
});

// undefined needs special treatment because {} is not the same as {keyid: undefined}
it('should error with with value undefined', function (done) {
testUtils.signJWTHelper({}, undefined, {keyid: undefined, algorithm: 'none'}, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(Error);
expect(err).to.have.property('message', '"keyid" must be a string');
});
});
});
});

describe('when signing a token', function () {
it('should not add "kid" header when "keyid" option not provided', function(done) {
signWithKeyId(undefined, {}, (err, token) => {
testUtils.asyncCheck(done, () => {
const decoded = jwt.decode(token, {complete: true});
expect(err).to.be.null;
expect(decoded.header).to.not.have.property('kid');
});
});
});

it('should add "kid" header when "keyid" option is provided and an object payload', function(done) {
signWithKeyId('foo', {}, (err, token) => {
testUtils.asyncCheck(done, () => {
const decoded = jwt.decode(token, {complete: true});
expect(err).to.be.null;
expect(decoded.header).to.have.property('kid', 'foo');
});
});
});

it('should add "kid" header when "keyid" option is provided and a Buffer payload', function(done) {
signWithKeyId('foo', new Buffer('a Buffer payload'), (err, token) => {
testUtils.asyncCheck(done, () => {
const decoded = jwt.decode(token, {complete: true});
expect(err).to.be.null;
expect(decoded.header).to.have.property('kid', 'foo');
});
});
});

it('should add "kid" header when "keyid" option is provided and a string payload', function(done) {
signWithKeyId('foo', 'a string payload', (err, token) => {
testUtils.asyncCheck(done, () => {
const decoded = jwt.decode(token, {complete: true});
expect(err).to.be.null;
expect(decoded.header).to.have.property('kid', 'foo');
});
});
});
});
});
24 changes: 0 additions & 24 deletions test/iat.tests.js

This file was deleted.

3 changes: 1 addition & 2 deletions test/invalid_exp.tests.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
var jwt = require('../index');
var expect = require('chai').expect;
var assert = require('chai').assert;

describe('invalid expiration', function() {

it('should fail with string', function (done) {
var broken_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxMjMiLCJmb28iOiJhZGFzIn0.cDa81le-pnwJMcJi3o3PBwB7cTJMiXCkizIhxbXAKRg';

jwt.verify(broken_token, '123', function (err, decoded) {
jwt.verify(broken_token, '123', function (err) {
expect(err.name).to.equal('JsonWebTokenError');
done();
});
15 changes: 0 additions & 15 deletions test/issue_196.tests.js

This file was deleted.

10 changes: 5 additions & 5 deletions test/issue_304.tests.js
Original file line number Diff line number Diff line change
@@ -4,35 +4,35 @@ 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) {
jwt.verify(123, 'foo', function (err) {
expect(err.name).to.equal('JsonWebTokenError');
done();
});
});

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

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

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

it('should fail with booleans', function (done) {
jwt.verify(true, 'foo', function (err, decoded) {
jwt.verify(true, 'foo', function (err) {
expect(err.name).to.equal('JsonWebTokenError');
done();
});
318 changes: 0 additions & 318 deletions test/jwt.asymmetric_signing.tests.js
Original file line number Diff line number Diff line change
@@ -113,324 +113,6 @@ describe('Asymmetric Algorithms', function(){
});
});

describe('when signing a token with not before', function () {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, notBefore: -10 * 3600 });

it('should be valid expiration', function (done) {
jwt.verify(token, pub, function (err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});

it('should be invalid', function (done) {
// not active token
token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, notBefore: '10m' });

jwt.verify(token, pub, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'NotBeforeError');
assert.instanceOf(err.date, Date);
assert.instanceOf(err, jwt.NotBeforeError);
done();
});
});


it('should valid when date are equals', function (done) {
Date.fix(1451908031);

token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, notBefore: 0 });

jwt.verify(token, pub, function (err, decoded) {
assert.isNull(err);
assert.isNotNull(decoded);
Date.unfix();
done();
});
});

it('should NOT be invalid', function (done) {
// not active token
token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, notBefore: '10m' });

jwt.verify(token, pub, { ignoreNotBefore: true }, function (err, decoded) {
assert.ok(decoded.foo);
assert.equal('bar', decoded.foo);
done();
});
});
});

describe('when signing a token with audience', function () {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, audience: 'urn:foo' });

it('should check audience', function (done) {
jwt.verify(token, pub, { audience: 'urn:foo' }, function (err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});

it('should check audience using RegExp', function (done) {
jwt.verify(token, pub, { audience: /urn:f[o]{2}/ }, function (err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});

it('should check audience in array', function (done) {
jwt.verify(token, pub, { audience: ['urn:foo', 'urn:other'] }, function (err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});

it('should check audience in array using RegExp', function (done) {
jwt.verify(token, pub, { audience: ['urn:bar', /urn:f[o]{2}/, 'urn:other'] }, function (err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});

it('should throw when invalid audience', function (done) {
jwt.verify(token, pub, { audience: 'urn:wrong' }, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
done();
});
});

it('should throw when invalid audience using RegExp', function (done) {
jwt.verify(token, pub, { audience: /urn:bar/ }, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
done();
});
});

it('should throw when invalid audience in array', function (done) {
jwt.verify(token, pub, { audience: ['urn:wrong', 'urn:morewrong', /urn:bar/] }, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
done();
});
});

});

describe('when signing a token with array audience', function () {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, audience: ['urn:foo', 'urn:bar'] });

it('should check audience', function (done) {
jwt.verify(token, pub, { audience: 'urn:foo' }, function (err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});

it('should check other audience', function (done) {
jwt.verify(token, pub, { audience: 'urn:bar' }, function (err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});

it('should check audience using RegExp', function (done) {
jwt.verify(token, pub, { audience: /urn:f[o]{2}/ }, function (err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});

it('should check audience in array', function (done) {
jwt.verify(token, pub, { audience: ['urn:foo', 'urn:other'] }, function (err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});

it('should check audience in array using RegExp', function (done) {
jwt.verify(token, pub, { audience: ['urn:one', 'urn:other', /urn:f[o]{2}/] }, function (err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});

it('should throw when invalid audience', function (done) {
jwt.verify(token, pub, { audience: 'urn:wrong' }, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
done();
});
});

it('should throw when invalid audience using RegExp', function (done) {
jwt.verify(token, pub, { audience: /urn:wrong/ }, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
done();
});
});

it('should throw when invalid audience in array', function (done) {
jwt.verify(token, pub, { audience: ['urn:wrong', 'urn:morewrong'] }, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
done();
});
});

it('should throw when invalid audience in array', function (done) {
jwt.verify(token, pub, { audience: ['urn:wrong', 'urn:morewrong', /urn:alsowrong/] }, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
done();
});
});

});

describe('when signing a token without audience', function () {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm });

it('should check audience', function (done) {
jwt.verify(token, pub, { audience: 'urn:wrong' }, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
done();
});
});

it('should check audience using RegExp', function (done) {
jwt.verify(token, pub, { audience: /urn:wrong/ }, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
done();
});
});

it('should check audience in array', function (done) {
jwt.verify(token, pub, { audience: ['urn:wrong', 'urn:morewrong', /urn:alsowrong/] }, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
done();
});
});

});

describe('when signing a token with issuer', function () {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, issuer: 'urn:foo' });

it('should check issuer', function (done) {
jwt.verify(token, pub, { issuer: 'urn:foo' }, function (err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});

it('should check the issuer when providing a list of valid issuers', function (done) {
jwt.verify(token, pub, { issuer: ['urn:foo', 'urn:bar'] }, function (err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});

it('should throw when invalid issuer', function (done) {
jwt.verify(token, pub, { issuer: 'urn:wrong' }, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
done();
});
});
});

describe('when signing a token without issuer', function () {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm });

it('should check issuer', function (done) {
jwt.verify(token, pub, { issuer: 'urn:foo' }, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
done();
});
});
});

describe('when signing a token with subject', function () {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, subject: 'subject' });

it('should check subject', function (done) {
jwt.verify(token, pub, { subject: 'subject' }, function (err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});

it('should throw when invalid subject', function (done) {
jwt.verify(token, pub, { subject: 'wrongSubject' }, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
done();
});
});
});

describe('when signing a token without subject', function () {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm });

it('should check subject', function (done) {
jwt.verify(token, pub, { subject: 'subject' }, function (err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
assert.equal(err.name, 'JsonWebTokenError');
assert.instanceOf(err, jwt.JsonWebTokenError);
done();
});
});
});

describe('when signing a token with jwt id', function () {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, jwtid: 'jwtid' });

2 changes: 1 addition & 1 deletion test/jwt.hs.tests.js
Original file line number Diff line number Diff line change
@@ -102,7 +102,7 @@ describe('HS256', function() {

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) {
jwt.verify(malformedToken, secret, { algorithm: 'HS256', ignoreExpiration: true }, function(err) {
assert.isNotNull(err);
assert.equal('JsonWebTokenError', err.name);
assert.equal('invalid token', err.message);
9 changes: 0 additions & 9 deletions test/keyid.tests.js

This file was deleted.

15 changes: 0 additions & 15 deletions test/non_object_values.tests.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
var jwt = require('../index');
var expect = require('chai').expect;
var JsonWebTokenError = require('../lib/JsonWebTokenError');

describe('non_object_values values', function() {

@@ -10,20 +9,6 @@ describe('non_object_values values', function() {
expect(result).to.equal('hello');
});

//v6 version will throw in this case:
it('should throw with expiresIn', function () {
expect(function () {
jwt.sign('hello', '123', { expiresIn: '12h' });
}).to.throw(/invalid expiresIn option for string payload/);
});

it('should fail to validate audience when the payload is string', function () {
var token = jwt.sign('hello', '123');
expect(function () {
jwt.verify(token, '123', { audience: 'foo' });
}).to.throw(JsonWebTokenError);
});

it('should work with number', function () {
var token = jwt.sign(123, '123');
var result = jwt.verify(token, '123');
70 changes: 70 additions & 0 deletions test/option-maxAge.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use strict';

const jwt = require('../');
const expect = require('chai').expect;
const sinon = require('sinon');
const util = require('util');

describe('maxAge option', function() {
let token;

let fakeClock;
beforeEach(function() {
fakeClock = sinon.useFakeTimers({now: 60000});
token = jwt.sign({iat: 70}, undefined, {algorithm: 'none'});
});

afterEach(function() {
fakeClock.uninstall();
});

[
{
description: 'should work with a positive string value',
maxAge: '3s',
},
{
description: 'should work with a negative string value',
maxAge: '-3s',
},
{
description: 'should work with a positive numeric value',
maxAge: 3,
},
{
description: 'should work with a negative numeric value',
maxAge: -3,
},
].forEach((testCase) => {
it(testCase.description, function (done) {
expect(jwt.verify(token, undefined, {maxAge: '3s'})).to.not.throw;
jwt.verify(token, undefined, {maxAge: testCase.maxAge}, (err) => {
expect(err).to.be.null;
done();
})
});
});

[
true,
'invalid',
[],
['foo'],
{},
{foo: 'bar'},
].forEach((maxAge) => {
it(`should error with value ${util.inspect(maxAge)}`, function (done) {
expect(() => jwt.verify(token, undefined, {maxAge})).to.throw(
jwt.JsonWebTokenError,
'"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'
);
jwt.verify(token, undefined, {maxAge}, (err) => {
expect(err).to.be.instanceOf(jwt.JsonWebTokenError);
expect(err.message).to.equal(
'"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'
);
done();
})
});
});
});
57 changes: 57 additions & 0 deletions test/option-nonce.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use strict';

const jwt = require('../');
const expect = require('chai').expect;
const util = require('util');
const testUtils = require('./test-utils')

describe('nonce option', function () {
let token;

beforeEach(function () {
token = jwt.sign({ nonce: 'abcde' }, undefined, { algorithm: 'none' });
});
[
{
description: 'should work with a string',
nonce: 'abcde',
},
].forEach((testCase) => {
it(testCase.description, function (done) {
testUtils.verifyJWTHelper(token, undefined, { nonce: testCase.nonce }, (err, decoded) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.null;
expect(decoded).to.have.property('nonce', 'abcde');
});
});
});
});
[
true,
false,
null,
-1,
0,
1,
-1.1,
1.1,
-Infinity,
Infinity,
NaN,
'',
' ',
[],
['foo'],
{},
{ foo: 'bar' },
].forEach((nonce) => {
it(`should error with value ${util.inspect(nonce)}`, function (done) {
testUtils.verifyJWTHelper(token, undefined, { nonce }, (err) => {
testUtils.asyncCheck(done, () => {
expect(err).to.be.instanceOf(jwt.JsonWebTokenError);
expect(err).to.have.property('message', 'nonce must be a non-empty string')
});
});
});
});
});
66 changes: 0 additions & 66 deletions test/schema.tests.js
Original file line number Diff line number Diff line change
@@ -14,36 +14,6 @@ describe('schema', function() {
jwt.sign({foo: 123}, isEcdsa ? cert_ecdsa_priv : cert_rsa_priv, options);
}

it('should validate expiresIn', function () {
expect(function () {
sign({ expiresIn: '1 monkey' });
}).to.throw(/"expiresIn" should be a number of seconds or string representing a timespan/);
expect(function () {
sign({ expiresIn: 1.1 });
}).to.throw(/"expiresIn" should be a number of seconds or string representing a timespan/);
sign({ expiresIn: '10s' });
sign({ expiresIn: 10 });
});

it('should validate notBefore', function () {
expect(function () {
sign({ notBefore: '1 monkey' });
}).to.throw(/"notBefore" should be a number of seconds or string representing a timespan/);
expect(function () {
sign({ notBefore: 1.1 });
}).to.throw(/"notBefore" should be a number of seconds or string representing a timespan/);
sign({ notBefore: '10s' });
sign({ notBefore: 10 });
});

it('should validate audience', function () {
expect(function () {
sign({ audience: 10 });
}).to.throw(/"audience" must be a string or array/);
sign({ audience: 'urn:foo' });
sign({ audience: ['urn:foo'] });
});

it('should validate algorithm', function () {
expect(function () {
sign({ algorithm: 'foo' });
@@ -74,34 +44,12 @@ describe('schema', function() {
sign({encoding: 'utf8'});
});

it('should validate issuer', function () {
expect(function () {
sign({ issuer: 10 });
}).to.throw(/"issuer" must be a string/);
sign({issuer: 'foo'});
});

it('should validate subject', function () {
expect(function () {
sign({ subject: 10 });
}).to.throw(/"subject" must be a string/);
sign({subject: 'foo'});
});

it('should validate noTimestamp', function () {
expect(function () {
sign({ noTimestamp: 10 });
}).to.throw(/"noTimestamp" must be a boolean/);
sign({noTimestamp: true});
});

it('should validate keyid', function () {
expect(function () {
sign({ keyid: 10 });
}).to.throw(/"keyid" must be a string/);
sign({keyid: 'foo'});
});

});

describe('sign payload registered claims', function() {
@@ -110,27 +58,13 @@ describe('schema', function() {
jwt.sign(payload, 'foo123');
}

it('should validate iat', function () {
expect(function () {
sign({ iat: '1 monkey' });
}).to.throw(/"iat" should be a number of seconds/);
sign({ iat: 10.1 });
});

it('should validate exp', function () {
expect(function () {
sign({ exp: '1 monkey' });
}).to.throw(/"exp" should be a number of seconds/);
sign({ exp: 10.1 });
});

it('should validate nbf', function () {
expect(function () {
sign({ nbf: '1 monkey' });
}).to.throw(/"nbf" should be a number of seconds/);
sign({ nbf: 10.1 });
});

});

});
125 changes: 125 additions & 0 deletions test/test-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
'use strict';

const jwt = require('../');
const expect = require('chai').expect;
const sinon = require('sinon');

/**
* Correctly report errors that occur in an asynchronous callback
* @param {function(err): void} done The mocha callback
* @param {function(): void} testFunction The assertions function
*/
function asyncCheck(done, testFunction) {
try {
testFunction();
done();
}
catch(err) {
done(err);
}
}

/**
* Assert that two errors are equal
* @param e1 {Error} The first error
* @param e2 {Error} The second error
*/
// chai does not do deep equality on errors: https://github.com/chaijs/chai/issues/1009
function expectEqualError(e1, e2) {
// message and name are not always enumerable, so manually reference them
expect(e1.message, 'Async/Sync Error equality: message').to.equal(e2.message);
expect(e1.name, 'Async/Sync Error equality: name').to.equal(e2.name);

// compare other enumerable error properties
for(const propertyName in e1) {
expect(e1[propertyName], `Async/Sync Error equality: ${propertyName}`).to.deep.equal(e2[propertyName]);
}
}

/**
* Base64-url encode a string
* @param str {string} The string to encode
* @returns {string} The encoded string
*/
function base64UrlEncode(str) {
return Buffer.from(str).toString('base64')
.replace(/[=]/g, "")
.replace(/\+/g, "-")
.replace(/\//g, "_")
;
}

/**
* Verify a JWT, ensuring that the asynchronous and synchronous calls to `verify` have the same result
* @param {string} jwtString The JWT as a string
* @param {string} secretOrPrivateKey The shared secret or private key
* @param {object} options Verify options
* @param {function(err, token):void} callback
*/
function verifyJWTHelper(jwtString, secretOrPrivateKey, options, callback) {
// freeze the time to ensure the clock remains stable across the async and sync calls
const fakeClock = sinon.useFakeTimers({now: Date.now()});
let error;
let syncVerified;
try {
syncVerified = jwt.verify(jwtString, secretOrPrivateKey, options);
}
catch (err) {
error = err;
}
jwt.verify(jwtString, secretOrPrivateKey, options, (err, asyncVerifiedToken) => {
try {
if (error) {
expectEqualError(err, error);
callback(err);
}
else {
expect(syncVerified, 'Async/Sync token equality').to.deep.equal(asyncVerifiedToken);
callback(null, syncVerified);
}
}
finally {
if (fakeClock) {
fakeClock.restore();
}
}
});
}

/**
* Sign a payload to create a JWT, ensuring that the asynchronous and synchronous calls to `sign` have the same result
* @param {object} payload The JWT payload
* @param {string} secretOrPrivateKey The shared secret or private key
* @param {object} options Sign options
* @param {function(err, token):void} callback
*/
function signJWTHelper(payload, secretOrPrivateKey, options, callback) {
// freeze the time to ensure the clock remains stable across the async and sync calls
const fakeClock = sinon.useFakeTimers({now: Date.now()});
let error;
let syncSigned;
try {
syncSigned = jwt.sign(payload, secretOrPrivateKey, options);
}
catch (err) {
error = err;
}
jwt.sign(payload, secretOrPrivateKey, options, (err, asyncSigned) => {
fakeClock.restore();
if (error) {
expectEqualError(err, error);
callback(err);
}
else {
expect(syncSigned, 'Async/Sync token equality').to.equal(asyncSigned);
callback(null, syncSigned);
}
});
}

module.exports = {
asyncCheck,
base64UrlEncode,
signJWTHelper,
verifyJWTHelper,
};
1 change: 0 additions & 1 deletion test/undefined_secretOrPublickey.tests.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
var fs = require('fs');
var jwt = require('../index');
var JsonWebTokenError = require('../lib/JsonWebTokenError');
var expect = require('chai').expect;
32 changes: 0 additions & 32 deletions test/util/fakeDate.js

This file was deleted.

208 changes: 1 addition & 207 deletions test/verify.tests.js
Original file line number Diff line number Diff line change
@@ -188,127 +188,11 @@ describe('verify', function() {
});
});

it('should not error if within maxAge timespan', function (done) {
clock = sinon.useFakeTimers(1437018587500); // iat + 5.5s, exp - 4.5s
var options = {algorithms: ['HS256'], maxAge: '6s'};

jwt.verify(token, key, options, function (err, p) {
assert.isNull(err);
assert.equal(p.foo, 'bar');
done();
});
});

describe('option: maxAge', function () {

[String('3s'), '3s', 3].forEach(function(maxAge) {
it(`should error for claims issued before a certain timespan (${typeof maxAge} type)`, function (done) {
clock = sinon.useFakeTimers(1437018587000); // iat + 5s, exp - 5s
var options = {algorithms: ['HS256'], maxAge: maxAge};

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), 1437018585000);
assert.isUndefined(p);
done();
});
});
});

[String('5s'), '5s', 5].forEach(function (maxAge) {
it(`should not error for claims issued before a certain timespan but still inside clockTolerance timespan (${typeof maxAge} type)`, function (done) {
clock = sinon.useFakeTimers(1437018587500); // iat + 5.5s, exp - 4.5s
var options = {algorithms: ['HS256'], maxAge: maxAge, clockTolerance: 1 };

jwt.verify(token, key, options, function (err, p) {
assert.isNull(err);
assert.equal(p.foo, 'bar');
done();
});
});
});

[String('6s'), '6s', 6].forEach(function (maxAge) {
it(`should not error if within maxAge timespan (${typeof maxAge} type)`, function (done) {
clock = sinon.useFakeTimers(1437018587500);// iat + 5.5s, exp - 4.5s
var options = {algorithms: ['HS256'], maxAge: maxAge};

jwt.verify(token, key, options, function (err, p) {
assert.isNull(err);
assert.equal(p.foo, 'bar');
done();
});
});
});

[String('8s'), '8s', 8].forEach(function (maxAge) {
it(`can be more restrictive than expiration (${typeof maxAge} type)`, function (done) {
clock = sinon.useFakeTimers(1437018591900); // iat + 9.9s, exp - 0.1s
var options = {algorithms: ['HS256'], maxAge: maxAge };

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), 1437018590000);
assert.isUndefined(p);
done();
});
});
});

[String('12s'), '12s', 12].forEach(function (maxAge) {
it(`cannot be more permissive than expiration (${typeof maxAge} type)`, function (done) {
clock = sinon.useFakeTimers(1437018593000); // iat + 11s, exp + 1s
var options = {algorithms: ['HS256'], maxAge: '12s'};

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), 1437018592000);
assert.isUndefined(p);
done();
});
});
});

[new String('1s'), 'no-timespan-string'].forEach(function (maxAge){
it(`should error if maxAge is specified with a wrong string format/type (value: ${maxAge}, type: ${typeof maxAge})`, function (done) {
clock = sinon.useFakeTimers(1437018587000); // iat + 5s, exp - 5s
var options = { algorithms: ['HS256'], maxAge: maxAge };

jwt.verify(token, key, options, function (err, p) {
assert.equal(err.name, 'JsonWebTokenError');
assert.equal(err.message, '"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60');
assert.isUndefined(p);
done();
});
});
});

it('should error if maxAge is specified but there is no iat claim', function (done) {
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIifQ.0MBPd4Bru9-fK_HY3xmuDAc6N_embknmNuhdb9bKL_U';
var options = {algorithms: ['HS256'], 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();
});
});

});

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) {
jwt.verify(token, key, {clockTimestamp: clockTimestamp}, function (err) {
assert.isNull(err);
done();
});
@@ -333,89 +217,11 @@ describe('verify', function() {
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 } exp = iat + 218s
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 = 1437018592; // iat + 10s
var options = {
algorithms: ['HS256'],
clockTimestamp: clockTimestamp,
maxAge: '3s',
clockTolerance: 10
};

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 = 1437018587; // iat + 5s
var options = {algorithms: ['HS256'], clockTimestamp: clockTimestamp, maxAge: '6s'};

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; // iat + 6s
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; // iat + 318s (exp: iat + 218s)
var options = {algorithms: ['HS256'], clockTimestamp: clockTimestamp, maxAge: '1000y'};
@@ -430,18 +236,6 @@ describe('verify', function() {
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();
});
});
});
});
});
10 changes: 10 additions & 0 deletions verify.js
Original file line number Diff line number Diff line change
@@ -33,6 +33,10 @@ module.exports = function (jwtString, secretOrPublicKey, options, callback) {
return done(new JsonWebTokenError('clockTimestamp must be a number'));
}

if (options.nonce !== undefined && (typeof options.nonce !== 'string' || options.nonce.trim() === '')) {
return done(new JsonWebTokenError('nonce must be a non-empty string'));
}

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

if (!jwtString){
@@ -179,6 +183,12 @@ module.exports = function (jwtString, secretOrPublicKey, options, callback) {
}
}

if (options.nonce) {
if (payload.nonce !== options.nonce) {
return done(new JsonWebTokenError('jwt nonce invalid. expected: ' + options.nonce));
}
}

if (options.maxAge) {
if (typeof payload.iat !== 'number') {
return done(new JsonWebTokenError('iat required when maxAge is specified'));