├── .eslintrc ├── .gitignore ├── .jscsrc ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── errors.json ├── lib ├── errors.js └── jwt-utils.js ├── package.json └── test ├── benchmark └── index.js ├── environment.js └── unit ├── jwt-utils-test.js └── sampleKeys ├── README.md ├── privateKey.pem ├── privateKey2.pem ├── publicKey.pem └── publicKey2.pem /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "rules": { 6 | "no-use-before-define": [1, "nofunc"], //latedef in jshint 7 | "quotes": [2, "single"], 8 | "no-underscore-dangle": 0, 9 | "new-cap": [1, {"capIsNew": false}], 10 | "no-mixed-requires": [1, true] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | report/ 4 | site/ 5 | .idea/ 6 | .DS_Store 7 | *.log 8 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | "maximumLineLength": 120, 4 | "disallowSpacesInCallExpression": true, 5 | "validateJSDoc": null, 6 | "disallowMultipleVarDecl": null, 7 | "requireSpacesInNamedFunctionExpression": { 8 | "beforeOpeningCurlyBrace": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | script: "npm run travis" 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 1. Make a fork 4 | 5 | 2. Test the code! 6 | ```sh 7 | npm run test 8 | ``` 9 | 10 | 3. Check your coverage! 11 | ```sh 12 | npm run coverage 13 | ``` 14 | 15 | 4. Lint your code! 16 | ```sh 17 | npm run lint 18 | ``` 19 | 20 | 5. Try to squash your commits (optional) 21 | 22 | 6. Make a PR to the develop branch 23 | 24 | 7. Thanks a lot! 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2014,2015 Telefónica I+D 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jwt-utils 2 | ========= 3 | 4 | JSON Web Tokens (JWT) utils. 5 | 6 | [![npm version](https://badge.fury.io/js/jwt-utils.svg)](http://badge.fury.io/js/jwt-utils) 7 | [![Build Status](https://travis-ci.org/telefonica/node-jwt-utils.svg)](https://travis-ci.org/telefonica/node-jwt-utils) 8 | [![Coverage Status](https://img.shields.io/coveralls/telefonica/node-jwt-utils.svg)](https://coveralls.io/r/telefonica/node-jwt-utils) 9 | 10 | This module is able to parse and generate both encrypted and unencrypted JWT tokens. 11 | 12 | - A **hash key** (```hashKey```) is needed to generate a JWT signature. 13 | - An **encryption key** (```encryptKey```) is needed only if you need to deal with encrypted JWT tokens. 14 | 15 | This module only allows hexadecimal strings as hash and encryption keys. The encryption key must be 16 | a 32-length hexadecimal value (16 bytes) if the encryption algorithm is ```A128CBC```. 17 | 18 | The ```kid``` (key identifier) goes in the JWT header, and serves as a client identifier, in case you need 19 | to use different keys for different clients. 20 | 21 | ## Installation 22 | 23 | ```bash 24 | npm install jwt-utils 25 | ``` 26 | 27 | ## Examples 28 | 29 | ```javascript 30 | var config = { expiration: 600}; 31 | var jwt = require('jwt-utils')(config); 32 | 33 | // Encode JWT token 34 | jwt.buildJWT(payload, header, hashKey, function(err, token) { 35 | if (!err) { 36 | console.log(token); // something in the format "aaa.bbb.ccc" 37 | } 38 | }); 39 | 40 | // Decode JWT token 41 | jwt.readJWT(token, hashKey, function(err, token) { 42 | if (!err) { 43 | console.log(token.payload); 44 | console.log(token.header); 45 | } 46 | }); 47 | 48 | // Encrypt JWT token 49 | jwt.buildJWTEncrypted(payload, header, encryptKey, hashKey, function(err, token) { 50 | if (!err) { 51 | console.log(token); // something in the format "aaa.bbb.ccc.ddd.eee" 52 | } 53 | }); 54 | 55 | // Decrypt JWT token 56 | jwt.readJWTEncrypted(token, encryptKey, hashKey, function(err, token) { 57 | if (!err) { 58 | console.log(token.payload); 59 | console.log(token.header); 60 | } 61 | }); 62 | 63 | // Read JWT header 64 | jwt.readJWTHeader(token, function(err, header) { 65 | if (!err) { 66 | console.log(header); 67 | } 68 | }); 69 | ``` 70 | 71 | ## Configuration 72 | 73 | * expiration: Expiration time in seconds to generate exp field in token (this functionality can be disabled by setting the value to 0). 74 | * futureTolerance: Tolerance seconds to find out if a jwt comes from the future or not. 75 | 76 | ## Development information 77 | 78 | ``` 79 | npm test 80 | ``` 81 | 82 | ## Errors: 83 | The following file has registered the error of this library: 84 | [errors.json](errors.json) 85 | 86 | ## License 87 | 88 | Copyright 2015 [Telefónica Investigación y Desarrollo, S.A.U](http://www.tid.es) 89 | 90 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 91 | 92 | http://www.apache.org/licenses/LICENSE-2.0 93 | 94 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 95 | -------------------------------------------------------------------------------- /errors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "errors": [ 4 | { 5 | "type": "MISSING_REQUIRED_KID", 6 | "fields": [ 7 | { 8 | "key": "message", 9 | "value": "Missing required kid.", 10 | "doc": null 11 | }, 12 | { 13 | "key": "level", 14 | "value": "info", 15 | "doc": null 16 | }, 17 | { 18 | "key": "namespace", 19 | "value": "JWTHEADER", 20 | "doc": null 21 | } 22 | ], 23 | "doc": { 24 | "description": "The JWT header does not have the kid field.", 25 | "tags": [] 26 | } 27 | }, 28 | { 29 | "type": "ALGORITHM_NOT_SUPPORTED", 30 | "fields": [ 31 | { 32 | "key": "message", 33 | "value": "Algorithm not supported.", 34 | "doc": null 35 | }, 36 | { 37 | "key": "level", 38 | "value": "info", 39 | "doc": null 40 | }, 41 | { 42 | "key": "namespace", 43 | "value": "JWTHEADER", 44 | "doc": null 45 | } 46 | ], 47 | "doc": { 48 | "description": "The JWT header has an algorithm not supported.", 49 | "tags": [] 50 | } 51 | }, 52 | { 53 | "type": "INVALID_HEADER", 54 | "fields": [ 55 | { 56 | "key": "message", 57 | "value": "Invalid header.", 58 | "doc": null 59 | }, 60 | { 61 | "key": "level", 62 | "value": "info", 63 | "doc": null 64 | }, 65 | { 66 | "key": "namespace", 67 | "value": "JWTHEADER", 68 | "doc": null 69 | } 70 | ], 71 | "doc": { 72 | "description": "The JWT cannot be decoded.", 73 | "tags": [] 74 | } 75 | }, 76 | { 77 | "type": "MISSING_REQUIRED_HEADER", 78 | "fields": [ 79 | { 80 | "key": "message", 81 | "value": "Missing required header.", 82 | "doc": null 83 | }, 84 | { 85 | "key": "level", 86 | "value": "info", 87 | "doc": null 88 | }, 89 | { 90 | "key": "namespace", 91 | "value": "JWTHEADER", 92 | "doc": null 93 | } 94 | ], 95 | "doc": { 96 | "description": "The header argument is null or undefined.", 97 | "tags": [] 98 | } 99 | }, 100 | { 101 | "type": "MISSING_REQUIRED_KEY", 102 | "fields": [ 103 | { 104 | "key": "message", 105 | "value": "Missing required key.", 106 | "doc": null 107 | }, 108 | { 109 | "key": "level", 110 | "value": "info", 111 | "doc": null 112 | }, 113 | { 114 | "key": "namespace", 115 | "value": "JWT", 116 | "doc": null 117 | } 118 | ], 119 | "doc": { 120 | "description": "The key value is missing, and it is required.", 121 | "tags": [] 122 | } 123 | }, 124 | { 125 | "type": "SEGMENTS_NUMBER_ERROR", 126 | "fields": [ 127 | { 128 | "key": "message", 129 | "value": "Not enough or too many segments {1}.", 130 | "doc": null 131 | }, 132 | { 133 | "key": "level", 134 | "value": "info", 135 | "doc": null 136 | }, 137 | { 138 | "key": "namespace", 139 | "value": "JWT", 140 | "doc": null 141 | } 142 | ], 143 | "doc": { 144 | "description": "The number of segments of the jwt are invalid.", 145 | "tags": [] 146 | } 147 | }, 148 | { 149 | "type": "WRONG_TOKEN_SIGNATURE", 150 | "fields": [ 151 | { 152 | "key": "message", 153 | "value": "Wrong token signature", 154 | "doc": null 155 | }, 156 | { 157 | "key": "level", 158 | "value": "info", 159 | "doc": null 160 | }, 161 | { 162 | "key": "namespace", 163 | "value": "JWT", 164 | "doc": null 165 | } 166 | ], 167 | "doc": { 168 | "description": "The hash key or the hash are invalid.", 169 | "tags": [] 170 | } 171 | }, 172 | { 173 | "type": "INVALID_FOURTH_SEGMENT", 174 | "fields": [ 175 | { 176 | "key": "message", 177 | "value": "The fourth segment must not be null.", 178 | "doc": null 179 | }, 180 | { 181 | "key": "level", 182 | "value": "info", 183 | "doc": null 184 | }, 185 | { 186 | "key": "namespace", 187 | "value": "JWT", 188 | "doc": null 189 | } 190 | ], 191 | "doc": { 192 | "description": "The value of the fourth segment of an encrypted jwt is invalid.", 193 | "tags": [] 194 | } 195 | }, 196 | { 197 | "type": "DECRYPTION_ERROR", 198 | "fields": [ 199 | { 200 | "key": "message", 201 | "value": "JWT cannot be decrypted{1}", 202 | "doc": null 203 | }, 204 | { 205 | "key": "level", 206 | "value": "info", 207 | "doc": null 208 | }, 209 | { 210 | "key": "namespace", 211 | "value": "JWT", 212 | "doc": null 213 | } 214 | ], 215 | "doc": { 216 | "description": "Error decrypting an jwt encrypted.", 217 | "tags": [] 218 | } 219 | }, 220 | { 221 | "type": "ENCRYPTION_ERROR", 222 | "fields": [ 223 | { 224 | "key": "message", 225 | "value": "Encryption error: {1}", 226 | "doc": null 227 | }, 228 | { 229 | "key": "level", 230 | "value": "info", 231 | "doc": null 232 | }, 233 | { 234 | "key": "namespace", 235 | "value": "JWT", 236 | "doc": null 237 | } 238 | ], 239 | "doc": { 240 | "description": "Error when it tries to encrypt in order to generate a jwt encrypted.", 241 | "tags": [] 242 | } 243 | }, 244 | { 245 | "type": "INVALID_IAT", 246 | "fields": [ 247 | { 248 | "key": "message", 249 | "value": "JWT iat is not valid.", 250 | "doc": null 251 | }, 252 | { 253 | "key": "level", 254 | "value": "info", 255 | "doc": null 256 | }, 257 | { 258 | "key": "namespace", 259 | "value": "JWTPAYLOAD", 260 | "doc": null 261 | } 262 | ], 263 | "doc": { 264 | "description": "Invalid iat value.", 265 | "tags": [] 266 | } 267 | }, 268 | { 269 | "type": "MISSING_IAT", 270 | "fields": [ 271 | { 272 | "key": "message", 273 | "value": "iat field is missing in the JWT payload.", 274 | "doc": null 275 | }, 276 | { 277 | "key": "level", 278 | "value": "info", 279 | "doc": null 280 | }, 281 | { 282 | "key": "namespace", 283 | "value": "JWTPAYLOAD", 284 | "doc": null 285 | } 286 | ], 287 | "doc": { 288 | "description": "Iat is required.", 289 | "tags": [] 290 | } 291 | }, 292 | { 293 | "type": "INVALID_EXP", 294 | "fields": [ 295 | { 296 | "key": "message", 297 | "value": "JWT exp is not valid.", 298 | "doc": null 299 | }, 300 | { 301 | "key": "level", 302 | "value": "info", 303 | "doc": null 304 | }, 305 | { 306 | "key": "namespace", 307 | "value": "JWTPAYLOAD", 308 | "doc": null 309 | } 310 | ], 311 | "doc": { 312 | "description": "Invalid exp value.", 313 | "tags": [] 314 | } 315 | }, 316 | { 317 | "type": "EMPTY_PAYLOAD", 318 | "fields": [ 319 | { 320 | "key": "message", 321 | "value": "Empty payload", 322 | "doc": null 323 | }, 324 | { 325 | "key": "level", 326 | "value": "info", 327 | "doc": null 328 | }, 329 | { 330 | "key": "namespace", 331 | "value": "JWTPAYLOAD", 332 | "doc": null 333 | } 334 | ], 335 | "doc": { 336 | "description": "The payload of the token is empty.", 337 | "tags": [] 338 | } 339 | }, 340 | { 341 | "type": "EXPIRED_JWT", 342 | "fields": [ 343 | { 344 | "key": "message", 345 | "value": "JWT has expired.", 346 | "doc": null 347 | }, 348 | { 349 | "key": "level", 350 | "value": "info", 351 | "doc": null 352 | }, 353 | { 354 | "key": "namespace", 355 | "value": "JWTPAYLOAD", 356 | "doc": null 357 | } 358 | ], 359 | "doc": { 360 | "description": "The jwt has expired taking into account the configured value.", 361 | "tags": [] 362 | } 363 | }, 364 | { 365 | "type": "NO_FRESH_JWT", 366 | "fields": [ 367 | { 368 | "key": "message", 369 | "value": "JWT has discarded by client. No fresh JWT.", 370 | "doc": null 371 | }, 372 | { 373 | "key": "level", 374 | "value": "info", 375 | "doc": null 376 | }, 377 | { 378 | "key": "namespace", 379 | "value": "JWTPAYLOAD", 380 | "doc": null 381 | } 382 | ], 383 | "doc": { 384 | "description": "The jwt has discarded by client. Current time is greater than client expiration.", 385 | "tags": [] 386 | } 387 | }, 388 | { 389 | "type": "FUTURE_JWT", 390 | "fields": [ 391 | { 392 | "key": "message", 393 | "value": "JWT belongs to the future.", 394 | "doc": null 395 | }, 396 | { 397 | "key": "level", 398 | "value": "info", 399 | "doc": null 400 | }, 401 | { 402 | "key": "namespace", 403 | "value": "JWTPAYLOAD", 404 | "doc": null 405 | } 406 | ], 407 | "doc": { 408 | "description": "The jwt has an invalid date. Perhaps the machine clocks has some differences or you may suffering an attack.", 409 | "tags": [] 410 | } 411 | } 412 | ], 413 | "namespace": null, 414 | "doc": { 415 | "description": "JWT Utils errors.", 416 | "tags": [] 417 | } 418 | } 419 | ] -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | var therror = require('therror'); 21 | 22 | /** 23 | * JWT Utils errors. 24 | */ 25 | module.exports = therror.register({ 26 | 27 | /** 28 | * The JWT header does not have the kid field. 29 | */ 30 | MISSING_REQUIRED_KID: { 31 | message: 'Missing required kid.', 32 | level: 'info', 33 | namespace: 'JWTHEADER' 34 | }, 35 | 36 | /** 37 | * The JWT header has an algorithm not supported. 38 | */ 39 | ALGORITHM_NOT_SUPPORTED: { 40 | message: 'Algorithm not supported.', 41 | level: 'info', 42 | namespace: 'JWTHEADER' 43 | }, 44 | 45 | /** 46 | * The JWT cannot be decoded. 47 | */ 48 | INVALID_HEADER: { 49 | message: 'Invalid header.', 50 | level: 'info', 51 | namespace: 'JWTHEADER' 52 | }, 53 | 54 | /** 55 | * The header argument is null or undefined. 56 | */ 57 | MISSING_REQUIRED_HEADER: { 58 | message: 'Missing required header.', 59 | level: 'info', 60 | namespace: 'JWTHEADER' 61 | }, 62 | 63 | /** 64 | * The key value is missing, and it is required. 65 | */ 66 | MISSING_REQUIRED_KEY: { 67 | message: 'Missing required key.', 68 | level: 'info', 69 | namespace: 'JWT' 70 | }, 71 | 72 | /** 73 | * The number of segments of the jwt are invalid. 74 | */ 75 | SEGMENTS_NUMBER_ERROR: { 76 | message: 'Not enough or too many segments {1}.', 77 | level: 'info', 78 | namespace: 'JWT' 79 | }, 80 | 81 | /** 82 | * The hash key or the hash are invalid. 83 | */ 84 | WRONG_TOKEN_SIGNATURE: { 85 | message: 'Wrong token signature', 86 | level: 'info', 87 | namespace: 'JWT' 88 | }, 89 | 90 | /** 91 | * The value of the fourth segment of an encrypted jwt is invalid. 92 | */ 93 | INVALID_FOURTH_SEGMENT: { 94 | message: 'The fourth segment must not be null.', 95 | level: 'info', 96 | namespace: 'JWT' 97 | }, 98 | 99 | /** 100 | * Error decrypting an jwt encrypted. 101 | */ 102 | DECRYPTION_ERROR: { 103 | message: 'JWT cannot be decrypted{1}', 104 | level: 'info', 105 | namespace: 'JWT' 106 | }, 107 | 108 | /** 109 | * Error when it tries to encrypt in order to generate a jwt encrypted. 110 | */ 111 | ENCRYPTION_ERROR: { 112 | message: 'Encryption error: {1}', 113 | level: 'info', 114 | namespace: 'JWT' 115 | }, 116 | 117 | /** 118 | * Invalid iat value. 119 | */ 120 | INVALID_IAT: { 121 | message: 'JWT iat is not valid.', 122 | level: 'info', 123 | namespace: 'JWTPAYLOAD' 124 | }, 125 | 126 | /** 127 | * Iat is required. 128 | */ 129 | MISSING_IAT: { 130 | message: 'iat field is missing in the JWT payload.', 131 | level: 'info', 132 | namespace: 'JWTPAYLOAD' 133 | }, 134 | 135 | /** 136 | * Invalid exp value. 137 | */ 138 | INVALID_EXP: { 139 | message: 'JWT exp is not valid.', 140 | level: 'info', 141 | namespace: 'JWTPAYLOAD' 142 | }, 143 | 144 | /** 145 | * The payload of the token is empty. 146 | */ 147 | EMPTY_PAYLOAD: { 148 | message: 'Empty payload', 149 | level: 'info', 150 | namespace: 'JWTPAYLOAD' 151 | }, 152 | 153 | /** 154 | * The jwt has expired taking into account the configured value. 155 | */ 156 | EXPIRED_JWT: { 157 | message: 'JWT has expired.', 158 | level: 'info', 159 | namespace: 'JWTPAYLOAD' 160 | }, 161 | 162 | /** 163 | * The jwt has discarded by client. Current time is greater than client expiration. 164 | */ 165 | NO_FRESH_JWT: { 166 | message: 'JWT has discarded by client. No fresh JWT.', 167 | level: 'info', 168 | namespace: 'JWTPAYLOAD' 169 | }, 170 | 171 | /** 172 | * The jwt has an invalid date. Perhaps the machine clocks has some differences or you may suffering an attack. 173 | */ 174 | FUTURE_JWT: { 175 | message: 'JWT belongs to the future.', 176 | level: 'info', 177 | namespace: 'JWTPAYLOAD' 178 | } 179 | }); 180 | -------------------------------------------------------------------------------- /lib/jwt-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | 'use strict'; 19 | 20 | var crypto = require('crypto'); 21 | var debugLib = require('debug'), 22 | _ = require('underscore'), 23 | jwa = require('jwa'); 24 | var errors = require('./errors'); 25 | 26 | var debug = debugLib('tef:base:jwtUtils'); 27 | 28 | var hashAlgorithms = { 29 | HS256: 'sha256', 30 | RS256: 'rsa-sha256', 31 | HS512: 'sha512' 32 | }; 33 | 34 | var cipherMap = { 35 | A128CBC: 'aes-128-cbc', 36 | A256CBC: 'aes-256-cbc' 37 | }; 38 | 39 | var authenticationTagBits = { 40 | 'sha256': 16, 41 | 'sha512': 32 42 | }; 43 | 44 | /*jshint -W069 */ 45 | /*jshint -W072*/ 46 | /*jshint -W098*/ 47 | /*jshint -W117*/ 48 | var DEFAULT_CONFIG = { 49 | //Number of seconds to consider a jwt expired 50 | expiration: 600, 51 | futureTolerance: 4 52 | }; 53 | 54 | /** 55 | * The exported API 56 | * @type {*} 57 | * @return {Object} 58 | */ 59 | module.exports = function(configuration) { 60 | 61 | var config = {}; 62 | 63 | function base64urlEscape(str) { 64 | return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); 65 | } 66 | 67 | function base64urlUnescape(str) { 68 | str += new Array(5 - str.length % 4).join('='); 69 | return str.replace(/\-/g, '+').replace(/_/g, '/'); 70 | } 71 | 72 | function base64urlEncode(str) { 73 | return base64urlEscape(new Buffer(str).toString('base64')); 74 | } 75 | 76 | function base64urlDecode(str) { 77 | return new Buffer(base64urlUnescape(str), 'base64'); 78 | } 79 | 80 | function encodeBase64url(base64) { 81 | return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, ''); 82 | } 83 | 84 | function convertSlowBufToBuf(slowBuffer) { 85 | var buffer = new Buffer(slowBuffer.length); 86 | slowBuffer.copy(buffer); 87 | return buffer; 88 | } 89 | 90 | function readJWTHeader(base64header) { 91 | var headerStr = base64urlDecode(base64header); 92 | var header = JSON.parse(headerStr); 93 | return header; 94 | } 95 | 96 | function getEncryptionAndHashAlgoritms(encHeaderValue) { 97 | var algs = encHeaderValue.split('-'); 98 | var cipherAlgorithm = cipherMap[algs[0]]; 99 | var hashAlgorithm = hashAlgorithms[algs[1]]; 100 | 101 | if (!cipherAlgorithm) { 102 | throw errors.ALGORITHM_NOT_SUPPORTED('Invalid encryption algorithm: ' + algs[0]); 103 | } 104 | 105 | if (!hashAlgorithm) { 106 | throw errors.ALGORITHM_NOT_SUPPORTED('Invalid hash algorithm: ' + algs[1]); 107 | } 108 | return { 109 | cipherAlgorithm: cipherAlgorithm, 110 | hashAlgorithm: hashAlgorithm 111 | }; 112 | } 113 | 114 | function encodeJWT(payload, header, key) { 115 | if (!key) { 116 | throw errors.MISSING_REQUIRED_KEY(); 117 | } 118 | 119 | if (!hashAlgorithms[header.alg]) { 120 | throw errors.ALGORITHM_NOT_SUPPORTED(); 121 | } 122 | 123 | // header, typ is fixed value. 124 | if (!header.kid) { 125 | throw errors.MISSING_REQUIRED_KID(); 126 | } 127 | 128 | // create segments, all segment should be base64 string 129 | var segments = []; 130 | segments.push(base64urlEncode(JSON.stringify(header))); 131 | segments.push(base64urlEncode(JSON.stringify(payload))); 132 | var signer = jwa(header.alg); 133 | //If we use RS256, the key is the private Key 134 | if (header.alg.indexOf('HS') === 0) { 135 | key = new Buffer(key, 'hex'); 136 | } 137 | segments.push(signer.sign(segments.join('.'), key)); 138 | 139 | return segments.join('.'); 140 | } 141 | 142 | function decodeJWT(token, key) { 143 | var segments = token.split('.'); 144 | if (segments.length !== 3) { 145 | throw errors.SEGMENTS_NUMBER_ERROR(segments.length); 146 | } 147 | 148 | var header = readJWTHeader(segments[0]); 149 | 150 | var algorithm = header.alg || 'HS256'; 151 | 152 | if (!hashAlgorithms[header.alg]) { 153 | throw errors.ALGORITHM_NOT_SUPPORTED(); 154 | } 155 | 156 | var signer = jwa(algorithm); 157 | if (algorithm.indexOf('HS') === 0) { 158 | key = new Buffer(key, 'hex'); 159 | } 160 | if (!signer.verify(segments[0] + '.' + segments[1], segments[2], key)) { 161 | throw errors.WRONG_TOKEN_SIGNATURE(); 162 | } 163 | 164 | var payloadStr = base64urlDecode(segments[1]); 165 | 166 | return { 167 | header: header, 168 | payload: JSON.parse(payloadStr) 169 | }; 170 | } 171 | 172 | function decryptJWT(token, encKey, hashKey) { 173 | var segments = token.split('.'); 174 | if (segments.length !== 5) { 175 | throw errors.SEGMENTS_NUMBER_ERROR(segments.length); 176 | } 177 | 178 | var header = readJWTHeader(segments[0]); 179 | 180 | if (!header.kid) { 181 | throw errors.MISSING_REQUIRED_KID(); 182 | } 183 | 184 | var algorithms = getEncryptionAndHashAlgoritms(header.enc); 185 | 186 | var initializationVector = null; 187 | if (segments[2]) { 188 | initializationVector = base64urlDecode(segments[2]); 189 | } 190 | 191 | var cypherText = null; 192 | if (segments[3] === null || segments[3] === '') { 193 | throw errors.INVALID_FOURTH_SEGMENT(); 194 | } else { 195 | cypherText = base64urlDecode(segments[3]); 196 | } 197 | 198 | var origAuthenticationTag = null; 199 | if (segments[4]) { 200 | origAuthenticationTag = segments[4]; 201 | } 202 | 203 | var encKeyBuffer = new Buffer(encKey, 'hex'); 204 | var result, hashBuf; 205 | try { 206 | var decipher = crypto.createDecipheriv(algorithms.cipherAlgorithm, encKeyBuffer, initializationVector); 207 | 208 | decipher.setAutoPadding(true); 209 | 210 | var mainBody = decipher.update(cypherText); 211 | var endBody = decipher.final(); 212 | result = Buffer.concat([convertSlowBufToBuf(mainBody), convertSlowBufToBuf(endBody)]).toString(); 213 | 214 | var b64Header = new Buffer(segments[0]); 215 | var iv = new Buffer(initializationVector); 216 | var encryptedBody = new Buffer(cypherText); 217 | 218 | // create al vector 219 | var al = new Buffer(4); 220 | al.writeInt32BE(b64Header.length * 8, 0); 221 | 222 | var buf4Clear = new Buffer(4); 223 | buf4Clear.fill(0); 224 | var alResult = Buffer.concat([buf4Clear, al]); 225 | 226 | var authTag = new Buffer(Buffer.concat([b64Header, iv, encryptedBody, alResult])); 227 | 228 | var hashKeyBuffer = new Buffer(hashKey, 'hex'); 229 | var authTagHash = crypto.createHmac(algorithms.hashAlgorithm, hashKeyBuffer).update(authTag).digest(); 230 | 231 | var authTagHashBuf = convertSlowBufToBuf(authTagHash); 232 | hashBuf = authTagHashBuf.slice(0, authenticationTagBits[algorithms.hashAlgorithm]); 233 | } catch (err) { 234 | debug('Error Decrypt: ', err.message); 235 | throw errors.DECRYPTION_ERROR(': ' + err.message); 236 | } 237 | 238 | if (base64urlEscape(hashBuf.toString('base64')) !== origAuthenticationTag) { 239 | throw errors.DECRYPTION_ERROR(''); 240 | } else { 241 | return { 242 | header: header, 243 | payload: JSON.parse(result) 244 | }; 245 | } 246 | } 247 | 248 | function generateRandomInitializationVector() { 249 | var iv = []; 250 | for (var i = 0; i < 16; i++) { 251 | iv.push(Math.round(Math.random() * 255)); 252 | } 253 | 254 | return new Buffer(iv); 255 | } 256 | 257 | function encryptJWT(payload, header, encKey, hashKey) { 258 | 259 | header = _.defaults(header, { 260 | alg: 'dir', 261 | enc: 'A256CBC-HS512' 262 | }); 263 | 264 | if (!header.kid) { 265 | throw errors.MISSING_REQUIRED_KID(); 266 | } 267 | 268 | var algorithms = getEncryptionAndHashAlgoritms(header.enc); 269 | 270 | var iv = generateRandomInitializationVector(); 271 | 272 | var segments = []; 273 | 274 | var b64Header = encodeBase64url(new Buffer(JSON.stringify(header)).toString('base64')); 275 | segments.push(b64Header); 276 | 277 | var b64Jek = ''; 278 | segments.push(b64Jek); 279 | 280 | var b64IV = encodeBase64url(iv.toString('base64')); 281 | segments.push(b64IV); 282 | 283 | var encKeyBuffer = new Buffer(encKey, 'hex'); 284 | var cipher = crypto.createCipheriv(algorithms.cipherAlgorithm, encKeyBuffer, iv); 285 | 286 | var cipherTextBegin = cipher.update(new Buffer(JSON.stringify(payload), 'utf8')); 287 | 288 | var cipherTextEnd = cipher.final(); 289 | 290 | var cipherTextBuf = Buffer.concat([convertSlowBufToBuf(cipherTextBegin), convertSlowBufToBuf(cipherTextEnd)]); 291 | 292 | var b64CipherText = encodeBase64url(cipherTextBuf.toString('base64')); 293 | segments.push(b64CipherText); 294 | 295 | // Calculate AuthTag 296 | 297 | var b64HeaderBuf = new Buffer(b64Header); 298 | 299 | // We have iv Buffer and cipherTextBuf Buffer calculated. 300 | 301 | var al = new Buffer(4); 302 | al.writeInt32BE(b64HeaderBuf.length * 8, 0); 303 | 304 | var buf4Clear = new Buffer(4); 305 | buf4Clear.fill(0); 306 | 307 | var alResult = Buffer.concat([buf4Clear, al]); 308 | 309 | var authTag = new Buffer(Buffer.concat([b64HeaderBuf, iv, cipherTextBuf, alResult])); 310 | 311 | var hashKeyBuffer = new Buffer(hashKey, 'hex'); 312 | var base64str = crypto.createHmac(algorithms.hashAlgorithm, hashKeyBuffer).update(authTag).digest(); 313 | 314 | var buf = convertSlowBufToBuf(base64str); 315 | 316 | var result = buf.slice(0, authenticationTagBits[algorithms.hashAlgorithm]); 317 | 318 | var b64AuthTag = encodeBase64url(result.toString('base64')); 319 | segments.push(b64AuthTag); 320 | 321 | return segments.join('.'); 322 | } 323 | 324 | function makePayload(payload) { 325 | if (config.expiration !== 0) { 326 | payload.iat = payload.iat || parseInt(new Date().getTime() / 1000, 10); 327 | payload.exp = payload.exp || payload.iat + config.expiration; 328 | } 329 | return payload; 330 | } 331 | 332 | function checkPayload(token, cb) { 333 | var payload = token.payload; 334 | if (!payload) { 335 | return cb(errors.EMPTY_PAYLOAD()); 336 | } 337 | 338 | var currentDate = parseInt(Date.now() / 1000, 10); 339 | 340 | if (payload.iat) { 341 | var iatParsed = parseInt(payload.iat, 10); 342 | if (isNaN(iatParsed) || payload.iat !== iatParsed) { 343 | return cb(errors.INVALID_IAT()); 344 | } 345 | if (payload.iat > currentDate + config.futureTolerance) { 346 | return cb(errors.FUTURE_JWT()); 347 | } 348 | } else { 349 | if (payload.hasOwnProperty('iat')) { 350 | return cb(errors.INVALID_IAT()); 351 | } else if (config.expiration !== 0) { 352 | return cb(errors.MISSING_IAT(), token); 353 | } 354 | } 355 | 356 | if (config.expiration !== 0) { 357 | var expLimit = iatParsed + config.expiration; 358 | if (expLimit <= currentDate) { 359 | return cb(errors.NO_FRESH_JWT(), token); 360 | } 361 | } 362 | 363 | if (payload.exp) { 364 | var expParsed = parseInt(payload.exp, 10); 365 | 366 | if (isNaN(expParsed) || payload.exp !== expParsed) { 367 | return cb(errors.INVALID_EXP()); 368 | } 369 | 370 | if (payload.exp <= currentDate) { 371 | return cb(errors.EXPIRED_JWT(), token); 372 | } 373 | } else { 374 | if (payload.hasOwnProperty('exp')) { 375 | return cb(errors.INVALID_EXP()); 376 | } 377 | } 378 | 379 | return cb(null, token); 380 | } 381 | 382 | if (configuration) { 383 | if (configuration.expiration || configuration.expiration === 0) { 384 | config.expiration = configuration.expiration; 385 | } else { 386 | config.expiration = DEFAULT_CONFIG.expiration; 387 | } 388 | 389 | config.futureTolerance = configuration.futureTolerance || DEFAULT_CONFIG.futureTolerance; 390 | } else { 391 | config = DEFAULT_CONFIG; 392 | } 393 | return { 394 | buildJWTEncrypted: function(payload, header, encKey, hashKey, cb) { 395 | if (!header) { 396 | return cb(errors.MISSING_REQUIRED_HEADER()); 397 | } 398 | var result; 399 | try { 400 | var completePayload = makePayload(payload); 401 | result = encryptJWT(completePayload, header, encKey, hashKey); 402 | } catch (err) { 403 | if (err.namespace && err.name) { 404 | return cb(err); 405 | } else { 406 | return cb(errors.ENCRYPTION_ERROR(err.message)); 407 | } 408 | 409 | } 410 | cb(null, result); 411 | }, 412 | 413 | buildJWT: function(payload, header, key, cb) { 414 | if (!header) { 415 | return cb(errors.MISSING_REQUIRED_HEADER()); 416 | } 417 | if (!header.alg) { 418 | header.alg = 'HS256'; 419 | } 420 | var result; 421 | try { 422 | var completePayload = makePayload(payload); 423 | result = encodeJWT(completePayload, header, key); 424 | } catch (err) { 425 | return cb(err); 426 | } 427 | cb(null, result); 428 | }, 429 | 430 | readJWTEncrypted: function(jwtToken, encKey, hashKey, cb) { 431 | var result; 432 | try { 433 | result = decryptJWT(jwtToken, encKey, hashKey); 434 | } catch (err) { 435 | return cb(err); 436 | } 437 | debug('Decrypted data is %j', result); 438 | checkPayload(result, cb); 439 | }, 440 | 441 | readJWT: function(jwtToken, key, cb) { 442 | var result; 443 | try { 444 | result = decodeJWT(jwtToken, key); 445 | } catch (err) { 446 | return cb(err); 447 | } 448 | debug('Unencoded data is %j', result); 449 | checkPayload(result, cb); 450 | }, 451 | 452 | readJWTHeader: function(jwtToken, cb) { 453 | var result; 454 | try { 455 | var segments = jwtToken.split('.'); 456 | result = readJWTHeader(segments[0]); 457 | } catch (err) { 458 | return cb(errors.INVALID_HEADER(err.message)); 459 | } 460 | debug('Header is %j', result); 461 | cb(null, result); 462 | } 463 | }; 464 | }; 465 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jwt-utils", 3 | "description": "JSON Web Tokens (JWT) utils", 4 | "version": "1.0.5", 5 | "license": "Apache-2.0", 6 | "author": { 7 | "name": "Guido García Bernardo", 8 | "email": "guido.garciabernardo@telefonica.com" 9 | }, 10 | "contributors": [ 11 | "Juan Antonio Hernando Labajo ", 12 | "Jorge Lorenzo Gallardo " 13 | ], 14 | "main": "lib/jwt-utils", 15 | "engines": { 16 | "node": ">= 0.10.26" 17 | }, 18 | "scripts": { 19 | "test": "mocha -R spec test/environment.js test/unit/*-test.js", 20 | "coverage": "istanbul cover ./node_modules/mocha/bin/_mocha -- -R dot test/environment.js test/unit/*-test.js", 21 | "lint": "jscs lib && eslint lib", 22 | "prepublish": "npm run test && npm run lint", 23 | "travis": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec test/environment.js test/*-test.js && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git@github.com:telefonica/node-jwt-utils.git" 28 | }, 29 | "keywords": [ 30 | "jwt", 31 | "token", 32 | "tdaf" 33 | ], 34 | "devDependencies": { 35 | "chai": "^3.0.0", 36 | "coveralls": "^2.11.2", 37 | "istanbul": "^0.3.16", 38 | "mocha": "^2.2.5", 39 | "proxyquire": "^1.5.0", 40 | "should": "^7.0.1", 41 | "sinon": "~1.15.3", 42 | "sinon-chai": "^2.8.0", 43 | "supertest": "^1.0.1", 44 | "xunit-file": "^0.0.6", 45 | "jscs": "^1.13.1", 46 | "eslint": "^0.23.0" 47 | }, 48 | "dependencies": { 49 | "debug": "^2.2.0", 50 | "therror": "^0.2.0", 51 | "jwa": "^1.0.0", 52 | "underscore": "^1.8.3" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/benchmark/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var jwt = require('../../index')(); 4 | 5 | /*jshint -W069 */ 6 | /*jshint -W106 */ 7 | /*jshint -W072*/ 8 | /*jshint -W098*/ 9 | /*jshint -W117*/ 10 | 11 | var HEADER = { 12 | kid: 'benchmark', 13 | corr: 'benchmark-correlator' 14 | }; 15 | 16 | var PAYLOAD = { 17 | nonce: '1', 18 | client_id: 'benchmark', 19 | redirect_uri: 'http://localhost', 20 | iss: 'http://localhost:6543/benchmark', 21 | aud: 'http://localhost:6543', 22 | acr_values: '2 3' 23 | }; 24 | 25 | var SECRET = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923'; 26 | 27 | var TOTAL_TIMES = 2000; 28 | 29 | var timesEncrypted = 0; 30 | 31 | var start; 32 | 33 | function benchmarkEncryptedTokens() { 34 | jwt.buildJWTEncrypted(PAYLOAD, HEADER, SECRET, SECRET, function(err, token) { 35 | timesEncrypted = timesEncrypted + 1; 36 | if (timesEncrypted === TOTAL_TIMES) { 37 | var elapsed = Date.now() - start; 38 | console.log('%d encrypted tokens generated in %d millis', TOTAL_TIMES, elapsed); 39 | } else { 40 | benchmarkEncryptedTokens(); 41 | } 42 | }); 43 | } 44 | 45 | var timesUnencrypted = 0; 46 | 47 | function benchmarkUnencryptedTokens() { 48 | jwt.buildJWT(PAYLOAD, HEADER, SECRET, function(err, token) { 49 | timesUnencrypted = timesUnencrypted + 1; 50 | if (timesUnencrypted === TOTAL_TIMES) { 51 | var elapsed = Date.now() - start; 52 | console.log('%d unencrypted tokens generated in %d millis', TOTAL_TIMES, elapsed); 53 | } else { 54 | benchmarkUnencryptedTokens(); 55 | } 56 | }); 57 | } 58 | 59 | start = Date.now(); 60 | benchmarkEncryptedTokens(); 61 | 62 | start = Date.now(); 63 | benchmarkUnencryptedTokens(); 64 | -------------------------------------------------------------------------------- /test/environment.js: -------------------------------------------------------------------------------- 1 | var sinon = require ('sinon'), 2 | chai = require ('chai'), 3 | sinonChai = require('sinon-chai'), 4 | utils = require('chai/lib/chai/utils'); 5 | 6 | chai.use(sinonChai); 7 | 8 | global.expect = chai.expect; 9 | 10 | beforeEach(function(){ 11 | global.sinon = sinon.sandbox.create(); 12 | }); 13 | 14 | afterEach(function(){ 15 | global.sinon.restore(); 16 | }); 17 | 18 | utils.addMethod(chai.Assertion.prototype, 'apiError', function(errorIdObj) { 19 | var obj = utils.flag(this, 'object'); 20 | new chai.Assertion(obj).to.exist; 21 | new chai.Assertion(obj.namespace).to.be.equal(errorIdObj.namespace); 22 | new chai.Assertion(obj.name).to.be.equal(errorIdObj.name); 23 | }); 24 | -------------------------------------------------------------------------------- /test/unit/jwt-utils-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var jwt = require('../../lib/jwt-utils'); 4 | var errors = require('../../lib/errors'); 5 | var fs = require('fs'); 6 | 7 | /*jshint -W098 */ 8 | describe('Jwt Utils Tests', function() { 9 | var jwtUtils; 10 | 11 | beforeEach(function() { 12 | jwtUtils = jwt(); 13 | }); 14 | 15 | it('should be error when header is null', function() { 16 | jwtUtils.buildJWT({'payload': 'payloadData'}, null, 'hashKey', function(err, token) { 17 | expect(err).to.be.apiError(errors.MISSING_REQUIRED_HEADER()); 18 | expect(token).not.to.exist; 19 | }); 20 | }); 21 | 22 | it('should be error when header has an invalid algorithm', function() { 23 | jwtUtils.buildJWT({'payload': 'payloadData'}, {alg: 'HS4353'}, 'hashKey', function(err, token) { 24 | expect(err).to.be.apiError(errors.ALGORITHM_NOT_SUPPORTED()); 25 | expect(token).not.to.exist; 26 | }); 27 | }); 28 | 29 | it('should be error when key is null', function() { 30 | jwtUtils.buildJWT({'payload': 'payloadData'}, {}, null, function(err, token) { 31 | expect(err).to.be.apiError(errors.MISSING_REQUIRED_KEY()); 32 | expect(token).not.to.exist; 33 | }); 34 | }); 35 | 36 | it('should be error when kid is null', function() { 37 | jwtUtils.buildJWT({'payload': 'payloadData'}, {}, 'hashKey', function(err, token) { 38 | expect(err).to.be.apiError(errors.MISSING_REQUIRED_KID()); 39 | expect(token).not.to.exist; 40 | }); 41 | }); 42 | 43 | it('should be generate a token with key, payload and kid', function() { 44 | var payload = { 45 | 'request': { 46 | 'continue': 'http://your-service.com/continue-handler', 47 | 'foceLogin': true 48 | }, 49 | 'aud': ['tdaf-accounts'], 50 | 'iss': 'something-to-help-trace', 51 | 'jti': 'be4ea97e-bad8-45d6-a069-928b53441f4c', 52 | 'iat': 1374078871 53 | }; 54 | 55 | var hashKey = '796f75722d7365637265742d6b657923'; 56 | var kid = 'your-client-id'; 57 | 58 | jwtUtils.buildJWT(payload, {alg: 'HS256', kid: kid}, hashKey, function(err, token) { 59 | expect(err).not.to.exist; 60 | expect(token).to.exist; 61 | expect(token).to.equal('eyJhbGciOiJIUzI1NiIsImtp' + 62 | 'ZCI6InlvdXItY2xpZW50LWlkIn0.eyJyZXF1ZXN0Ijp' + 63 | '7ImNvbnRpbnVlIjoiaHR0cDovL3lvdXItc2VydmljZS' + 64 | '5jb20vY29udGludWUtaGFuZGxlciIsImZvY2VMb2dpb' + 65 | 'iI6dHJ1ZX0sImF1ZCI6WyJ0ZGFmLWFjY291bnRzIl0s' + 66 | 'ImlzcyI6InNvbWV0aGluZy10by1oZWxwLXRyYWNlIiw' + 67 | 'ianRpIjoiYmU0ZWE5N2UtYmFkOC00NWQ2LWEwNjktOT' + 68 | 'I4YjUzNDQxZjRjIiwiaWF0IjoxMzc0MDc4ODcxLCJle' + 69 | 'HAiOjEzNzQwNzk0NzF9.e-H48r40XUZBdczeEwlKl8n' + 70 | 'PQxDou_r_83HzSCMbVwY'); 71 | }); 72 | }); 73 | 74 | 75 | it('should read jwt encripted with the right hash', function() { 76 | var jwtToken = 'eyJhbGciOiJkaXIiLCJlbmMiOi' + 77 | 'JBMTI4Q0JDLUhTMjU2Iiwia2lkIjoieW91ci1' + 78 | 'jbGllbnQtaWQifQ..c-xfuN2UeyOMuMBey-Rr' + 79 | 'rg.OWATzLb6knY3WklIIz3CoXJsgnC9MSC-sV' + 80 | '3EKBl5ffE8Yqtg2_oUElZUgku08fR2p4IZwOT' + 81 | 'EmGf982vO7lIHN_YlnF-2tAfLrDZSr1Lb4ETL' + 82 | 'G-2pfKkgx3iGoL7TqA2liDIfFWeoQpXku-dRc' + 83 | 'beJuVsrTCbyvMvqcBbcZ3ETmTytlUjhmmwGRL' + 84 | 'xBlsGt3V9le0cB_p4KiWzYlYr0Jj-7gzLmL4Y' + 85 | 'XQqN-6KrjtuC6IXwCt-8ba_lyv0AmtqxQq-UC' + 86 | 'I2adk-ZGYyAJi7lv4jAG_g.6TBHBz6uzJ3FS8' + 87 | '7KFf0x6g'; 88 | 89 | var hashKey = '796f75722d7365637265742d6b657923'; 90 | var encKey = '796f75722d7365637265742d6b657923'; 91 | 92 | var clock = sinon.useFakeTimers(0, 'Date'); 93 | clock.tick((1375267462041 + 1) * 1000); 94 | 95 | jwtUtils.readJWTEncrypted(jwtToken, encKey, hashKey, function(err, token) { 96 | expect(err).not.to.exist; 97 | expect(token).to.exist; 98 | expect(token.payload.iss).to.equal('tdaf-accounts'); 99 | clock.restore(); 100 | }); 101 | }); 102 | 103 | it('should return an algorithm error ', function() { 104 | var jwtToken = 'eyJhbGciOiJkaXIiLCJlbmMiOi' + 105 | 'JVTktOT1dOLUhTMjU2Iiwia2lkIjoieW91ci1' + 106 | 'jbGllbnQtaWQifQ..c-xfuN2UeyOMuMBey-Rr' + 107 | 'rg.OWATzLb6knY3WklIIz3CoXJsgnC9MSC-sV' + 108 | '3EKBl5ffE8Yqtg2_oUElZUgku08fR2p4IZwOT' + 109 | 'EmGf982vO7lIHN_YlnF-2tAfLrDZSr1Lb4ETL' + 110 | 'G-2pfKkgx3iGoL7TqA2liDIfFWeoQpXku-dRc' + 111 | 'beJuVsrTCbyvMvqcBbcZ3ETmTytlUjhmmwGRL' + 112 | 'xBlsGt3V9le0cB_p4KiWzYlYr0Jj-7gzLmL4Y' + 113 | 'XQqN-6KrjtuC6IXwCt-8ba_lyv0AmtqxQq-UC' + 114 | 'I2adk-ZGYyAJi7lv4jAG_g.6TBHBz6uzJ3FS8' + 115 | '7KFf0x6g'; 116 | 117 | var hashKey = '796f75722d7365637265742d6b657923'; 118 | var encKey = '796f75722d7365637265742d6b657923'; 119 | 120 | jwtUtils.readJWTEncrypted(jwtToken, encKey, hashKey, function(err, token) { 121 | expect(err).to.exist; 122 | expect(token).to.not.exist; 123 | expect(err).to.be.apiError(errors.ALGORITHM_NOT_SUPPORTED()); 124 | }); 125 | }); 126 | 127 | it('should not read a jwt from future', function() { 128 | var jwtToken = 'eyJhbGciOiJkaXIiLCJlbmMiOi' + 129 | 'JBMTI4Q0JDLUhTMjU2Iiwia2lkIjoieW91ci1' + 130 | 'jbGllbnQtaWQifQ..c-xfuN2UeyOMuMBey-Rr' + 131 | 'rg.OWATzLb6knY3WklIIz3CoXJsgnC9MSC-sV' + 132 | '3EKBl5ffE8Yqtg2_oUElZUgku08fR2p4IZwOT' + 133 | 'EmGf982vO7lIHN_YlnF-2tAfLrDZSr1Lb4ETL' + 134 | 'G-2pfKkgx3iGoL7TqA2liDIfFWeoQpXku-dRc' + 135 | 'beJuVsrTCbyvMvqcBbcZ3ETmTytlUjhmmwGRL' + 136 | 'xBlsGt3V9le0cB_p4KiWzYlYr0Jj-7gzLmL4Y' + 137 | 'XQqN-6KrjtuC6IXwCt-8ba_lyv0AmtqxQq-UC' + 138 | 'I2adk-ZGYyAJi7lv4jAG_g.6TBHBz6uzJ3FS8' + 139 | '7KFf0x6g'; 140 | 141 | var hashKey = '796f75722d7365637265742d6b657923'; 142 | var encKey = '796f75722d7365637265742d6b657923'; 143 | 144 | var clock = sinon.useFakeTimers(0, 'Date'); 145 | clock.tick((1375267462041 - 5) * 1000); 146 | 147 | jwtUtils.readJWTEncrypted(jwtToken, encKey, hashKey, function(err, token) { 148 | expect(err).to.be.apiError(errors.FUTURE_JWT()); 149 | expect(token).to.not.exist; 150 | clock.restore(); 151 | }); 152 | }); 153 | 154 | it('should return an error when jwt encrypted has a bad hash', function() { 155 | var jwtToken = 'eyJhbGciOiJkaXIiLCJlbmMiOi' + 156 | 'JBMTI4Q0JDLUhTMjU2Iiwia2lkIjoieW91ci1jb' + 157 | 'GllbnQtaWQifQ==..c-xfuN2UeyOMuMBey-Rrrg' + 158 | '==.DhtMD5203CkjQNVu4P0-W3q19ZRq5JJbkrh4' + 159 | 'uzg8MLq68a6CRgfKbhV6QFNOoeydzCfzoQ8nA4z' + 160 | 'gLf40JX_DuP4QAKDyyLK-lczyOYVhIp7KwpAFqW' + 161 | 'WOIjoDRi-5xIMEV9h8RzHuD-moMKHWepDnaNtAy' + 162 | 'L1X6LtNVEpux3YldduaHoVNkTTGtLWBpjICZwG0' + 163 | 'QoFwWn3mU9O6gu8pHbSm2unrLEENqHqUBKpO276' + 164 | 'G8huJnU0-mhgP2MV2CdUgEAYC__WDh7KMDaDZXF' + 165 | 'UW5ZNBsA==.11111cb2qTCGTyaKDmDA=='; 166 | 167 | var hashKey = '796f75722d7365637265742d6b657923'; 168 | var encKey = '796f75722d7365637265742d6b657923'; 169 | 170 | jwtUtils.readJWTEncrypted(jwtToken, encKey, hashKey, function(err, token) { 171 | expect(err).to.be.apiError(errors.DECRYPTION_ERROR()); 172 | expect(token).not.to.exist; 173 | }); 174 | }); 175 | 176 | it('should return an error when jwt encrypted does not have hash', function() { 177 | var jwtToken = 'eyJhbGciOiJkaXIiLCJlbmMiOi' + 178 | 'JBMTI4Q0JDLUhTMjU2Iiwia2lkIjoieW91ci1jb' + 179 | 'GllbnQtaWQifQ==..c-xfuN2UeyOMuMBey-Rrrg' + 180 | '==.DhtMD5203CkjQNVu4P0-W3q19ZRq5JJbkrh4' + 181 | 'uzg8MLq68a6CRgfKbhV6QFNOoeydzCfzoQ8nA4z' + 182 | 'gLf40JX_DuP4QAKDyyLK-lczyOYVhIp7KwpAFqW' + 183 | 'WOIjoDRi-5xIMEV9h8RzHuD-moMKHWepDnaNtAy' + 184 | 'L1X6LtNVEpux3YldduaHoVNkTTGtLWBpjICZwG0' + 185 | 'QoFwWn3mU9O6gu8pHbSm2unrLEENqHqUBKpO276' + 186 | 'G8huJnU0-mhgP2MV2CdUgEAYC__WDh7KMDaDZXF' + 187 | 'UW5ZNBsA=='; 188 | 189 | var hashKey = '796f75722d7365637265742d6b657923'; 190 | var encKey = '796f75722d7365637265742d6b657923'; 191 | 192 | jwtUtils.readJWTEncrypted(jwtToken, encKey, hashKey, function(err, token) { 193 | expect(err).to.be.apiError(errors.SEGMENTS_NUMBER_ERROR(4)); 194 | expect(token).not.to.exist; 195 | }); 196 | }); 197 | 198 | it('return an error when jwt encrypted does not have the fourth segment', function() { 199 | var jwtToken = 'eyJhbGciOiJkaXIiLCJlbmMiOi' + 200 | 'JBMTI4Q0JDLUhTMjU2Iiwia2lkIjoieW91ci1' + 201 | 'jbGllbnQtaWQifQ....6TBHBz6uzJ3FS8' + 202 | '7KFf0x6g'; 203 | 204 | var hashKey = '796f75722d7365637265742d6b657923'; 205 | var encKey = '796f75722d7365637265742d6b657923'; 206 | 207 | jwtUtils.readJWTEncrypted(jwtToken, encKey, hashKey, function(err, token) { 208 | expect(err).to.be.apiError(errors.INVALID_FOURTH_SEGMENT()); 209 | expect(token).not.to.exist; 210 | }); 211 | }); 212 | 213 | it('should encrypt a JWT (default A256CBC-HS256)', function(done) { 214 | var payload = { 215 | hola: 'caracola' 216 | }, 217 | kid = 'your-client-id', 218 | key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923', 219 | hashKey = '796f75722d7365637265742d6b657923'; 220 | 221 | jwtUtils.buildJWTEncrypted(payload, {kid: kid}, key, hashKey, function(err, jwt) { 222 | expect(jwt).to.be.ok; 223 | jwtUtils.readJWTEncrypted(jwt, key, hashKey, function(err, token) { 224 | expect(err).to.not.exist; 225 | expect(token.payload).to.be.deep.equal(payload); 226 | done(err); 227 | }); 228 | }); 229 | }); 230 | 231 | it('should encrypt a JWT with A128CBC-HS256', function(done) { 232 | var payload = { 233 | hola: 'caracola' 234 | }, 235 | kid = 'your-client-id', 236 | key = '796f75722d7365637265742d6b657923', 237 | hashKey = '796f75722d7365637265742d6b657923'; 238 | 239 | jwtUtils.buildJWTEncrypted(payload, {kid: kid, enc: 'A128CBC-HS256'}, key, hashKey, function(err, jwt) { 240 | expect(jwt).to.be.ok; 241 | var segments = jwt.split('.'); 242 | expect(segments[1]).to.be.equal(''); 243 | var authTagBuff = new Buffer(segments[4], 'base64'); 244 | expect(authTagBuff.length).to.be.equal(16); 245 | expect(err).to.not.exist; 246 | jwtUtils.readJWTEncrypted(jwt, key, hashKey, function(err, token) { 247 | expect(err).to.not.exist; 248 | expect(token.payload).to.be.deep.equal(payload); 249 | done(err); 250 | }); 251 | }); 252 | }); 253 | 254 | it('should return a missing header error when it tries to build a jwtEnc', function() { 255 | var payload = { 256 | hola: 'caracola' 257 | }, 258 | key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923', 259 | hashKey = '796f75722d7365637265742d6b657923'; 260 | 261 | jwtUtils.buildJWTEncrypted(payload, null, key, hashKey, function(err, jwt) { 262 | expect(err).to.be.apiError(errors.MISSING_REQUIRED_HEADER()); 263 | expect(jwt).not.to.exist; 264 | }); 265 | }); 266 | 267 | it('should return a missing kid error when it tries to build a jwtEnc', function() { 268 | var payload = { 269 | hola: 'caracola' 270 | }, 271 | key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923', 272 | hashKey = '796f75722d7365637265742d6b657923'; 273 | 274 | jwtUtils.buildJWTEncrypted(payload, {}, key, hashKey, function(err, jwt) { 275 | expect(err).to.be.apiError(errors.MISSING_REQUIRED_KID()); 276 | expect(jwt).not.to.exist; 277 | }); 278 | }); 279 | 280 | it('should return a algorithm not supported error when it tries to build a jwtEnc', function() { 281 | var payload = { 282 | hola: 'caracola' 283 | }, 284 | key = '796f75722d7365637265742d6b657923', 285 | hashKey = '796f75722d7365637265742d6b657923'; 286 | 287 | jwtUtils.buildJWTEncrypted(payload, {enc: 'UNKNOWN', kid: 'kid'}, key, hashKey, function(err, jwt) { 288 | expect(err).to.be.apiError(errors.ALGORITHM_NOT_SUPPORTED()); 289 | expect(jwt).not.to.exist; 290 | }); 291 | }); 292 | 293 | it('should add iat to payload if not present', function(done) { 294 | var payload = { 295 | hola: 'caracola' 296 | }, 297 | kid = 'your-client-id', 298 | key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923', 299 | hashKey = '796f75722d7365637265742d6b657923'; 300 | 301 | var seconds = 10; 302 | var clock = sinon.useFakeTimers(seconds * 1000); 303 | 304 | jwtUtils.buildJWTEncrypted(payload, {kid: kid}, key, hashKey, function(err, jwt) { 305 | expect(err).to.not.exist; 306 | jwtUtils.readJWTEncrypted(jwt, key, hashKey, function(err, token) { 307 | expect(err).to.not.exist; 308 | expect(token.payload).to.have.property('iat'); 309 | expect(token.payload.iat).to.equal(seconds); 310 | done(err); 311 | }); 312 | }); 313 | }); 314 | 315 | it('should not add iat to payload if already present', function(done) { 316 | var payload = { 317 | hola: 'caracola', 318 | iat: 1 319 | }, 320 | kid = 'your-client-id', 321 | key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923', 322 | hashKey = '796f75722d7365637265742d6b657923'; 323 | 324 | jwtUtils.buildJWTEncrypted(payload, {kid: kid}, key, hashKey, function(err, jwt) { 325 | var clock = sinon.useFakeTimers(payload.iat * 1000); 326 | expect(err).to.not.exist; 327 | jwtUtils.readJWTEncrypted(jwt, key, hashKey, function(err, token) { 328 | expect(err).to.not.exist; 329 | expect(token.payload).to.have.property('iat'); 330 | expect(token.payload.iat).to.equal(payload.iat); 331 | done(err); 332 | }); 333 | }); 334 | }); 335 | 336 | it('should be able to maintain overriden fields', function(done) { 337 | var payload = { 338 | hola: 'caracola', 339 | iss: 'loquillo', 340 | iat: 123, 341 | jti: 'troglodita' 342 | }, 343 | kid = 'your-client-id', 344 | key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923', 345 | hashKey = '796f75722d7365637265742d6b657923'; 346 | 347 | 348 | jwtUtils.buildJWTEncrypted(payload, {kid: kid}, key, hashKey, function(err, jwt) { 349 | expect(err).to.not.exist; 350 | var clock = sinon.useFakeTimers(0, 'Date'); 351 | clock.tick(payload.iat * 1000); 352 | jwtUtils.readJWTEncrypted(jwt, key, hashKey, function(err, token) { 353 | clock.restore(); 354 | expect(err).to.not.exist; 355 | expect(token.payload).to.have.property('jti', 'troglodita'); 356 | expect(token.payload).to.have.property('iss', 'loquillo'); 357 | expect(token.payload).to.have.property('iat', payload.iat); 358 | done(err); 359 | }); 360 | }); 361 | }); 362 | 363 | it('should fail to decrypt a JWT with other hashKey', function(done) { 364 | var payload = { 365 | hola: 'caracola' 366 | }, 367 | kid = 'your-client-id', 368 | key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923', 369 | hashKey = '796f75722d7365637265742d6b657923', 370 | invalidHashKey = '11111111111111111111111111111111'; 371 | 372 | jwtUtils.buildJWTEncrypted(payload, {kid: kid}, key, hashKey, function(err, jwt) { 373 | expect(err).to.not.exist; 374 | jwtUtils.readJWTEncrypted(jwt, key, invalidHashKey, function(err, token) { 375 | expect(err).to.be.apiError(errors.DECRYPTION_ERROR()); 376 | expect(token).to.not.exist; 377 | done(); 378 | }); 379 | }); 380 | }); 381 | 382 | it('should fail decrypting a JWT with other key', function(done) { 383 | var payload = { 384 | hola: 'caracola' 385 | }, 386 | kid = 'your-client-id', 387 | hashKey = '796f75722d7365637265742d6b657923', 388 | key1 = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923', 389 | key2 = '1111111111111111111111111111111111111111111111111111111111111111'; 390 | 391 | jwtUtils.buildJWTEncrypted(payload, {kid: kid}, key1, hashKey, function(err, jwt) { 392 | expect(err).to.not.exist; 393 | jwtUtils.readJWTEncrypted(jwt, key2, hashKey, function(err, token) { 394 | expect(err).to.be.apiError(errors.DECRYPTION_ERROR()); 395 | expect(token).to.not.exist; 396 | done(); 397 | }); 398 | }); 399 | }); 400 | 401 | it('should fail encrypting a JWT with an invalid key', function(done) { 402 | var payload = { 403 | hola: 'caracola' 404 | }, 405 | kid = 'your-client-id', 406 | hashKey = '796f75722d7365637265742d6b657923', 407 | invalidKey1 = '1', // too short 408 | invalidKey2 = '11111111111111111111111111111111333333'; // too long 409 | 410 | jwtUtils.buildJWTEncrypted(payload, {kid: kid}, invalidKey1, hashKey, function(err) { 411 | expect(err).to.be.apiError(errors.ENCRYPTION_ERROR()); 412 | jwtUtils.buildJWTEncrypted(payload, {kid: kid}, invalidKey2, hashKey, function(err) { 413 | expect(err).to.be.apiError(errors.ENCRYPTION_ERROR()); 414 | done(); 415 | }); 416 | }); 417 | }); 418 | 419 | it('should fail when the JWT is expired', function(done) { 420 | var expiration = 10; 421 | var jwtUtilsMod = jwt({expiration: expiration}); 422 | 423 | var payload = { 424 | hola: 'caracola' 425 | }, 426 | kid = 'your-client-id', 427 | key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923', 428 | hashKey = '796f75722d7365637265742d6b657923'; 429 | 430 | jwtUtilsMod.buildJWTEncrypted(payload, {kid: kid}, key, hashKey, function(err, jwt) { 431 | expect(err).to.not.exist; 432 | var clock = sinon.useFakeTimers(new Date().getTime()); 433 | clock.tick((expiration + 1) * 1000); 434 | jwtUtils.readJWTEncrypted(jwt, key, hashKey, function(err, token) { 435 | expect(err).to.exist; 436 | expect(err).to.be.apiError(errors.EXPIRED_JWT()); 437 | clock.restore(); 438 | done(); 439 | }); 440 | }); 441 | }); 442 | 443 | it('should read an unencrypted token', function() { 444 | var jwtToken = 'eyJhbGciOiJIUzI1NiIsImtpZC' + 445 | 'I6InlvdXItY2xpZW50LWlkIn0.eyJyZXF1ZXN0I' + 446 | 'jp7ImNvbnRpbnVlIjoiaHR0cDovL3lvdXItc2Vy' + 447 | 'dmljZS5jb20vY29udGludWUtaGFuZGxlciIsImZ' + 448 | 'vY2VMb2dpbiI6dHJ1ZX0sImF1ZCI6WyJ0ZGFmLW' + 449 | 'FjY291bnRzIl0sImlzcyI6InNvbWV0aGluZy10b' + 450 | 'y1oZWxwLXRyYWNlIiwianRpIjoiYmU0ZWE5N2Ut' + 451 | 'YmFkOC00NWQ2LWEwNjktOTI4YjUzNDQxZjRjIiw' + 452 | 'iaWF0IjoxMzc0MDc4ODcxfQ.rxS6hoB1xP_a13J' + 453 | 'rEFqY7RdzY3pxko-UDfZZ3QFqnEI'; 454 | 455 | var hashKey = '796f75722d7365637265742d6b657923'; 456 | var tokenIat = 1374078871; 457 | 458 | var clock = sinon.useFakeTimers(0, 'Date'); 459 | clock.tick((tokenIat + 1) * 1000); 460 | 461 | jwtUtils.readJWT(jwtToken, hashKey, function(err, token) { 462 | expect(err).not.to.exist; 463 | expect(token.payload).to.exist; 464 | 465 | expect(token.payload).to.be.deep.equal({ 466 | 'request': { 467 | 'continue': 'http://your-service.com/continue-handler', 468 | 'foceLogin': true 469 | }, 470 | 'aud': ['tdaf-accounts'], 471 | 'iss': 'something-to-help-trace', 472 | 'jti': 'be4ea97e-bad8-45d6-a069-928b53441f4c', 473 | 'iat': tokenIat 474 | }); 475 | }); 476 | }); 477 | 478 | it('should fail to read an unencrypted token with invalid number of segments', function() { 479 | var jwtToken = 'eyJhbGciOiJIUzI1NiJ9.eyJpc' + 480 | '3MiOiIiLCJqdGkiOiIiLCJpYXQiOjEzNzQwNzg4' + 481 | 'NzF9'; 482 | 483 | var hashKey = '11111111111111111111111111111111'; 484 | jwtUtils.readJWT(jwtToken, hashKey, function(err, token) { 485 | expect(err).to.be.apiError(errors.SEGMENTS_NUMBER_ERROR(2)); 486 | expect(token).not.to.exist; 487 | }); 488 | }); 489 | 490 | it('should fail to read an unencrypted token without a valid algorithm', function() { 491 | var jwtToken = 'eyJhbGciOiJIUzI1NyIsICJraWQiOiJteUtpZCJ9.eyJpc' + 492 | '3MiOiIiLCJqdGkiOiIiLCJpYXQiOjEzNzQwNzg4' + 493 | 'NzF9.khagsjdgjas'; 494 | 495 | var hashKey = '11111111111111111111111111111111'; 496 | jwtUtils.readJWT(jwtToken, hashKey, function(err, token) { 497 | expect(err).to.be.apiError(errors.ALGORITHM_NOT_SUPPORTED()); 498 | expect(token).not.to.exist; 499 | }); 500 | }); 501 | 502 | it('should fail to read an unencrypted token without a valid key or hash', function() { 503 | var jwtToken = 'eyJhbGciOiJIUzI1NiIsICJraWQiOiJteUtpZCJ9.eyJpc' + 504 | '3MiOiIiLCJqdGkiOiIiLCJpYXQiOjEzNzQwNzg4' + 505 | 'NzF9.khagsjdgjas'; 506 | 507 | var hashKey = '11111111111111111111111111111111'; 508 | jwtUtils.readJWT(jwtToken, hashKey, function(err, token) { 509 | expect(err).to.be.apiError(errors.WRONG_TOKEN_SIGNATURE()); 510 | expect(token).not.to.exist; 511 | }); 512 | }); 513 | 514 | 515 | it('should read the header of an encrypted JWT', function() { 516 | var payload = {}, 517 | kid = 'your-client-id', 518 | key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923', 519 | hashKey = '796f75722d7365637265742d6b657923'; 520 | 521 | jwtUtils.buildJWTEncrypted(payload, {kid: kid}, key, hashKey, function(err, jwt) { 522 | jwtUtils.readJWTHeader(jwt, function(err, header) { 523 | expect(header).to.have.property('alg'); 524 | expect(header.kid).to.equal(kid); 525 | }); 526 | }); 527 | }); 528 | 529 | it('should read the header of an unencrypted JWT', function() { 530 | var payload = {}, 531 | kid = 'your-client-id', 532 | hashKey = '796f75722d7365637265742d6b657923'; 533 | 534 | jwtUtils.buildJWT(payload, {kid: kid}, hashKey, function(err, jwt) { 535 | jwtUtils.readJWTHeader(jwt, function(err, header) { 536 | expect(header).to.have.property('alg'); 537 | expect(header.kid).to.equal(kid); 538 | }); 539 | }); 540 | }); 541 | 542 | it('should fail while reading the header of an invalid JWT', function() { 543 | var jwtToken = 'wrongheader.wrongpayload.wrongsignature'; 544 | jwtUtils.readJWTHeader(jwtToken, function(err, header) { 545 | expect(err).to.be.apiError(errors.INVALID_HEADER()); 546 | expect(header).not.to.exist; 547 | }); 548 | }); 549 | 550 | it('should generate and read a JWT signed using RS256, privKey 1024', function() { 551 | var payload = { 552 | myField: 'myValue' 553 | }, 554 | header = { 555 | kid: 'kid', 556 | alg: 'RS256' 557 | }; 558 | 559 | var privateKey = fs.readFileSync(__dirname + '/sampleKeys/privateKey.pem'); 560 | var publicKey = fs.readFileSync(__dirname + '/sampleKeys/publicKey.pem'); 561 | jwtUtils.buildJWT(payload, header, privateKey, function(err, result) { 562 | expect(err).to.not.exist; 563 | expect(result).to.exist; 564 | jwtUtils.readJWT(result, publicKey, function(err, decodedResult) { 565 | expect(err).to.not.exist; 566 | expect(decodedResult).to.exist; 567 | expect(decodedResult.header).to.exist; 568 | expect(decodedResult.header.alg).to.be.equal('RS256'); 569 | expect(decodedResult.header.kid).to.be.equal('kid'); 570 | expect(decodedResult.payload).to.exist; 571 | expect(decodedResult.payload.iat).to.exist; 572 | expect(decodedResult.payload.myField).to.be.equal('myValue'); 573 | }); 574 | }); 575 | }); 576 | 577 | it('should generate and read a JWT signed using RS256, privKey 512', function() { 578 | var payload = { 579 | myField: 'myValue' 580 | }, 581 | header = { 582 | kid: 'kid', 583 | alg: 'RS256' 584 | }; 585 | var privateKey = fs.readFileSync(__dirname + '/sampleKeys/privateKey2.pem'); 586 | var publicKey = fs.readFileSync(__dirname + '/sampleKeys/publicKey2.pem'); 587 | jwtUtils.buildJWT(payload, header, privateKey, function(err, result) { 588 | expect(err).to.not.exist; 589 | expect(result).to.exist; 590 | jwtUtils.readJWT(result, publicKey, function(err, decodedResult) { 591 | expect(err).to.not.exist; 592 | expect(decodedResult).to.exist; 593 | expect(decodedResult.header).to.exist; 594 | expect(decodedResult.header.alg).to.be.equal('RS256'); 595 | expect(decodedResult.header.kid).to.be.equal('kid'); 596 | expect(decodedResult.payload).to.exist; 597 | expect(decodedResult.payload.iat).to.exist; 598 | expect(decodedResult.payload.myField).to.be.equal('myValue'); 599 | }); 600 | }); 601 | }); 602 | 603 | it('should return a read error of a JWT signed by privateKey and verified with publicKey2', function() { 604 | var payload = { 605 | myField: 'myValue' 606 | }, 607 | header = { 608 | kid: 'kid', 609 | alg: 'RS256' 610 | }; 611 | var privateKey = fs.readFileSync(__dirname + '/sampleKeys/privateKey.pem'); 612 | var publicKey = fs.readFileSync(__dirname + '/sampleKeys/publicKey2.pem'); 613 | jwtUtils.buildJWT(payload, header, privateKey, function(err, result) { 614 | expect(err).to.not.exist; 615 | expect(result).to.exist; 616 | jwtUtils.readJWT(result, publicKey, function(err, decodedResult) { 617 | expect(err).to.exist; 618 | expect(err).to.be.apiError(errors.WRONG_TOKEN_SIGNATURE()); 619 | expect(decodedResult).to.not.exist; 620 | }); 621 | }); 622 | }); 623 | 624 | it('should encrypt and decrypt with A256CBC-HS512', function() { 625 | var payload = { 626 | status: 'ok' 627 | }; 628 | var header = { 629 | alg: 'dir', 630 | enc: 'A256CBC-HS512', 631 | corr: 'corr', 632 | kid: 'kid' 633 | }; 634 | var key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923'; 635 | jwtUtils.buildJWTEncrypted(payload, header, key, key, function(error, jwt) { 636 | expect(jwt).to.exist; 637 | var segments = jwt.split('.'); 638 | expect(segments[1]).to.be.equal(''); 639 | var authTagBuff = new Buffer(segments[4], 'base64'); 640 | expect(authTagBuff.length).to.be.equal(32); 641 | expect(error).to.not.exist; 642 | jwtUtils.readJWTEncrypted(jwt, key, key, function(err, token) { 643 | expect(token).to.exist; 644 | expect(token.header).to.exist; 645 | expect(token.header.corr).to.be.equal('corr'); 646 | expect(token.header.kid).to.be.equal('kid'); 647 | expect(token.header.alg).to.be.equal('dir'); 648 | expect(token.header.enc).to.be.equal('A256CBC-HS512'); 649 | expect(token.payload).to.exist; 650 | expect(token.payload.iat).to.exist; 651 | expect(token.payload.status).to.be.equal('ok'); 652 | expect(err).to.not.exist; 653 | }); 654 | }); 655 | }); 656 | 657 | it('should encrypt and decrypt with A256CBC-HS512 with utf8 characters in payload', function() { 658 | var payload = { 659 | status: 'ok', 660 | testStr: 'áéíóúñçÇÁÉÍÓÚ' 661 | }; 662 | var header = { 663 | alg: 'dir', 664 | enc: 'A256CBC-HS512', 665 | corr: 'corr', 666 | kid: 'kid' 667 | }; 668 | var key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923'; 669 | jwtUtils.buildJWTEncrypted(payload, header, key, key, function(error, jwt) { 670 | expect(jwt).to.exist; 671 | var segments = jwt.split('.'); 672 | expect(segments[1]).to.be.equal(''); 673 | var authTagBuff = new Buffer(segments[4], 'base64'); 674 | expect(authTagBuff.length).to.be.equal(32); 675 | expect(error).to.not.exist; 676 | jwtUtils.readJWTEncrypted(jwt, key, key, function(err, token) { 677 | expect(token).to.exist; 678 | expect(token.header).to.exist; 679 | expect(token.header.corr).to.be.equal('corr'); 680 | expect(token.header.kid).to.be.equal('kid'); 681 | expect(token.header.alg).to.be.equal('dir'); 682 | expect(token.header.enc).to.be.equal('A256CBC-HS512'); 683 | expect(token.payload).to.exist; 684 | expect(token.payload.iat).to.exist; 685 | expect(token.payload.status).to.be.equal('ok'); 686 | expect(token.payload.testStr).to.be.equal(payload.testStr); 687 | expect(err).to.not.exist; 688 | }); 689 | }); 690 | }); 691 | 692 | it('should encrypt and decrypt with A256CBC-HS512 with utf8 characters in payload and longer length than encryption' + 693 | ' block', function() { 694 | var payload = { 695 | status: 'ok', 696 | testStr: 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 697 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 698 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 699 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 700 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 701 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 702 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 703 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 704 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 705 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' 706 | }; 707 | var header = { 708 | alg: 'dir', 709 | enc: 'A256CBC-HS512', 710 | corr: 'corr', 711 | kid: 'kid' 712 | }; 713 | var key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923'; 714 | jwtUtils.buildJWTEncrypted(payload, header, key, key, function(error, jwt) { 715 | expect(jwt).to.exist; 716 | var segments = jwt.split('.'); 717 | expect(segments[1]).to.be.equal(''); 718 | var authTagBuff = new Buffer(segments[4], 'base64'); 719 | expect(authTagBuff.length).to.be.equal(32); 720 | expect(error).to.not.exist; 721 | jwtUtils.readJWTEncrypted(jwt, key, key, function(err, token) { 722 | expect(token).to.exist; 723 | expect(token.header).to.exist; 724 | expect(token.header.corr).to.be.equal('corr'); 725 | expect(token.header.kid).to.be.equal('kid'); 726 | expect(token.header.alg).to.be.equal('dir'); 727 | expect(token.header.enc).to.be.equal('A256CBC-HS512'); 728 | expect(token.payload).to.exist; 729 | expect(token.payload.iat).to.exist; 730 | expect(token.payload.status).to.be.equal('ok'); 731 | expect(token.payload.testStr).to.be.equal(payload.testStr); 732 | expect(err).to.not.exist; 733 | }); 734 | }); 735 | }); 736 | 737 | it('should encrypt and decrypt with A256CBC-HS512 with utf8 characters in payload and longer length than encryption' + 738 | ' block with an ascii character in the beginning of the string', function() { 739 | var payload = { 740 | status: 'ok', 741 | testStr: 'aáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 742 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 743 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 744 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 745 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 746 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 747 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 748 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 749 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' + 750 | 'áéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚáéíóúñçÇÁÉÍÓÚ' 751 | }; 752 | var header = { 753 | alg: 'dir', 754 | enc: 'A256CBC-HS512', 755 | corr: 'corr', 756 | kid: 'kid' 757 | }; 758 | var key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923'; 759 | jwtUtils.buildJWTEncrypted(payload, header, key, key, function(error, jwt) { 760 | expect(jwt).to.exist; 761 | var segments = jwt.split('.'); 762 | expect(segments[1]).to.be.equal(''); 763 | var authTagBuff = new Buffer(segments[4], 'base64'); 764 | expect(authTagBuff.length).to.be.equal(32); 765 | expect(error).to.not.exist; 766 | jwtUtils.readJWTEncrypted(jwt, key, key, function(err, token) { 767 | expect(token).to.exist; 768 | expect(token.header).to.exist; 769 | expect(token.header.corr).to.be.equal('corr'); 770 | expect(token.header.kid).to.be.equal('kid'); 771 | expect(token.header.alg).to.be.equal('dir'); 772 | expect(token.header.enc).to.be.equal('A256CBC-HS512'); 773 | expect(token.payload).to.exist; 774 | expect(token.payload.iat).to.exist; 775 | expect(token.payload.status).to.be.equal('ok'); 776 | expect(token.payload.testStr).to.be.equal(payload.testStr); 777 | expect(err).to.not.exist; 778 | }); 779 | }); 780 | }); 781 | 782 | it('should fail when it try to encrypt with an unknown hash algorithm', function() { 783 | var payload = { 784 | status: 'ok' 785 | }; 786 | var header = { 787 | alg: 'dir', 788 | enc: 'A256CBC-HS4645', 789 | corr: 'corr', 790 | kid: 'kid' 791 | }; 792 | var key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923'; 793 | jwtUtils.buildJWTEncrypted(payload, header, key, key, function(err, jwt) { 794 | expect(jwt).to.not.exist; 795 | expect(err).to.be.apiError(errors.ALGORITHM_NOT_SUPPORTED()); 796 | expect(err.message).to.be.equal('Algorithm not supported. Invalid hash algorithm: HS4645'); 797 | }); 798 | }); 799 | 800 | it('should fail when it try to decrypt with an unknown hash algorithm', function() { 801 | var jwt = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2Q0JDLUhTMjQ1NiIsImNvcnI' + 802 | 'iOiJjb3JyIiwia2lkIjoia2lkIn0=..i5DUiRZiE7nudmAaNqbkow.eMSIEpr' + 803 | '5Brj0Wc8Acn7QzcGM-_5ALrKUVy9xLUobIFokMBw9RT5wKXmQ_AXWTnqS.Ce3' + 804 | 'jmzDaztKcav0sFd2cQw'; 805 | var key = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923'; 806 | jwtUtils.readJWTEncrypted(jwt, key, key, function(err, token) { 807 | expect(token).to.not.exist; 808 | expect(err).to.be.apiError(errors.ALGORITHM_NOT_SUPPORTED()); 809 | expect(err.message).to.be.equal('Algorithm not supported. Invalid hash algorithm: HS2456'); 810 | 811 | }); 812 | }); 813 | 814 | it('should fail when client expired the jwt without taking into account the exp field', function() { 815 | var jwtToken = 'eyJraWQiOiJraWQiLCJhbGciOiJkaXIiLCJlbmMiOiJBMjU' + 816 | '2Q0JDLUhTNTEyIiwiY29yciI6ImNvcnIifQ..Xuaus8N7yqmojQFFFNgxL' + 817 | 'g.UCM5odV53W9NEaVvee7fCKb31B1C7wedJn02vapNOf2v3-dmKDaZxt2G' + 818 | 'nIDYK-TQ-XkiziRkRtZkjABFRNHPYw.FFduxvzrjIhWu8dQ3MMOVH11-tE' + 819 | 'BgE6955c4M9EO3fk'; 820 | 821 | var hashKey = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923'; 822 | var encKey = '796f75722d7365637265742d6b657923796f75722d7365637265742d6b657923'; 823 | 824 | var clock = sinon.useFakeTimers(0, 'Date'); 825 | clock.tick((1443688542000 + 1) * 1000); 826 | 827 | var jwtUtilsMod = jwt({expiration: 1}); 828 | 829 | jwtUtilsMod.readJWTEncrypted(jwtToken, encKey, hashKey, function(err, token) { 830 | expect(err).to.exist; 831 | expect(err.name).to.be.equal('NO_FRESH_JWT'); 832 | clock.restore(); 833 | }); 834 | }); 835 | 836 | it('should not check expiry with config.expiration set to 0', function() { 837 | var jwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Gfx6VO9tcxwk6xqx9yYzSfebfeakZp5JYIgP_edcw_A'; 838 | var hashKey = '796f75722d3235362d6269742d736563726574'; 839 | 840 | var jwtUtilsMod = jwt({expiration: 0}); 841 | 842 | jwtUtilsMod.readJWT(jwtToken, hashKey, function(err, token) { 843 | expect(err).to.not.exist; 844 | }); 845 | }); 846 | 847 | it('should check expiry with config.expiration set to a value other than 0', function() { 848 | var jwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Gfx6VO9tcxwk6xqx9yYzSfebfeakZp5JYIgP_edcw_A'; 849 | var hashKey = '796f75722d3235362d6269742d736563726574'; 850 | 851 | var jwtUtilsMod = jwt({expiration: 1}); 852 | 853 | jwtUtilsMod.readJWT(jwtToken, hashKey, function(err, token) { 854 | expect(err).to.exist; 855 | }); 856 | }); 857 | 858 | it('should check iat and exp if present, even with config.expiration set to 0', function() { 859 | var jwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIwLCJleHAiOjE1MTYyMzkwMjF9.H_PgIMtvx7m9-vwIc3JBL6FBUEowa9MSYg7bx-BPmBQ'; 860 | var hashKey = '796f75722d3235362d6269742d736563726574'; 861 | 862 | var clock = sinon.useFakeTimers(0, 'Date'); 863 | clock.tick((1516239022000 + 1) * 1000); 864 | 865 | var jwtUtilsMod = jwt({expiration: 0}); 866 | 867 | jwtUtilsMod.readJWT(jwtToken, hashKey, function(err, token) { 868 | expect(err).to.exist; 869 | clock.restore(); 870 | }); 871 | }); 872 | 873 | }); 874 | -------------------------------------------------------------------------------- /test/unit/sampleKeys/README.md: -------------------------------------------------------------------------------- 1 | Key generation 2 | ================= 3 | 4 | ### Private Key: 5 | 6 | ```sh 7 | openssl genrsa -out privateKey.pem 512 8 | ``` 9 | 10 | ### Public Key: 11 | 12 | ```sh 13 | openssl rsa -in privateKey.pem -pubout > publicKey.pem 14 | ``` 15 | -------------------------------------------------------------------------------- /test/unit/sampleKeys/privateKey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXgIBAAKBgQDYllz5xzTCplYYkiMb6l40KJHshaVvqF/vqan5Y/ZE23EVDTN3 3 | WzSAGlhrGJXh6z/FxNSdxScnwaU/iAbx6S1wxcLnhH9Ld1Xbsa5Xjc3nNjLqjhpq 4 | LVTMJJIN+cFxfJQyo7H/nZntPG6GmNfz5fRjWwaxKWW47Y2heDo7pblgvQIDAQAB 5 | AoGBANI9+5jXP6bnklGdogkaE0HsU+JVxUbDGgVqG74jw84JehbmTpxA45CqgkPL 6 | hMrd8nTG4WEZHS4hcoheQeeX4ut5KzOLNHCa9tgttsNUmhE7T8NR0PpuzESfFzyH 7 | wE/jBPJtwOFSy8egVZexSsO2XGX7RS0xRrUEwUJDkyZe2gGBAkEA8fLsgJ2abEfc 8 | hbFqbgEbNfa1U8AYhAK1FQ9rVBJLWFhO+JMy15gbhBNYWgfyIeqHCTT7zf3rG6Hh 9 | LcRDyFPmnQJBAOUqYv7si5fshA2Ek+/hLvze/t5J5lT+Q0OAgxxLa/AaRj1RZwSP 10 | oEvA3XRPlAx2p4E/EavzsBu8z2XfBm+fOKECQQDZ06ZfiQTtdakxaHB0h2RzKiFI 11 | PIsV1sYnJtGvSCUzbZXzr7q3ZYdoWm5R6BNzu41FmcEWTQPtxclQhgGsivT1AkB4 12 | KIhtuCPdjfMTj29uOLlFd7tVXJQ7/nHQZALbBCTdaTlpD1SfzJs0/TRgtxUej9x2 13 | ZVZjvcuaT57DgHREdMzhAkEAhiQFd+9oIBq/U7kKVG8TyLgwsC0df4Knn1ThUd57 14 | z/QjLQ2FBv8/+KerUG98WKIpA1OO1h4uISb8n3bsLzxFBw== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /test/unit/sampleKeys/privateKey2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIBOwIBAAJBALvuZlEfHzd41vg7MNRufws0u/TWYntVnnYOls1XxF0vCDNw1aRH 3 | cwWBfeC1Q76GnZBSrkWi9ZyaT5okwRNNxvECAwEAAQJBAKBqwX/hdv4c8jL2I5pR 4 | r7RbEU3PakUpMphHn8YscvuuYSn6ArlYUFfq7zMZdV1SucfUqaxBShDy3aQySuB8 5 | 4bkCIQDkOL1WvszdxE41cP6tNGBqHUvlF9Nu5h46StRIT8pVwwIhANLOO9W36ewx 6 | EWxgoI5mTuonpVWERLO9wazGsEYCrME7AiBDhb4PMjhuzKvPB/VxzXNRMgXIbDo0 7 | L4V1/bm9A057/QIhAK7EVezoTaIISBaewV1tSk1sUvDj+IU/c1rHWoqgWxLTAiBO 8 | CS7jCXpr1ujPsIdEnvRbGUCDwl/S/BssOoKYAaQnKw== 9 | -----END RSA PRIVATE KEY----- 10 | -------------------------------------------------------------------------------- /test/unit/sampleKeys/publicKey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYllz5xzTCplYYkiMb6l40KJHs 3 | haVvqF/vqan5Y/ZE23EVDTN3WzSAGlhrGJXh6z/FxNSdxScnwaU/iAbx6S1wxcLn 4 | hH9Ld1Xbsa5Xjc3nNjLqjhpqLVTMJJIN+cFxfJQyo7H/nZntPG6GmNfz5fRjWwax 5 | KWW47Y2heDo7pblgvQIDAQAB 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /test/unit/sampleKeys/publicKey2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALvuZlEfHzd41vg7MNRufws0u/TWYntV 3 | nnYOls1XxF0vCDNw1aRHcwWBfeC1Q76GnZBSrkWi9ZyaT5okwRNNxvECAwEAAQ== 4 | -----END PUBLIC KEY----- 5 | --------------------------------------------------------------------------------