From 7a83804a4f7cbd40c4febded402bfb8c51073c21 Mon Sep 17 00:00:00 2001 From: Joaquim Serafim Date: Mon, 23 Jan 2017 18:46:36 +0000 Subject: [PATCH] add support to set optional reserved headers --- README.md | 40 +++++++++++++++++++++++++++++++++------- index.js | 52 +++++++++++++++++++++++++++++++++------------------- package.json | 5 +++-- test/test.js | 47 +++++++++++++++++++++++------------------------ 4 files changed, 92 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 79d451c..5db896f 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ the version `2.*.*` should work only for NodeJS >= **4** for NodeJS **0.10** and ##### jwt#encode(key, payload, [algorithm], cb) * **key**, your secret -* **payload**, the payload or Claim Names, +* **payload**, the payload or Claim Names or an object with {payload, header} *ex:* ```js @@ -49,7 +49,7 @@ the version `2.*.*` should work only for NodeJS >= **4** for NodeJS **0.10** and * **key**, your secret * **token**, the JWT token -* **cb**, the callback(err[name, message], payloadDecoded) +* **cb**, the callback(err[name, message], decodedPayload[, decodedHeader]) #### Example @@ -74,25 +74,51 @@ var secret = 'TOPSECRETTTTT'; // encode jwt.encode(secret, payload, function (err, token) { if (err) { - return console.error(err.name, err.message); + console.error(err.name, err.message); } else { console.log(token); // decode - jwt.decode(secret, token, function (err_, decode) { + jwt.decode(secret, token, function (err_, decodedPayload, decodedHeader) { if (err) { - return console.error(err.name, err.message); + console.error(err.name, err.message); } else { - console.log(decode); + console.log(decodedPayload, decodedHeader); } }); } }); ``` +**using the optional [reserved headers](http://self-issued.info/docs/draft-jones-json-web-token-01.html#ReservedHeaderParameterName) (alg and typ can't be set using this method)** +```js +var settingAddHeaders = { + payload: { + "iss": "my_issurer", + "aud": "World", + "iat": 1400062400223, + "typ": "/online/transactionstatus/v2", + "request": { + "myTransactionId": "[myTransactionId]", + "merchantTransactionId": "[merchantTransactionId]", + "status": "SUCCESS" + } + }, + header: { + kid: 'key ID' + } +} + +jwt.encode(secret, settingAddHeaders, function (err, token) { + +}) + +``` + + --- -#####this projet has been set up with a precommit that forces you to follow a code style, no jshint issues and 100% of code coverage before commit +#### this projet has been set up with a precommit that forces you to follow a code style, no jshint issues and 100% of code coverage before commit to run test ```js diff --git a/index.js b/index.js index eea1b0d..e78639c 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,11 @@ 'use strict' -var xtend = require('xtend') - const crypto = require('crypto') const b64url = require('base64-url') const inherits = require('util').inherits const parse = require('json-parse-safe') +const extend = require('xtend') +const isObject = require('is.object') // // supported algorithms @@ -33,22 +33,29 @@ function getAlgorithms () { return Object.keys(algorithms) } -function encode (key, payload, algorithm, cb) { +function encode (key, data, algorithm, cb) { if (paramIsValid(algorithm, 'function')) { cb = algorithm algorithm = 'HS256' } - var validationError = encodeValidations(key, payload, algorithm) + var defaultHeader = {typ: 'JWT', alg: algorithm} + + var payload = isObject(data) && data.payload ? + data.payload : + data + + var header = isObject(data) && data.header ? + extend(data.header, defaultHeader) : + defaultHeader + + const validationError = encodeValidations(key, payload, algorithm) if (validationError) { return prcResult(validationError, null, cb) } - var header = xtend({typ: 'JWT', alg: algorithm}, payload.header); - delete payload.header; - - var parts = b64url.encode(JSON.stringify(header)) + + const parts = b64url.encode(JSON.stringify(header)) + '.' + b64url.encode(JSON.stringify(payload)) @@ -64,7 +71,7 @@ function decode (key, token, cb) { return prcResult('The key and token are mandatory!', null, cb) } - var parts = token.split('.') + const parts = token.split('.') // check all parts're present if (parts.length !== 3) { @@ -76,25 +83,25 @@ function decode (key, token, cb) { } // base64 decode and parse JSON - var header = JSONParse(b64url.decode(parts[0])) - var payload = JSONParse(b64url.decode(parts[1])) + const header = JSONParse(b64url.decode(parts[0])) + const payload = JSONParse(b64url.decode(parts[1])) // get algorithm hash and type and check if is valid - var algorithm = algorithms[header.alg] + const algorithm = algorithms[header.alg] if (!algorithm) { return prcResult('The algorithm is not supported!', null, cb) } // verify the signature - var res = verify( + const res = verify( algorithm, key, parts.slice(0, 2).join('.'), parts[2] ) - return prcResult(!res && 'Invalid key!' || null, payload, cb) + return prcResult(!res && 'Invalid key!' || null, payload, header, cb) } function encodeValidations (key, payload, algorithm) { @@ -140,12 +147,20 @@ function verify (alg, key, input, signVar) { .verify(key, b64url.unescape(signVar), 'base64') } -function prcResult (err, res, cb) { +function prcResult (err, payload, header, cb) { + if (paramIsValid(header, 'function')) { + cb = header + header = undefined + } + err = err && new JWTError(err) return cb ? - cb(err, res) : - {error: err, value: res} + cb(err, payload, header) : + (header ? + {error: err, value: payload, header: header} : + {error: err, value: payload} + ) } function paramIsValid (param, type) { @@ -157,8 +172,7 @@ function paramsAreFalsy (param1, param2) { } function JSONParse (str) { - var res = parse(str) + const res = parse(str) return res.error && '' || res.value } - diff --git a/package.json b/package.json index a4d1dbe..68cfb59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-web-token", - "version": "2.0.3", + "version": "2.1.3", "description": "JSON Web Token (JWT) is a compact token format intended for space constrained environments such as HTTP Authorization headers and URI query parameters.", "main": "index.js", "scripts": { @@ -39,11 +39,12 @@ "homepage": "https://github.com/joaquimserafim/json-web-token", "dependencies": { "base64-url": "^1.2.2", + "is.object": "^1.0.0", "json-parse-safe": "^1.0.3", "xtend": "^4.0.1" }, "devDependencies": { - "istanbul": "^0.4.3", + "istanbul": "0.4.3", "jscs": "^2.11.0", "jshint": "^2.9.2", "nsp": "^2.4.0", diff --git a/test/test.js b/test/test.js index 1b77269..9b22862 100644 --- a/test/test.js +++ b/test/test.js @@ -3,17 +3,14 @@ var read = require('fs').readFileSync var test = require('tape') var b64url = require('base64-url') + var jwt = require('../.') -var xtend = require('xtend') var payload = { iss: 'my_issurer', aud: 'World', iat: 1400062400223, typ: '/online/transactionstatus/v2', - header: { - kid: 'TestKeyId' - }, request: { myTransactionId: '[myTransactionId]', merchantTransactionId: '[merchantTransactionId]', @@ -21,12 +18,6 @@ var payload = { } } -var extraHeaders = { - header: {kid: 'TestKeyId'} - }; - -var payloadWithHeaders = xtend(payload, extraHeaders); - var secret = 'TOPSECRETTTTT' var theToken = null var theTokenSign = null @@ -68,17 +59,6 @@ test('jwt - encode with callback / sign', function(assert) { }) }) -test('jwt + custom headers - encode with callback / sign', function(assert) { - var pem = read(__dirname + '/fixtures/test.pem').toString('ascii') - jwt.encode(pem, payloadWithHeaders, 'RS256', function(err, token) { - assert.deepEqual(err, null) - assert.ok(token) - theTokenSignWithHeaders = token - assert.deepEqual(token.split('.').length, 3) - assert.end() - }) -}) - test('jwt - encode with callback / bad algorithm', function(assert) { jwt.encode(secret, payload, 'wow', function(err) { assert.deepEqual(err.message, 'The algorithm is not supported!') @@ -103,11 +83,30 @@ test('jwt - decode with callback / sign', function(assert) { }) }) +test('jwt + custom headers - encode with callback / sign', function(assert) { + var pem = read(__dirname + '/fixtures/test.pem').toString('ascii') + var payloadAndHeaders = { + payload: payload, + header: { + kid: 'TestKeyId' + } + } + + jwt.encode(pem, payloadAndHeaders, 'RS256', function(err, token) { + assert.deepEqual(err, null) + assert.ok(token) + theTokenSignWithHeaders = token + assert.deepEqual(token.split('.').length, 3) + assert.end() + }) +}) + test('jwt + custom headers - decode with callback / sign', function(assert) { var crt = read(__dirname + '/fixtures/test.crt').toString('ascii') - jwt.decode(crt, theTokenSignWithHeaders, function(err, decodePayload) { + jwt.decode(crt, theTokenSignWithHeaders, function(err, decPayload, header) { assert.deepEqual(err, null) - assert.deepEqual(decodePayload, payloadWithHeaders) + assert.deepEqual(decPayload, payload) + assert.deepEqual(header.kid, 'TestKeyId') assert.end() }) }) @@ -184,6 +183,7 @@ test('jwt - decode with callback / bad token', function(assert) { // // without callback but returning the result // + test('jwt - encode without callback / hmac', function(assert) { var res = jwt.encode(secret, payload) assert.deepEqual(typeof res, 'object') @@ -250,4 +250,3 @@ test('should not decode for the "none" algorithm', function(assert) { assert.equal(result.error.message, 'The algorithm is not supported!') assert.end() }) -