JsonWebTokenError = function (message, error) { Error.call(this, message); Error.captureStackTrace(this, this.constructor); this.name = 'JsonWebTokenError'; this.message = message; if (error) this.inner = error; }
n/a
NotBeforeError = function (message, date) { JsonWebTokenError.call(this, message); this.name = 'NotBeforeError'; this.date = date; }
n/a
TokenExpiredError = function (message, expiredAt) { JsonWebTokenError.call(this, message); this.name = 'TokenExpiredError'; this.expiredAt = expiredAt; }
n/a
decode = function (jwt, options) { options = options || {}; var decoded = jws.decode(jwt, options); if (!decoded) { return null; } var payload = decoded.payload; //try parse the payload if(typeof payload === 'string') { try { var obj = JSON.parse(payload); if(typeof obj === 'object') { payload = obj; } } catch (e) { } } //return header if `complete` option is enabled. header includes claims //such as `kid` and `alg` used to select the key within a JWKS needed to //verify the signature if (options.complete === true) { return { header: decoded.header, payload: payload, signature: decoded.signature }; } return payload; }
...
var jws = require('jws');
module.exports = function (jwt, options) {
options = options || {};
var decoded = jws.decode(jwt, options);
if (!decoded) { return null; }
var payload = decoded.payload;
//try parse the payload
if(typeof payload === 'string') {
try {
var obj = JSON.parse(payload);
...
sign = function (payload, secretOrPrivateKey, options, callback) { if (typeof options === 'function') { callback = options; options = {}; } else { options = options || {}; } var isObjectPayload = typeof payload === 'object' && !Buffer.isBuffer(payload); var header = xtend({ alg: options.algorithm || 'HS256', typ: isObjectPayload ? 'JWT' : undefined, kid: options.keyid }, options.header); function failure(err) { if (callback) { return callback(err); } throw err; } if (typeof payload === 'undefined') { return failure(new Error('payload is required')); } else if (isObjectPayload) { var payload_validation_result = registered_claims_schema.validate(payload); if (payload_validation_result.error) { return failure(payload_validation_result.error); } payload = xtend(payload); } else { var invalid_options = options_for_objects.filter(function (opt) { return typeof options[opt] !== 'undefined'; }); if (invalid_options.length > 0) { return failure(new Error('invalid ' + invalid_options.join(',') + ' option for ' + (typeof payload ) + ' payload')); } } if (typeof payload.exp !== 'undefined' && typeof options.expiresIn !== 'undefined') { return failure(new Error('Bad "options.expiresIn" option the payload already has an "exp" property.')); } if (typeof payload.nbf !== 'undefined' && typeof options.notBefore !== 'undefined') { return failure(new Error('Bad "options.notBefore" option the payload already has an "nbf" property.')); } var validation_result = sign_options_schema.validate(options); if (validation_result.error) { return failure(validation_result.error); } var timestamp = payload.iat || Math.floor(Date.now() / 1000); if (!options.noTimestamp) { payload.iat = timestamp; } else { delete payload.iat; } if (typeof options.notBefore !== 'undefined') { payload.nbf = timespan(options.notBefore); 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); 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')); } } Object.keys(options_to_payload).forEach(function (key) { var claim = options_to_payload[key]; if (typeof options[key] !== 'undefined') { if (typeof payload[claim] !== 'undefined') { return failure(new Error('Bad "options.' + key + '" option. The payload already has an "' + claim + '" property.')); } payload[claim] = options[key]; } }); var encoding = options.encoding || 'utf8'; 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); }); } else { return jws.sign({header: header, payload: payload, secret: secretOrPrivateKey, encoding: encoding}); } }
...
```bash
$ npm install jsonwebtoken
```
# Usage
### jwt.sign(payload, secretOrPrivateKey, [options, callback])
(Asynchronous) If a callback is supplied, callback is called with the `err` or the JWT.
(Synchronous) Returns the JsonWebToken as string
`payload` could be an object literal, buffer or string. *Please note that* `exp` is only set if the payload is an object literal
.
...
verify = function (jwtString, secretOrPublicKey, options, callback) { if ((typeof options === 'function') && !callback) { callback = options; options = {}; } if (!options) { options = {}; } //clone this object since we are going to mutate it. options = xtend(options); var done; if (callback) { done = function() { var args = Array.prototype.slice.call(arguments, 0); return process.nextTick(function() { callback.apply(null, args); }); }; } else { done = function(err, data) { if (err) throw err; return data; }; } if (options.clockTimestamp && typeof options.clockTimestamp !== 'number') { return done(new JsonWebTokenError('clockTimestamp must be a number')); } var clockTimestamp = options.clockTimestamp || Math.floor(Date.now() / 1000); if (!jwtString){ return done(new JsonWebTokenError('jwt must be provided')); } if (typeof jwtString !== 'string') { return done(new JsonWebTokenError('jwt must be a string')); } var parts = jwtString.split('.'); if (parts.length !== 3){ return done(new JsonWebTokenError('jwt malformed')); } var hasSignature = parts[2].trim() !== ''; if (!hasSignature && secretOrPublicKey){ return done(new JsonWebTokenError('jwt signature is required')); } if (hasSignature && !secretOrPublicKey) { return done(new JsonWebTokenError('secret or public key must be provided')); } if (!hasSignature && !options.algorithms) { options.algorithms = ['none']; } if (!options.algorithms) { options.algorithms = ~secretOrPublicKey.toString().indexOf('BEGIN CERTIFICATE') || ~secretOrPublicKey.toString().indexOf('BEGIN PUBLIC KEY') ? [ 'RS256','RS384','RS512','ES256','ES384','ES512' ] : ~secretOrPublicKey.toString().indexOf('BEGIN RSA PUBLIC KEY') ? [ 'RS256','RS384','RS512' ] : [ 'HS256','HS384','HS512' ]; } var decodedToken; try { decodedToken = jws.decode(jwtString); } catch(err) { return done(err); } if (!decodedToken) { return done(new JsonWebTokenError('invalid token')); } var header = decodedToken.header; if (!~options.algorithms.indexOf(header.alg)) { return done(new JsonWebTokenError('invalid algorithm')); } var valid; try { valid = jws.verify(jwtString, header.alg, secretOrPublicKey); } catch (e) { return done(e); } if (!valid) return done(new JsonWebTokenError('invalid signature')); var payload; try { payload = decode(jwtString); } catch(err) { return done(err); } if (typeof payload.nbf !== 'undefined' && !options.ignoreNotBefore) { if (typeof payload.nbf !== 'number') { return done(new JsonWebTokenError('invalid nbf value')); } if (payload.nbf > clockTimestamp + (options.clockTolerance || 0)) { return done(new NotBeforeError('jwt not active', new Date(payload.nbf * 1000))); } } if (typeof payload.exp !== 'undefined' && !options.ignoreExpiration) { if (typeof payload.exp !== 'number') { return done(new JsonWebTokenError('invalid exp value')); } if (clockTimestamp >= payload.exp + (options.clockTolerance || 0)) { return done(new TokenExpiredError('jwt expired', new Date(payload.exp * 1000))); } } if (options.audience) { var audiences = Array.isArray(options.audience)? options.audience : [options.audience]; var target = Array.isArray(payload.aud) ? payload.aud : [payload.aud]; var match = target.some(function(aud) { return audiences.indexOf(aud) != -1; }); if (!match) return done(new JsonWebTokenError('jwt audience invalid. expected: ' + audiences.join(' or '))); } if (options.issuer) { var invalid_issuer = (typeof options.issuer === 'string' && payload.iss !== options.issuer) || (Array.isArray(options.issuer) && options.issuer.indexOf(payload.iss) === -1); if (invalid_issuer) { return done(new JsonWebTokenError('jwt issuer inv ...
...
https://github.com/auth0/node-jsonwebtoken/commit/24a370080e0b75f11d4717cd2b11b2949d95fc2e
https://github.com/auth0/node-jsonwebtoken/commit/a77df6d49d4ec688dfd0a1cc723586bffe753516
### Security
- [verify] Update to jws@^3.0.0 and renaming `header.alg` mismatch exception to `invalid algorithm` and adding more mismatch tests
.
As `jws@3.0.0` changed the verify method signature to be `jws.verify(signature, algorithm
, secretOrKey)`, the token header must be decoded first in order to make sure that the `alg` field matches one of the allowed `options
.algorithms`. After that, the now validated `header.alg` is passed to `jws.verify`
As the order of steps has changed, the error that was thrown when the JWT was invalid is no longer the `jws` one:
```
{ [Error: Invalid token: no header in signature 'a.b.c'] code: 'MISSING_HEADER', signature: 'a.b.c'
; }
```
That old error (removed from jws) has been replaced by a `JsonWebTokenError` with message `invalid token`.
...
JsonWebTokenError = function (message, error) { Error.call(this, message); Error.captureStackTrace(this, this.constructor); this.name = 'JsonWebTokenError'; this.message = message; if (error) this.inner = error; }
n/a
constructor = function (message, error) { Error.call(this, message); Error.captureStackTrace(this, this.constructor); this.name = 'JsonWebTokenError'; this.message = message; if (error) this.inner = error; }
n/a
NotBeforeError = function (message, date) { JsonWebTokenError.call(this, message); this.name = 'NotBeforeError'; this.date = date; }
n/a
constructor = function (message, date) { JsonWebTokenError.call(this, message); this.name = 'NotBeforeError'; this.date = date; }
n/a
TokenExpiredError = function (message, expiredAt) { JsonWebTokenError.call(this, message); this.name = 'TokenExpiredError'; this.expiredAt = expiredAt; }
n/a
constructor = function (message, expiredAt) { JsonWebTokenError.call(this, message); this.name = 'TokenExpiredError'; this.expiredAt = expiredAt; }
n/a