Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding custom algorithm #429

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
All notable changes to this project will be documented in this file starting from version **v4.0.0**.
This project adheres to [Semantic Versioning](http://semver.org/).

## 8.1.2 - 2018.01.30

- Added possibility to use your own algorithm (#427) (https://github.com/auth0/node-jsonwebtoken/issues/427)

## 8.1.1 - 2018-01-22

- ci: add newer node versions to build matrix (#428) ([83f3eee44e122da06f812d7da4ace1fa26c24d9d](https://github.com/auth0/node-jsonwebtoken/commit/83f3eee44e122da06f812d7da4ace1fa26c24d9d))
- deps: Bump ms version to add support for negative numbers (#438) ([25e0e624545eaef76f3c324a134bf103bc394724](https://github.com/auth0/node-jsonwebtoken/commit/25e0e624545eaef76f3c324a134bf103bc394724))
- docs: Minor typo (#424) ([dddcb73ac05de11b81feeb629f6cf78dd03d2047](https://github.com/auth0/node-jsonwebtoken/commit/dddcb73ac05de11b81feeb629f6cf78dd03d2047))
- bug fix: Not Before (nbf) calculated based on iat/timestamp (#437) ([2764a64908d97c043d62eba0bf6c600674f9a6d6](https://github.com/auth0/node-jsonwebtoken/commit/2764a64908d97c043d62eba0bf6c600674f9a6d6)), closes [#435](https://github.com/auth0/node-jsonwebtoken/issues/435)


## 8.1.0 - 2017-10-09

- #402: Don't fail if captureStackTrace is not a function (#410) ([77ee965d9081faaf21650f266399f203f69533c5](https://github.com/auth0/node-jsonwebtoken/commit/77ee965d9081faaf21650f266399f203f69533c5))
Expand Down
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ encoded private key for RSA and ECDSA. In case of a private key with passphrase

`options`:

* `algorithm` (default: `HS256`)
* `algorithm` (default: `HS256`). It is possible to use a function here that will be used instead of default jws library to sign the payload.
* `expiresIn`: expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms). Eg: `60`, `"2 days"`, `"10h"`, `"7d"`
* `notBefore`: expressed in seconds or a string describing a time span [zeit/ms](https://github.com/zeit/ms). Eg: `60`, `"2 days"`, `"10h"`, `"7d"`
* `audience`
Expand Down Expand Up @@ -71,6 +71,23 @@ var token = jwt.sign({ foo: 'bar' }, cert, { algorithm: 'RS256'});
jwt.sign({ foo: 'bar' }, cert, { algorithm: 'RS256' }, function(err, token) {
console.log(token);
});

// sign asynchronously and synchronously with custom algorithm
function customAlgorithmFunctionAsync(payload, secretOrPrivateKey, options, callback) {
return callback(null, result);
}
var optAsync = {
algorithm: customAlgorithmFunctionAsync
};
jwt.sign({ foo: 'bar' }, 'secret', optAsync, callback);

function customAlgorithmFunctionSync(payload, secretOrPrivateKey, options) {
return result;
}
var optSync = {
algorithm: customAlgorithmFunctionSync
};
jwt.sign({ foo: 'bar' }, "secret", optSync);
```

#### Token Expiration (exp claim)
Expand Down Expand Up @@ -119,7 +136,7 @@ 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"]`.
* `algorithms`: List of strings with the names of the allowed algorithms. For instance, `["HS256", "HS384"]`. It is possible to put to this array a function that will be used instead of default jws library to verify the token;
* `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.
Expand All @@ -129,7 +146,6 @@ As mentioned in [this comment](https://github.com/auth0/node-jsonwebtoken/issues
* `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"`.
* `clockTimestamp`: the time in seconds that should be used as the current time for all necessary comparisons.


```js
// verify a token symmetric - synchronous
var decoded = jwt.verify(token, 'shhhhh');
Expand Down Expand Up @@ -189,6 +205,15 @@ jwt.verify(token, cert, { algorithms: ['RS256'] }, function (err, payload) {
// if token alg != RS256, err == invalid signature
});

//verify with custom alg
function customAlgorithmFunction (jwtString, secretOrPublicKey, options, callback) {
//custom logic here
return callback(null, decoded)
})
jwt.verify(jwtString, secretOrPublicKey, {
algorithms: [customAlgorithmFunction]
})

```

### jwt.decode(token [, options])
Expand Down Expand Up @@ -287,6 +312,8 @@ ES384 | ECDSA using P-384 curve and SHA-384 hash algorithm
ES512 | ECDSA using P-521 curve and SHA-512 hash algorithm
none | No digital signature or MAC value included

It is also possible to use your own function with custom algorithm to perform sign/verify/decode operations.

## Refreshing JWTs

First of all, we recommend to think carefully if auto-refreshing a JWT will not introduce any vulnerability in your system.
Expand Down
5 changes: 5 additions & 0 deletions decode.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ var jws = require('jws');

module.exports = function (jwt, options) {
options = options || {};

if (typeof options.algorithm === 'function') {
return options.algorithm(jwt);
}

var decoded = jws.decode(jwt, options);
if (!decoded) { return null; }
var payload = decoded.payload;
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ module.exports = {
sign: require('./sign'),
JsonWebTokenError: require('./lib/JsonWebTokenError'),
NotBeforeError: require('./lib/NotBeforeError'),
TokenExpiredError: require('./lib/TokenExpiredError'),
TokenExpiredError: require('./lib/TokenExpiredError')
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"jws": "^3.1.4",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isfunction": "^3.0.8",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
Expand Down
35 changes: 23 additions & 12 deletions sign.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var includes = require('lodash.includes');
var isBoolean = require('lodash.isboolean');
var isInteger = require('lodash.isinteger');
var isNumber = require('lodash.isnumber');
var isFunction = require('lodash.isfunction');
var isPlainObject = require('lodash.isplainobject');
var isString = require('lodash.isstring');
var once = require('lodash.once');
Expand All @@ -13,7 +14,7 @@ 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' },
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' },
algorithm: { isValid: function(value) { return includes(['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'none'], value) || isFunction(value); }, message: '"algorithm" must be a valid string enum value or a function!' },
header: { isValid: isPlainObject, message: '"header" must be an object' },
encoding: { isValid: isString, message: '"encoding" must be a string' },
issuer: { isValid: isString, message: '"issuer" must be a string' },
Expand Down Expand Up @@ -70,7 +71,7 @@ var options_for_objects = [
'audience',
'issuer',
'subject',
'jwtid',
'jwtid'
];

module.exports = function (payload, secretOrPrivateKey, options, callback) {
Expand Down Expand Up @@ -169,20 +170,30 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) {
});

var encoding = options.encoding || 'utf8';
var isCustomAlgorithmUsed = typeof options.algorithm === 'function';

if (typeof callback === 'function') {
callback = callback && once(callback);

jws.createSign({
header: header,
privateKey: secretOrPrivateKey,
payload: payload,
encoding: encoding
}).once('error', callback)
.once('done', function (signature) {
callback(null, signature);
if (typeof options.algorithm === 'function') {
options.algorithm(payload, secretOrPrivateKey, options, function (err, result) {
return callback(err, result);
});
} else {
jws.createSign({
header: header,
privateKey: secretOrPrivateKey,
payload: payload,
encoding: encoding
}).once('error', callback)
.once('done', function (signature) {
callback(null, signature);
});
}
} else {
return jws.sign({header: header, payload: payload, secret: secretOrPrivateKey, encoding: encoding});
if (isCustomAlgorithmUsed) {
return options.algorithm(payload, secretOrPrivateKey, options);
}
return jws.sign({header: header, payload: payload, secret: secretOrPrivateKey, encoding: encoding});
}
};
};
11 changes: 10 additions & 1 deletion verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ module.exports = function (jwtString, secretOrPublicKey, options, callback) {
return data;
};
}
var decodedToken;

if (typeof options.algorithm === 'function') {
try {
decodedToken = options.algorithm(jwtString, secretOrPublicKey, options);
return done(null, decodedToken);
} catch (err) {
return done(err);
}
}

if (options.clockTimestamp && typeof options.clockTimestamp !== 'number') {
return done(new JsonWebTokenError('clockTimestamp must be a number'));
Expand Down Expand Up @@ -73,7 +83,6 @@ module.exports = function (jwtString, secretOrPublicKey, options, callback) {

}

var decodedToken;
try {
decodedToken = jws.decode(jwtString);
} catch(err) {
Expand Down