diff --git a/README.md b/README.md index b1a613f..99a5cc8 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ encoded private key for RSA and ECDSA. In case of a private key with passphrase * `noTimestamp` * `header` * `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`. diff --git a/sign.js b/sign.js index d71189b..ea3bb36 100644 --- a/sign.js +++ b/sign.js @@ -20,7 +20,8 @@ var sign_options_schema = { subject: { isValid: isString, message: '"subject" must be a string' }, jwtid: { isValid: isString, message: '"jwtid" must be a string' }, noTimestamp: { isValid: isBoolean, message: '"noTimestamp" must be a boolean' }, - keyid: { isValid: isString, message: '"keyid" must be a string' } + keyid: { isValid: isString, message: '"keyid" must be a string' }, + mutatePayload: { isValid: isBoolean, message: '"mutatePayload" must be a boolean' } }; var registered_claims_schema = { @@ -110,7 +111,9 @@ module.exports = function (payload, secretOrPrivateKey, options, callback) { catch (error) { return failure(error); } - payload = xtend(payload); + if (!options.mutatePayload) { + payload = xtend(payload); + } } else { var invalid_options = options_for_objects.filter(function (opt) { return typeof options[opt] !== 'undefined'; diff --git a/test/async_sign.tests.js b/test/async_sign.tests.js index b6cc052..d22f974 100644 --- a/test/async_sign.tests.js +++ b/test/async_sign.tests.js @@ -82,6 +82,30 @@ describe('signing a token asynchronously', function() { }); }); + describe('when mutatePayload is not set', function() { + it('should not apply claims to the original payload object (mutatePayload defaults to false)', function(done) { + var originalPayload = { foo: 'bar' }; + jwt.sign(originalPayload, 'secret', { notBefore: 60, expiresIn: 600 }, function (err) { + if (err) { return done(err); } + expect(originalPayload).to.not.have.property('nbf'); + expect(originalPayload).to.not.have.property('exp'); + done(); + }); + }); + }); + + describe('when mutatePayload is set to true', function() { + it('should apply claims directly to the original payload object', function(done) { + var originalPayload = { foo: 'bar' }; + jwt.sign(originalPayload, 'secret', { notBefore: 60, expiresIn: 600, mutatePayload: true }, function (err) { + if (err) { return done(err); } + expect(originalPayload).to.have.property('nbf').that.is.a('number'); + expect(originalPayload).to.have.property('exp').that.is.a('number'); + done(); + }); + }); + }); + describe('secret must have a value', function(){ [undefined, '', 0].forEach(function(secret){ 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) {