Repository: auth0/node-jsonwebtoken Branch: master Commit: cf33b5f59a19 Files: 84 Total size: 237.0 KB Directory structure: gitextract_nrhlur7n/ ├── .commitlintrc.json ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github/ │ └── workflows/ │ ├── commitlint.yml │ ├── prepare-release.yml │ ├── release.yml │ ├── sca-scan.yml │ └── test.yml ├── .gitignore ├── .husky/ │ └── commit-msg ├── .npmignore ├── .releaserc.json ├── CHANGELOG.md ├── CODEOWNERS ├── LICENSE ├── README.md ├── bin/ │ └── changelog ├── decode.js ├── index.js ├── lib/ │ ├── JsonWebTokenError.js │ ├── NotBeforeError.js │ ├── TokenExpiredError.js │ ├── asymmetricKeyDetailsSupported.js │ ├── psSupported.js │ ├── rsaPssKeyDetailsSupported.js │ ├── timespan.js │ └── validateAsymmetricKey.js ├── opslevel.yml ├── package.json ├── sign.js ├── test/ │ ├── .eslintrc.json │ ├── async_sign.tests.js │ ├── buffer.tests.js │ ├── claim-aud.test.js │ ├── claim-exp.test.js │ ├── claim-iat.test.js │ ├── claim-iss.test.js │ ├── claim-jti.test.js │ ├── claim-nbf.test.js │ ├── claim-private.tests.js │ ├── claim-sub.tests.js │ ├── decoding.tests.js │ ├── dsa-private.pem │ ├── dsa-public.pem │ ├── ecdsa-private.pem │ ├── ecdsa-public-invalid.pem │ ├── ecdsa-public-x509.pem │ ├── ecdsa-public.pem │ ├── encoding.tests.js │ ├── expires_format.tests.js │ ├── header-kid.test.js │ ├── invalid_exp.tests.js │ ├── invalid_pub.pem │ ├── issue_147.tests.js │ ├── issue_304.tests.js │ ├── issue_70.tests.js │ ├── jwt.asymmetric_signing.tests.js │ ├── jwt.hs.tests.js │ ├── jwt.malicious.tests.js │ ├── noTimestamp.tests.js │ ├── non_object_values.tests.js │ ├── option-complete.test.js │ ├── option-maxAge.test.js │ ├── option-nonce.test.js │ ├── prime256v1-private.pem │ ├── priv.pem │ ├── pub.pem │ ├── rsa-private.pem │ ├── rsa-pss-invalid-salt-length-private.pem │ ├── rsa-pss-private.pem │ ├── rsa-public-key.pem │ ├── rsa-public-key.tests.js │ ├── rsa-public.pem │ ├── schema.tests.js │ ├── secp384r1-private.pem │ ├── secp521r1-private.pem │ ├── set_headers.tests.js │ ├── test-utils.js │ ├── undefined_secretOrPublickey.tests.js │ ├── validateAsymmetricKey.tests.js │ ├── verify.tests.js │ └── wrong_alg.tests.js └── verify.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .commitlintrc.json ================================================ { "extends": ["@commitlint/config-conventional"] } ================================================ FILE: .editorconfig ================================================ [*] indent_style = space indent_size = 2 ================================================ FILE: .eslintignore ================================================ .nyc_output/ coverage/ ================================================ FILE: .eslintrc.json ================================================ { "root": true, "parserOptions": { "ecmaVersion": 6 }, "env": { "es6": true, "node": true }, "rules": { "comma-style": "error", "dot-notation": "error", "indent": ["error", 2], "no-control-regex": "error", "no-div-regex": "error", "no-eval": "error", "no-implied-eval": "error", "no-invalid-regexp": "error", "no-trailing-spaces": "error", "no-undef": "error", "no-unused-vars": "error" } } ================================================ FILE: .github/workflows/commitlint.yml ================================================ name: Lint Commits on: pull_request: jobs: commitlint: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false - name: Setup Node.js uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: '24' - name: Install dependencies run: npm install - name: Validate commit messages run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose ================================================ FILE: .github/workflows/prepare-release.yml ================================================ name: Prepare Release on: push: branches: - master concurrency: group: prepare-release cancel-in-progress: true permissions: contents: write pull-requests: write jobs: prepare: runs-on: ubuntu-latest if: "!startsWith(github.event.head_commit.message, 'chore(release):')" steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false - name: Setup Node.js uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: '24' - name: Install dependencies run: npm install - name: Detect Next Version id: version env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | NEXT_VERSION=$(npx semantic-release --dry-run --plugins @semantic-release/commit-analyzer | tee /dev/stderr | awk '/The next release version is/{print $NF}') echo "next=$NEXT_VERSION" >> $GITHUB_OUTPUT - name: Update package.json if: steps.version.outputs.next != '' run: npm version "$NEXT_VERSION" --no-git-tag-version env: NEXT_VERSION: ${{ steps.version.outputs.next }} - name: Update CHANGELOG.md if: steps.version.outputs.next != '' run: npx conventional-changelog-cli -p angular -i CHANGELOG.md -s - name: Create Pull Request if: steps.version.outputs.next != '' uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: "chore(release): ${{ steps.version.outputs.next }}" branch: "release/v${{ steps.version.outputs.next }}" delete-branch: true title: "chore(release): ${{ steps.version.outputs.next }}" body: | This PR prepares the release of version ${{ steps.version.outputs.next }}. **Changes:** - Updated version in `package.json` to ${{ steps.version.outputs.next }} - Updated `CHANGELOG.md` with release notes **Next Steps:** Review and merge this PR to trigger the publish workflow. labels: release ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: push: branches: - master permissions: contents: write issues: write pull-requests: write id-token: write jobs: release: runs-on: ubuntu-latest if: startsWith(github.event.head_commit.message, 'chore(release):') steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false - name: Setup Node.js uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: '24' - name: Install dependencies run: npm install - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.10" - name: Install Python dependencies shell: bash run: pip install boto3>=1.34.159 requests>=2.32.3 rl-deploy>=2.2.3.0 pip-system-certs>=4.0 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0 with: role-to-assume: ${{ secrets.PRODSEC_TOOLS_ARN }} aws-region: us-east-1 mask-aws-account-id: true - name: Install rl-wrapper env: WRAPPER_INDEX_URL: "https://${{ secrets.PRODSEC_TOOLS_USER }}:${{ secrets.PRODSEC_TOOLS_TOKEN }}@a0us.jfrog.io/artifactory/api/pypi/python-local/simple" run: pip install "rl-wrapper>=1.0.0" --index-url $WRAPPER_INDEX_URL - name: Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_CONFIG_PROVENANCE: true RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }} RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }} SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }} PYTHONUNBUFFERED: 1 run: npx semantic-release ================================================ FILE: .github/workflows/sca-scan.yml ================================================ name: Snyk Scan on: push: branches: ["master"] jobs: snyk-cli: uses: auth0/devsecops-tooling/.github/workflows/sca-scan.yml@5246a8b59100e3eea284ce4f2e2a51b51e237380 secrets: inherit ================================================ FILE: .github/workflows/test.yml ================================================ name: Test on: pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: read jobs: test: strategy: fail-fast: false matrix: node: [16, 18, 20, 22, 24] name: Test (Node ${{ matrix.node }}) runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Setup Node uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: ${{ matrix.node }} - name: Install dependencies run: npm install - name: Test run: npm test ================================================ FILE: .gitignore ================================================ node_modules .DS_Store .nyc_output coverage package-lock.json ================================================ FILE: .husky/commit-msg ================================================ npx --no -- commitlint --edit ${1} ================================================ FILE: .npmignore ================================================ opslevel.yml ================================================ FILE: .releaserc.json ================================================ { "branches": [ "master" ], "plugins": [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", [ "@semantic-release/npm", { "npmPublish": true, "pkgRoot": "." } ], [ "@semantic-release/exec", { "verifyReleaseCmd": "ARTIFACT=\"$(pwd)/$(npm pack --ignore-scripts | tail -1)\" && rl-wrapper --artifact \"$ARTIFACT\" --name jsonwebtoken --version ${nextRelease.version} --repository $GITHUB_REPOSITORY --commit $GITHUB_SHA --build-env github_actions --suppress-output", "prepareCmd": "git diff --exit-code" } ], "@semantic-release/github" ] } ================================================ FILE: CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file starting from version **v4.0.0**. This project adheres to [Semantic Versioning](http://semver.org/). ## 9.0.3 - 2025-12-04 - updates jws version to 4.0.1. ## 9.0.2 - 2023-08-30 - security: updating semver to 7.5.4 to resolve CVE-2022-25883, closes [#921](https://github.com/auth0/node-jsonwebtoken/issues/921). - refactor: reduce library size by using lodash specific dependencies, closes [#878](https://github.com/auth0/node-jsonwebtoken/issues/878). ## 9.0.1 - 2023-07-05 - fix(stubs): allow decode method to be stubbed ## 9.0.0 - 2022-12-21 **Breaking changes: See [Migration from v8 to v9](https://github.com/auth0/node-jsonwebtoken/wiki/Migration-Notes:-v8-to-v9)** ### Breaking changes - Removed support for Node versions 11 and below. - The verify() function no longer accepts unsigned tokens by default. ([834503079514b72264fd13023a3b8d648afd6a16]https://github.com/auth0/node-jsonwebtoken/commit/834503079514b72264fd13023a3b8d648afd6a16) - RSA key size must be 2048 bits or greater. ([ecdf6cc6073ea13a7e71df5fad043550f08d0fa6]https://github.com/auth0/node-jsonwebtoken/commit/ecdf6cc6073ea13a7e71df5fad043550f08d0fa6) - Key types must be valid for the signing / verification algorithm ### Security fixes - security: fixes `Arbitrary File Write via verify function` - CVE-2022-23529 - security: fixes `Insecure default algorithm in jwt.verify() could lead to signature validation bypass` - CVE-2022-23540 - security: fixes `Insecure implementation of key retrieval function could lead to Forgeable Public/Private Tokens from RSA to HMAC` - CVE-2022-23541 - security: fixes `Unrestricted key type could lead to legacy keys usage` - CVE-2022-23539 ## 8.5.1 - 2019-03-18 ### Bug fix - fix: ensure correct PS signing and verification (#585) ([e5874ae428ffc0465e6bd4e660f89f78b56a74a6](https://github.com/auth0/node-jsonwebtoken/commit/e5874ae428ffc0465e6bd4e660f89f78b56a74a6)), closes [#585](https://github.com/auth0/node-jsonwebtoken/issues/585) ### Docs - README: fix markdown for algorithms table ([84e03ef70f9c44a3aef95a1dc122c8238854f683](https://github.com/auth0/node-jsonwebtoken/commit/84e03ef70f9c44a3aef95a1dc122c8238854f683)) ## 8.5.0 - 2019-02-20 ### New Functionality - feat: add PS JWA support for applicable node versions (#573) ([eefb9d9c6eec54718fa6e41306bda84788df7bec](https://github.com/auth0/node-jsonwebtoken/commit/eefb9d9c6eec54718fa6e41306bda84788df7bec)), closes [#573](https://github.com/auth0/node-jsonwebtoken/issues/573) - Add complete option in jwt.verify (#522) ([8737789dd330cf9e7870f4df97fd52479adbac22](https://github.com/auth0/node-jsonwebtoken/commit/8737789dd330cf9e7870f4df97fd52479adbac22)), closes [#522](https://github.com/auth0/node-jsonwebtoken/issues/522) ### Test Improvements - Add tests for private claims in the payload (#555) ([5147852896755dc1291825e2e40556f964411fb2](https://github.com/auth0/node-jsonwebtoken/commit/5147852896755dc1291825e2e40556f964411fb2)), closes [#555](https://github.com/auth0/node-jsonwebtoken/issues/555) - Force use_strict during testing (#577) ([7b60c127ceade36c33ff33be066e435802001c94](https://github.com/auth0/node-jsonwebtoken/commit/7b60c127ceade36c33ff33be066e435802001c94)), closes [#577](https://github.com/auth0/node-jsonwebtoken/issues/577) - Refactor tests related to jti and jwtid (#544) ([7eebbc75ab89e01af5dacf2aae90fe05a13a1454](https://github.com/auth0/node-jsonwebtoken/commit/7eebbc75ab89e01af5dacf2aae90fe05a13a1454)), closes [#544](https://github.com/auth0/node-jsonwebtoken/issues/544) - ci: remove nsp from tests (#569) ([da8f55c3c7b4dd0bfc07a2df228500fdd050242a](https://github.com/auth0/node-jsonwebtoken/commit/da8f55c3c7b4dd0bfc07a2df228500fdd050242a)), closes [#569](https://github.com/auth0/node-jsonwebtoken/issues/569) ### Docs - Fix 'cert' token which isn't a cert (#554) ([0c24fe68cd2866cea6322016bf993cd897fefc98](https://github.com/auth0/node-jsonwebtoken/commit/0c24fe68cd2866cea6322016bf993cd897fefc98)), closes [#554](https://github.com/auth0/node-jsonwebtoken/issues/554) ## 8.4.0 - 2018-11-14 ### New Functionality - Add verify option for nonce validation (#540) ([e7938f06fdf2ed3aa88745b72b8ae4ee66c2d0d0](https://github.com/auth0/node-jsonwebtoken/commit/e7938f06fdf2ed3aa88745b72b8ae4ee66c2d0d0)), closes [#540](https://github.com/auth0/node-jsonwebtoken/issues/540) ### Bug Fixes - Updating Node version in Engines spec in package.json (#528) ([cfd1079305170a897dee6a5f55039783e6ee2711](https://github.com/auth0/node-jsonwebtoken/commit/cfd1079305170a897dee6a5f55039783e6ee2711)), closes [#528](https://github.com/auth0/node-jsonwebtoken/issues/528) [#509](https://github.com/auth0/node-jsonwebtoken/issues/509) - Fixed error message when empty string passed as expiresIn or notBefore option (#531) ([7f9604ac98d4d0ff8d873c3d2b2ea64bd285cb76](https://github.com/auth0/node-jsonwebtoken/commit/7f9604ac98d4d0ff8d873c3d2b2ea64bd285cb76)), closes [#531](https://github.com/auth0/node-jsonwebtoken/issues/531) ### Docs - Update README.md (#527) ([b76f2a80f5229ee5cde321dd2ff14aa5df16d283](https://github.com/auth0/node-jsonwebtoken/commit/b76f2a80f5229ee5cde321dd2ff14aa5df16d283)), closes [#527](https://github.com/auth0/node-jsonwebtoken/issues/527) - Update README.md (#538) ([1956c4006472fd285b8a85074257cbdbe9131cbf](https://github.com/auth0/node-jsonwebtoken/commit/1956c4006472fd285b8a85074257cbdbe9131cbf)), closes [#538](https://github.com/auth0/node-jsonwebtoken/issues/538) - Edited the README.md to make certain parts of the document for the api easier to read, emphasizing the examples. (#548) ([dc89a641293d42f72ecfc623ce2eabc33954cb9d](https://github.com/auth0/node-jsonwebtoken/commit/dc89a641293d42f72ecfc623ce2eabc33954cb9d)), closes [#548](https://github.com/auth0/node-jsonwebtoken/issues/548) - Document NotBeforeError (#529) ([29cd654b956529e939ae8f8c30b9da7063aad501](https://github.com/auth0/node-jsonwebtoken/commit/29cd654b956529e939ae8f8c30b9da7063aad501)), closes [#529](https://github.com/auth0/node-jsonwebtoken/issues/529) ### Test Improvements - Use lolex for faking date in tests (#491) ([677ead6d64482f2067b11437dda07309abe73cfa](https://github.com/auth0/node-jsonwebtoken/commit/677ead6d64482f2067b11437dda07309abe73cfa)), closes [#491](https://github.com/auth0/node-jsonwebtoken/issues/491) - Update dependencies used for running tests (#518) ([5498bdc4865ffb2ba2fd44d889fad7e83873bb33](https://github.com/auth0/node-jsonwebtoken/commit/5498bdc4865ffb2ba2fd44d889fad7e83873bb33)), closes [#518](https://github.com/auth0/node-jsonwebtoken/issues/518) - Minor test refactoring for recently added tests (#504) ([e2860a9d2a412627d79741a95bc7159971b923b9](https://github.com/auth0/node-jsonwebtoken/commit/e2860a9d2a412627d79741a95bc7159971b923b9)), closes [#504](https://github.com/auth0/node-jsonwebtoken/issues/504) - Create and implement async/sync test helpers (#523) ([683d8a9b31ad6327948f84268bd2c8e4350779d1](https://github.com/auth0/node-jsonwebtoken/commit/683d8a9b31ad6327948f84268bd2c8e4350779d1)), closes [#523](https://github.com/auth0/node-jsonwebtoken/issues/523) - Refactor tests related to audience and aud (#503) ([53d405e0223cce7c83cb51ecf290ca6bec1e9679](https://github.com/auth0/node-jsonwebtoken/commit/53d405e0223cce7c83cb51ecf290ca6bec1e9679)), closes [#503](https://github.com/auth0/node-jsonwebtoken/issues/503) - Refactor tests related to expiresIn and exp (#501) ([72f0d9e5b11a99082250665d1200c58182903fa6](https://github.com/auth0/node-jsonwebtoken/commit/72f0d9e5b11a99082250665d1200c58182903fa6)), closes [#501](https://github.com/auth0/node-jsonwebtoken/issues/501) - Refactor tests related to iat and maxAge (#507) ([877bd57ab2aca9b7d230805b21f921baed3da169](https://github.com/auth0/node-jsonwebtoken/commit/877bd57ab2aca9b7d230805b21f921baed3da169)), closes [#507](https://github.com/auth0/node-jsonwebtoken/issues/507) - Refactor tests related to iss and issuer (#543) ([0906a3fa80f52f959ac1b6343d3024ce5c7e9dea](https://github.com/auth0/node-jsonwebtoken/commit/0906a3fa80f52f959ac1b6343d3024ce5c7e9dea)), closes [#543](https://github.com/auth0/node-jsonwebtoken/issues/543) - Refactor tests related to kid and keyid (#545) ([88645427a0adb420bd3e149199a2a6bf1e17277e](https://github.com/auth0/node-jsonwebtoken/commit/88645427a0adb420bd3e149199a2a6bf1e17277e)), closes [#545](https://github.com/auth0/node-jsonwebtoken/issues/545) - Refactor tests related to notBefore and nbf (#497) ([39adf87a6faef3df984140f88e6724ddd709fd89](https://github.com/auth0/node-jsonwebtoken/commit/39adf87a6faef3df984140f88e6724ddd709fd89)), closes [#497](https://github.com/auth0/node-jsonwebtoken/issues/497) - Refactor tests related to subject and sub (#505) ([5a7fa23c0b4ac6c25304dab8767ef840b43a0eca](https://github.com/auth0/node-jsonwebtoken/commit/5a7fa23c0b4ac6c25304dab8767ef840b43a0eca)), closes [#505](https://github.com/auth0/node-jsonwebtoken/issues/505) - Implement async/sync tests for exp claim (#536) ([9ae3f207ac64b7450ea0a3434418f5ca58d8125e](https://github.com/auth0/node-jsonwebtoken/commit/9ae3f207ac64b7450ea0a3434418f5ca58d8125e)), closes [#536](https://github.com/auth0/node-jsonwebtoken/issues/536) - Implement async/sync tests for nbf claim (#537) ([88bc965061ed65299a395f42a100fb8f8c3c683e](https://github.com/auth0/node-jsonwebtoken/commit/88bc965061ed65299a395f42a100fb8f8c3c683e)), closes [#537](https://github.com/auth0/node-jsonwebtoken/issues/537) - Implement async/sync tests for sub claim (#534) ([342b07bb105a35739eb91265ba5b9dd33c300fc6](https://github.com/auth0/node-jsonwebtoken/commit/342b07bb105a35739eb91265ba5b9dd33c300fc6)), closes [#534](https://github.com/auth0/node-jsonwebtoken/issues/534) - Implement async/sync tests for the aud claim (#535) ([1c8ff5a68e6da73af2809c9d87faaf78602c99bb](https://github.com/auth0/node-jsonwebtoken/commit/1c8ff5a68e6da73af2809c9d87faaf78602c99bb)), closes [#535](https://github.com/auth0/node-jsonwebtoken/issues/535) ### CI - Added Istanbul to check test-coverage (#468) ([9676a8306428a045e34c3987bd0680fb952b44e3](https://github.com/auth0/node-jsonwebtoken/commit/9676a8306428a045e34c3987bd0680fb952b44e3)), closes [#468](https://github.com/auth0/node-jsonwebtoken/issues/468) - Complete ESLint conversion and cleanup (#490) ([cb1d2e1e40547f7ecf29fa6635041df6cbba7f40](https://github.com/auth0/node-jsonwebtoken/commit/cb1d2e1e40547f7ecf29fa6635041df6cbba7f40)), closes [#490](https://github.com/auth0/node-jsonwebtoken/issues/490) - Make code-coverage mandatory when running tests (#495) ([fb0084a78535bfea8d0087c0870e7e3614a2cbe5](https://github.com/auth0/node-jsonwebtoken/commit/fb0084a78535bfea8d0087c0870e7e3614a2cbe5)), closes [#495](https://github.com/auth0/node-jsonwebtoken/issues/495) ## 8.3.0 - 2018-06-11 - docs: add some clarifications (#473) ([cd33cc81f06068b9df6c224d300dc6f70d8904ab](https://github.com/auth0/node-jsonwebtoken/commit/cd33cc81f06068b9df6c224d300dc6f70d8904ab)), closes [#473](https://github.com/auth0/node-jsonwebtoken/issues/473) - ci: fix ci execution, remove not needed script (#472) ([c8ff7b2c3ffcd954a64a0273c20a7d1b22339aa5](https://github.com/auth0/node-jsonwebtoken/commit/c8ff7b2c3ffcd954a64a0273c20a7d1b22339aa5)), closes [#472](https://github.com/auth0/node-jsonwebtoken/issues/472) - new feature: Secret callback revisited (#480) ([d01cc7bcbdeb606d997a580f967b3169fcc622ba](https://github.com/auth0/node-jsonwebtoken/commit/d01cc7bcbdeb606d997a580f967b3169fcc622ba)), closes [#480](https://github.com/auth0/node-jsonwebtoken/issues/480) - docs:Update README.md (#461) ([f0e0954505f274da95a8d9603598e455b4d2c894](https://github.com/auth0/node-jsonwebtoken/commit/f0e0954505f274da95a8d9603598e455b4d2c894)), closes [#461](https://github.com/auth0/node-jsonwebtoken/issues/461) ## 8.2.2 - 2018-05-30 - security: deps: jws@3.1.5 (#477) ([ebde9b7cc75cb7ab5176de7ebc4a1d6a8f05bd51](https://github.com/auth0/node-jsonwebtoken/commit/ebde9b7cc75cb7ab5176de7ebc4a1d6a8f05bd51)), closes [#465](https://github.com/auth0/node-jsonwebtoken/issues/465) - docs: add some clarifications (#473) ([cd33cc81f06068b9df6c224d300dc6f70d8904ab](https://github.com/auth0/node-jsonwebtoken/commit/cd33cc81f06068b9df6c224d300dc6f70d8904ab)), closes [#473](https://github.com/auth0/node-jsonwebtoken/issues/473) - ci: fix ci execution, remove not needed script (#472) ([c8ff7b2c3ffcd954a64a0273c20a7d1b22339aa5](https://github.com/auth0/node-jsonwebtoken/commit/c8ff7b2c3ffcd954a64a0273c20a7d1b22339aa5)), closes [#472](https://github.com/auth0/node-jsonwebtoken/issues/472) - docs: Update README.md (#461) ([f0e0954505f274da95a8d9603598e455b4d2c894](https://github.com/auth0/node-jsonwebtoken/commit/f0e0954505f274da95a8d9603598e455b4d2c894)), closes [#461](https://github.com/auth0/node-jsonwebtoken/issues/461) ## 8.2.1 - 2018-04-05 - bug fix: Check payload is not null when decoded. (#444) ([1232ae9352ce5fd1ca6c593291ce6ad0834a1ff5](https://github.com/auth0/node-jsonwebtoken/commit/1232ae9352ce5fd1ca6c593291ce6ad0834a1ff5)) - docs: Clarify that buffer/string payloads must be JSON (#442) ([e8ac1be7565a3fd986d40cb5e31a9f6c4d9aed1b](https://github.com/auth0/node-jsonwebtoken/commit/e8ac1be7565a3fd986d40cb5e31a9f6c4d9aed1b)) ## 8.2.0 - 2018-03-02 - Add a new mutatePayload option (#446) ([d6d7c5e5103f05a92d3633ac190d3025a0455be0](https://github.com/auth0/node-jsonwebtoken/commit/d6d7c5e5103f05a92d3633ac190d3025a0455be0)) ## 8.1.1 - 2018-01-22 - ci: add newer node versions to build matrix (#428) ([83f3eee44e122da06f812d7da4ace1fa26c24d9d](https://github.com/auth0/node-jsonwebtoken/commit/83f3eee44e122da06f812d7da4ace1fa26c24d9d)) - deps: Bump ms version to add support for negative numbers (#438) ([25e0e624545eaef76f3c324a134bf103bc394724](https://github.com/auth0/node-jsonwebtoken/commit/25e0e624545eaef76f3c324a134bf103bc394724)) - docs: Minor typo (#424) ([dddcb73ac05de11b81feeb629f6cf78dd03d2047](https://github.com/auth0/node-jsonwebtoken/commit/dddcb73ac05de11b81feeb629f6cf78dd03d2047)) - bug fix: Not Before (nbf) calculated based on iat/timestamp (#437) ([2764a64908d97c043d62eba0bf6c600674f9a6d6](https://github.com/auth0/node-jsonwebtoken/commit/2764a64908d97c043d62eba0bf6c600674f9a6d6)), closes [#435](https://github.com/auth0/node-jsonwebtoken/issues/435) ## 8.1.0 - 2017-10-09 - #402: Don't fail if captureStackTrace is not a function (#410) ([77ee965d9081faaf21650f266399f203f69533c5](https://github.com/auth0/node-jsonwebtoken/commit/77ee965d9081faaf21650f266399f203f69533c5)) - #403: Clarify error wording for "Expected object" error. (#409) ([bb27eb346f0ff675a320b2de16b391a7cfeadc58](https://github.com/auth0/node-jsonwebtoken/commit/bb27eb346f0ff675a320b2de16b391a7cfeadc58)) - Enhance audience check to verify against regular expressions (#398) ([81501a17da230af7b74a3f7535ab5cd3a19c8315](https://github.com/auth0/node-jsonwebtoken/commit/81501a17da230af7b74a3f7535ab5cd3a19c8315)) ## 8.0.1 - 2017-09-12 - Remove `lodash.isarray` dependency (#394) ([7508e8957cb1c778f72fa9a363a7b135b3c9c36d](https://github.com/auth0/node-jsonwebtoken/commit/7508e8957cb1c778f72fa9a363a7b135b3c9c36d)) ## 8.0.0 - 2017-09-06 **Breaking changes: See [Migration notes from v7](https://github.com/auth0/node-jsonwebtoken/wiki/Migration-Notes:-v7-to-v8)** - docs: readme, migration notes ([12cd8f7f47224f904f6b8f39d1dee73775de4f6f](https://github.com/auth0/node-jsonwebtoken/commit/12cd8f7f47224f904f6b8f39d1dee73775de4f6f)) - verify: remove process.nextTick (#302) ([3305cf04e3f674b9fb7e27c9b14ddd159650ff82](https://github.com/auth0/node-jsonwebtoken/commit/3305cf04e3f674b9fb7e27c9b14ddd159650ff82)) - Reduce size of NPM package (#347) ([0be5409ac6592eeaae373dce91ec992fa101bd8a](https://github.com/auth0/node-jsonwebtoken/commit/0be5409ac6592eeaae373dce91ec992fa101bd8a)) - Remove joi to shrink module size (#348) ([2e7e68dbd59e845cdd940afae0a296f48438445f](https://github.com/auth0/node-jsonwebtoken/commit/2e7e68dbd59e845cdd940afae0a296f48438445f)) - maxAge: Add validation to timespan result ([66a4f8b996c8357727ce62a84605a005b2f5eb18](https://github.com/auth0/node-jsonwebtoken/commit/66a4f8b996c8357727ce62a84605a005b2f5eb18)) ## 7.4.3 - 2017-08-17 - Fix breaking change on 7.4.2 for empty secret + "none" algorithm (sync code style) ([PR 386](https://github.com/auth0/node-jsonwebtoken/pull/386)) ## 7.4.2 - 2017-08-04 - bugfix: sign: add check to be sure secret has a value ([c584d1cbc34b788977b36f17cd57ab2212f1230e](https://github.com/auth0/node-jsonwebtoken/commit/c584d1cbc34b788977b36f17cd57ab2212f1230e)) - docs: about refreshing tokens ([016fc10b847bfbb76b82171cb530f32d7da2001b](https://github.com/auth0/node-jsonwebtoken/commit/016fc10b847bfbb76b82171cb530f32d7da2001b)) - docs: verifying with base64 encoded secrets ([c25e9906801f89605080cc71b3ee23a5e45a5811](https://github.com/auth0/node-jsonwebtoken/commit/c25e9906801f89605080cc71b3ee23a5e45a5811)) - tests: Add tests for ES256 ([89900ea00735f76b04f437c9f542285b420fa9cb](https://github.com/auth0/node-jsonwebtoken/commit/89900ea00735f76b04f437c9f542285b420fa9cb)) - docs: document keyid as option (#361) ([00086c2c006d7fc1a47bae02fa87d194d79aa558](https://github.com/auth0/node-jsonwebtoken/commit/00086c2c006d7fc1a47bae02fa87d194d79aa558)) - docs: readme: Using private key with passpharase (#353) ([27a7f1d4f35b662426ff0270526d48658da4c8b7](https://github.com/auth0/node-jsonwebtoken/commit/27a7f1d4f35b662426ff0270526d48658da4c8b7)) ## 7.4.1 - 2017-05-17 - bump ms to v2 due a ReDoS vulnerability (#352) ([adcfd6ae4088c838769d169f8cd9154265aa13e0](https://github.com/auth0/node-jsonwebtoken/commit/adcfd6ae4088c838769d169f8cd9154265aa13e0)) ## 7.4.0 - 2017-04-24 - Add docs about numeric date fields ([659f73119900a4d837650d9b3f5af4e64a2f843b](https://github.com/auth0/node-jsonwebtoken/commit/659f73119900a4d837650d9b3f5af4e64a2f843b)) - Make Options object optional for callback-ish sign ([e202c4fd00c35a24e9ab606eab89186ade13d0cc](https://github.com/auth0/node-jsonwebtoken/commit/e202c4fd00c35a24e9ab606eab89186ade13d0cc)) ## 7.3.0 - 2017-02-13 - Add more information to `maxAge` option in README ([1b0592e99cc8def293eed177e2575fa7f1cf7aa5](https://github.com/auth0/node-jsonwebtoken/commit/1b0592e99cc8def293eed177e2575fa7f1cf7aa5)) - Add `clockTimestamp` option to `verify()` you can set the current time in seconds with it (#274) ([8fdc1504f4325e7003894ffea078da9cba5208d9](https://github.com/auth0/node-jsonwebtoken/commit/8fdc1504f4325e7003894ffea078da9cba5208d9)) - Fix handling non string tokens on `verify()` input (#305) ([1b6ec8d466504f58c5a6e2dae3360c828bad92fb](https://github.com/auth0/node-jsonwebtoken/commit/1b6ec8d466504f58c5a6e2dae3360c828bad92fb)), closes [#305](https://github.com/auth0/node-jsonwebtoken/issues/305) - Fixed a simple typo in docs (#287) ([a54240384e24e18c00e75884295306db311d0cb7](https://github.com/auth0/node-jsonwebtoken/commit/a54240384e24e18c00e75884295306db311d0cb7)), closes [#287](https://github.com/auth0/node-jsonwebtoken/issues/287) - Raise jws.decode error to avoid confusion with "invalid token" error (#294) ([7f68fe06c88d5c5653785bd66bc68c5b20e1bd8e](https://github.com/auth0/node-jsonwebtoken/commit/7f68fe06c88d5c5653785bd66bc68c5b20e1bd8e)) - rauchg/ms.js changed to zeit/ms (#303) ([35d84152a6b716d757cb5b1dd3c79fe3a1bc0628](https://github.com/auth0/node-jsonwebtoken/commit/35d84152a6b716d757cb5b1dd3c79fe3a1bc0628)) ## 7.2.1 - 2016-12-07 - add nsp check to find vulnerabilities on npm test ([4219c34b5346811c07f520f10516cc495bcc70dd](https://github.com/auth0/node-jsonwebtoken/commit/4219c34b5346811c07f520f10516cc495bcc70dd)) - revert to joi@^6 to keep ES5 compatibility ([51d4796c07344bf817687f7ccfeef78f00bf5b4f](https://github.com/auth0/node-jsonwebtoken/commit/51d4796c07344bf817687f7ccfeef78f00bf5b4f)) ## 7.2.0 - 2016-12-06 - improve the documentation for expiration ([771e0b5f9bed90771fb79140eb38e51a3ecac8f0](https://github.com/auth0/node-jsonwebtoken/commit/771e0b5f9bed90771fb79140eb38e51a3ecac8f0)) - Restructured a sentence ([ccc7610187a862f7a50177eadc9152eef26cd065](https://github.com/auth0/node-jsonwebtoken/commit/ccc7610187a862f7a50177eadc9152eef26cd065)) - Allow `keyid` on `sign`. ([b412be91b89acb3a742bb609d3b54e47e1dfc441](https://github.com/auth0/node-jsonwebtoken/commit/b412be91b89acb3a742bb609d3b54e47e1dfc441)) - upgrade joi ([715e3d928023d414d45c6dc3f096a7c8448139ae](https://github.com/auth0/node-jsonwebtoken/commit/715e3d928023d414d45c6dc3f096a7c8448139ae)) - upgrade to latest nodes and Travis infrastructure ([3febcc1dd23ecdec1abbf89313959941d15eb47a](https://github.com/auth0/node-jsonwebtoken/commit/3febcc1dd23ecdec1abbf89313959941d15eb47a)) ## 7.1.10 - 2016-12-06 - Bump node-jws version number ([07813dd7194630c9f452684279178af76464a759](https://github.com/auth0/node-jsonwebtoken/commit/07813dd7194630c9f452684279178af76464a759)) - improve the documentation for expiration ([771e0b5f9bed90771fb79140eb38e51a3ecac8f0](https://github.com/auth0/node-jsonwebtoken/commit/771e0b5f9bed90771fb79140eb38e51a3ecac8f0)) ## 7.1.9 - 2016-08-11 - Revert "Merge branch 'venatir-master'" ([d06359ef3b4e619680e043ee7c16adda16598f52](https://github.com/auth0/node-jsonwebtoken/commit/d06359ef3b4e619680e043ee7c16adda16598f52)) ## 7.1.8 - 2016-08-10 - Fixed tests, however typ: 'JWT' should not be in the options at all, so please review other tests ([01903bcdc61b4ed429acbbd1fe0ffe0db364473b](https://github.com/auth0/node-jsonwebtoken/commit/01903bcdc61b4ed429acbbd1fe0ffe0db364473b)) - Removing unnecessary extra decoding. jwtString is already verified as valid and signature checked ([55d5834f7b637011e1d8b927ff78a92a5fd521cf](https://github.com/auth0/node-jsonwebtoken/commit/55d5834f7b637011e1d8b927ff78a92a5fd521cf)) - update changelog ([5117aacd0118a10331889a64e61d8186112d8a23](https://github.com/auth0/node-jsonwebtoken/commit/5117aacd0118a10331889a64e61d8186112d8a23)) ## 7.1.7 - 2016-07-29 - Use lodash.once instead of unlicensed/unmaintained cb ([3ac95ad93ef3068a64e03d8d14deff231b1ed529](https://github.com/auth0/node-jsonwebtoken/commit/3ac95ad93ef3068a64e03d8d14deff231b1ed529)) ## 7.1.6 - 2016-07-15 - fix issue with buffer payload. closes #216 ([6b50ff324b4dfd2cb0e49b666f14a6672d015b22](https://github.com/auth0/node-jsonwebtoken/commit/6b50ff324b4dfd2cb0e49b666f14a6672d015b22)), closes [#216](https://github.com/auth0/node-jsonwebtoken/issues/216) ## 7.1.5 - 2016-07-15 - update jws in package.json ([b6260951eefc68aae5f4ede359210761f901ff7a](https://github.com/auth0/node-jsonwebtoken/commit/b6260951eefc68aae5f4ede359210761f901ff7a)) ## 7.1.4 - 2016-07-14 - add redundant test ([bece8816096f324511c3efcb8db0e64b75d757a1](https://github.com/auth0/node-jsonwebtoken/commit/bece8816096f324511c3efcb8db0e64b75d757a1)) - fix an issue of double callback on error ([758ca5eeca2f1b06c32c9fce70642bf488b2e52b](https://github.com/auth0/node-jsonwebtoken/commit/758ca5eeca2f1b06c32c9fce70642bf488b2e52b)) ## 7.1.2 - 2016-07-12 - do not stringify the payload when signing async - closes #224 ([084f537d3dfbcef2bea411cc0a1515899cc8aa21](https://github.com/auth0/node-jsonwebtoken/commit/084f537d3dfbcef2bea411cc0a1515899cc8aa21)), closes [#224](https://github.com/auth0/node-jsonwebtoken/issues/224) ## 7.1.1 - 2016-07-12 - do not mutate options in jwt.verify, closes #227 ([63263a28a268624dab0927b9ad86fffa44a10f84](https://github.com/auth0/node-jsonwebtoken/commit/63263a28a268624dab0927b9ad86fffa44a10f84)), closes [#227](https://github.com/auth0/node-jsonwebtoken/issues/227) - refactor into multiple files ([e11d505207fa33501298300c9accbfb809d8748d](https://github.com/auth0/node-jsonwebtoken/commit/e11d505207fa33501298300c9accbfb809d8748d)) ## 7.1.0 - 2016-07-12 - Exp calculated based on iat. fix #217 ([757a16e0e35ad19f9e456820f55d5d9f3fc76aee](https://github.com/auth0/node-jsonwebtoken/commit/757a16e0e35ad19f9e456820f55d5d9f3fc76aee)), closes [#217](https://github.com/auth0/node-jsonwebtoken/issues/217) ## 7.0.0 - 2016-05-19 - change jwt.sign to return errors on callback instead of throwing errors ([1e46c5a42aa3dab8478efa4081d8f8f5c5485d56](https://github.com/auth0/node-jsonwebtoken/commit/1e46c5a42aa3dab8478efa4081d8f8f5c5485d56)) ## 6.2.0 - 2016-04-29 - add support for `options.clockTolerance` to `jwt.verify` ([65ddea934f226bf06bc9d6a55be9587515cfc38d](https://github.com/auth0/node-jsonwebtoken/commit/65ddea934f226bf06bc9d6a55be9587515cfc38d)) ## 6.1.2 - 2016-04-29 - fix sign method for node.js 0.12. closes #193 ([9c38374142d3929be3c9314b5e9bc5d963c5955f](https://github.com/auth0/node-jsonwebtoken/commit/9c38374142d3929be3c9314b5e9bc5d963c5955f)), closes [#193](https://github.com/auth0/node-jsonwebtoken/issues/193) - improve async test ([7b0981380ddc40a5f1208df520631785b5ffb85a](https://github.com/auth0/node-jsonwebtoken/commit/7b0981380ddc40a5f1208df520631785b5ffb85a)) ## 6.1.0 - 2016-04-27 - verify unsigned tokens ([ec880791c10ed5ef7c8df7bf28ebb95c810479ed](https://github.com/auth0/node-jsonwebtoken/commit/ec880791c10ed5ef7c8df7bf28ebb95c810479ed)) ## 6.0.1 - 2016-04-27 This was an immediate change after publishing 6.0.0. - throw error on invalid options when the payload is not an object ([304f1b33075f79ed66f784e27dc4f5307aa39e27](https://github.com/auth0/node-jsonwebtoken/commit/304f1b33075f79ed66f784e27dc4f5307aa39e27)) ## 6.0.0 - 2016-04-27 - Change .sign to standard async callback ([50873c7d45d2733244d5da8afef3d1872e657a60](https://github.com/auth0/node-jsonwebtoken/commit/50873c7d45d2733244d5da8afef3d1872e657a60)) - Improved the options for the `sign` method ([53c3987b3cc34e95eb396b26fc9b051276e2f6f9](https://github.com/auth0/node-jsonwebtoken/commit/53c3987b3cc34e95eb396b26fc9b051276e2f6f9)) - throw error on invalid options like `expiresIn` when the payload is not an object ([304f1b33075f79ed66f784e27dc4f5307aa39e27](https://github.com/auth0/node-jsonwebtoken/commit/304f1b33075f79ed66f784e27dc4f5307aa39e27)) - `expiresInMinutes` and `expiresInSeconds` are deprecated and no longer supported. - `notBeforeInMinutes` and `notBeforeInSeconds` are deprecated and no longer supported. - `options` are strongly validated. - `options.expiresIn`, `options.notBefore`, `options.audience`, `options.issuer`, `options.subject` and `options.jwtid` are mutually exclusive with `payload.exp`, `payload.nbf`, `payload.aud`, `payload.iss` - `options.algorithm` is properly validated. - `options.headers` is renamed to `options.header`. - update CHANGELOG to reflect most of the changes. closes #136 ([b87a1a8d2e2533fbfab518765a54f00077918eb7](https://github.com/auth0/node-jsonwebtoken/commit/b87a1a8d2e2533fbfab518765a54f00077918eb7)), closes [#136](https://github.com/auth0/node-jsonwebtoken/issues/136) - update readme ([53a88ecf4494e30e1d62a1cf3cc354650349f486](https://github.com/auth0/node-jsonwebtoken/commit/53a88ecf4494e30e1d62a1cf3cc354650349f486)) ## 5.7.0 - 2016-02-16 - add support for validating multiples issuers. closes #163 ([39d9309ae05648dbd72e5fd1993df064ad0e8fa5](https://github.com/auth0/node-jsonwebtoken/commit/39d9309ae05648dbd72e5fd1993df064ad0e8fa5)), closes [#163](https://github.com/auth0/node-jsonwebtoken/issues/163) ## 5.6.1 - 2016-02-16 - 5.6.1 ([06d8209d499dbc9a8dd978ab6cbb9c6818fde203](https://github.com/auth0/node-jsonwebtoken/commit/06d8209d499dbc9a8dd978ab6cbb9c6818fde203)) - fix wrong error when setting expiration on non-object payload. closes #153 ([7f7d76edfd918d6afc7c7cead888caa42ccaceb4](https://github.com/auth0/node-jsonwebtoken/commit/7f7d76edfd918d6afc7c7cead888caa42ccaceb4)), closes [#153](https://github.com/auth0/node-jsonwebtoken/issues/153) ## 5.6.0 - 2016-02-16 - added missing validations of sub and jti ([a1affe960d0fc52e9042bcbdedb65734f8855580](https://github.com/auth0/node-jsonwebtoken/commit/a1affe960d0fc52e9042bcbdedb65734f8855580)) - Fix tests in jwt.rs.tests.js which causes 4 to fail ([8aedf2b1f575b0d9575c1fc9f2ac7bc868f75ff1](https://github.com/auth0/node-jsonwebtoken/commit/8aedf2b1f575b0d9575c1fc9f2ac7bc868f75ff1)) - Update README.md ([349b7cd00229789b138928ca060d3ef015aedaf9](https://github.com/auth0/node-jsonwebtoken/commit/349b7cd00229789b138928ca060d3ef015aedaf9)) ## 5.5.4 - 2016-01-04 - minor ([46552e7c45025c76e3f647680d7539a66bfac612](https://github.com/auth0/node-jsonwebtoken/commit/46552e7c45025c76e3f647680d7539a66bfac612)) ## 5.5.3 - 2016-01-04 - add a console.warn on invalid options for string payloads ([71200f14deba0533d3261266348338fac2d14661](https://github.com/auth0/node-jsonwebtoken/commit/71200f14deba0533d3261266348338fac2d14661)) - minor ([65b1f580382dc58dd3da6f47a52713776fd7cdf2](https://github.com/auth0/node-jsonwebtoken/commit/65b1f580382dc58dd3da6f47a52713776fd7cdf2)) ## 5.5.2 - 2016-01-04 - fix signing method with sealed objects, do not modify the params object. closes #147 ([be9c09af83b09c9e72da8b2c6166fa51d92aeab6](https://github.com/auth0/node-jsonwebtoken/commit/be9c09af83b09c9e72da8b2c6166fa51d92aeab6)), closes [#147](https://github.com/auth0/node-jsonwebtoken/issues/147) ## 5.5.1 - 2016-01-04 - fix nbf verification. fix #152 ([786d37b299c67771b5e71a2ca476666ab0f97d98](https://github.com/auth0/node-jsonwebtoken/commit/786d37b299c67771b5e71a2ca476666ab0f97d98)), closes [#152](https://github.com/auth0/node-jsonwebtoken/issues/152) ## 5.5.0 - 2015-12-28 - improvements to nbf and jti claims ([46372e928f6d2e7398f9b88022ca617d2a3b0699](https://github.com/auth0/node-jsonwebtoken/commit/46372e928f6d2e7398f9b88022ca617d2a3b0699)) - Remove duplicate payload line (fix bug in IE strict mode) ([8163d698e0c5ad8c44817a5dcd42a15d7e9c6bc8](https://github.com/auth0/node-jsonwebtoken/commit/8163d698e0c5ad8c44817a5dcd42a15d7e9c6bc8)) - Remove duplicate require('ms') line ([7c00bcbcbf8f7503a1070b394a165eccd41de66f](https://github.com/auth0/node-jsonwebtoken/commit/7c00bcbcbf8f7503a1070b394a165eccd41de66f)) - Update README to reflect addition of async sign ([d661d4b6f68eb417834c99b36769444723041ccf](https://github.com/auth0/node-jsonwebtoken/commit/d661d4b6f68eb417834c99b36769444723041ccf)) ## 5.4.0 - 2015-10-02 - deprecate expireInMinutes and expireInSeconds - in favor of expiresIn ([39ecc6f8f310f8462e082f1d53de0b4222b29b6f](https://github.com/auth0/node-jsonwebtoken/commit/39ecc6f8f310f8462e082f1d53de0b4222b29b6f)) ## 5.3.0 - 2015-10-02 - 5.3.0 ([5d559ced3fbf10c1adae2e5792deda06ea89bcd3](https://github.com/auth0/node-jsonwebtoken/commit/5d559ced3fbf10c1adae2e5792deda06ea89bcd3)) - minor ([6e81ff87a3799b0e56db09cbae42a97e784716c4](https://github.com/auth0/node-jsonwebtoken/commit/6e81ff87a3799b0e56db09cbae42a97e784716c4)) ## 5.1.0 - 2015-10-02 - added async signing ([9414fbcb15a1f9cf4fe147d070e9424c547dabba](https://github.com/auth0/node-jsonwebtoken/commit/9414fbcb15a1f9cf4fe147d070e9424c547dabba)) - Update README.md ([40b2aaaa843442dfb8ee7b574f0a788177e7c904](https://github.com/auth0/node-jsonwebtoken/commit/40b2aaaa843442dfb8ee7b574f0a788177e7c904)) ## 5.0.5 - 2015-08-19 - add ms dep to package.json ([f13b3fb7f29dff787e7c91ebe2eb5adeeb05f251](https://github.com/auth0/node-jsonwebtoken/commit/f13b3fb7f29dff787e7c91ebe2eb5adeeb05f251)) - add note to explain, related to #96 #101 #6 ([dd8969e0e6ed0bcb9cae905d2b1a96476bd85da3](https://github.com/auth0/node-jsonwebtoken/commit/dd8969e0e6ed0bcb9cae905d2b1a96476bd85da3)) - add tests for options.headers ([7787dd74e705787c39a871ca29c75a2e0a3948ac](https://github.com/auth0/node-jsonwebtoken/commit/7787dd74e705787c39a871ca29c75a2e0a3948ac)) - add tests for verify expires ([d7c5793d98c300603440ab460c11665f661ad3a0](https://github.com/auth0/node-jsonwebtoken/commit/d7c5793d98c300603440ab460c11665f661ad3a0)) - add verify option maxAge (with tests) ([49d54e54f7e70b1c53a2e4ee67e116c907d75319](https://github.com/auth0/node-jsonwebtoken/commit/49d54e54f7e70b1c53a2e4ee67e116c907d75319)) - fix spelling error in error message ([8078b11b224fa05ac9003ca5aa2c85e9f0128cfb](https://github.com/auth0/node-jsonwebtoken/commit/8078b11b224fa05ac9003ca5aa2c85e9f0128cfb)) - Fix typo options.header is not a documented option + ([5feaa5b962ccbddeff054817a410f7b0c1e6ce7f](https://github.com/auth0/node-jsonwebtoken/commit/5feaa5b962ccbddeff054817a410f7b0c1e6ce7f)) - update JWT spec link. closes #112 ([f5fa50f797456a12240589161835c7ea30807195](https://github.com/auth0/node-jsonwebtoken/commit/f5fa50f797456a12240589161835c7ea30807195)), closes [#112](https://github.com/auth0/node-jsonwebtoken/issues/112) ## 5.0.3 - 2015-07-15 - Added nbf support ([f26ba4e2fa197a20497632b63ffcd13ae93aacc4](https://github.com/auth0/node-jsonwebtoken/commit/f26ba4e2fa197a20497632b63ffcd13ae93aacc4)) - Added support for subject and jwt id ([ab76ec5bc554e2d1e25376ddb7cea711d86af651](https://github.com/auth0/node-jsonwebtoken/commit/ab76ec5bc554e2d1e25376ddb7cea711d86af651)) - Fix `this` referring to the global object instead of `module.exports` in `verify()` ([93f554312e37129027fcf4916f48cb8d1b53588c](https://github.com/auth0/node-jsonwebtoken/commit/93f554312e37129027fcf4916f48cb8d1b53588c)) - Fix typo, line 139 README, complete option for .decode. ([59c110aeb8c7c1847ef2ffd77702d13627c89e10](https://github.com/auth0/node-jsonwebtoken/commit/59c110aeb8c7c1847ef2ffd77702d13627c89e10)) - minor ([61ff1172272b582902313e958058ff22413494af](https://github.com/auth0/node-jsonwebtoken/commit/61ff1172272b582902313e958058ff22413494af)) ## 5.0.2 - 2015-06-15 - fix typo in docs . closes #86 ([3d3413221f36acef4dfd1cbed87f1f3565cd6f84](https://github.com/auth0/node-jsonwebtoken/commit/3d3413221f36acef4dfd1cbed87f1f3565cd6f84)), closes [#86](https://github.com/auth0/node-jsonwebtoken/issues/86) ## 5.0.1 - 2015-05-15 - Add option to return header and payload when decoding. ([7254e011b59f892d1947e6c11819281adac7069d](https://github.com/auth0/node-jsonwebtoken/commit/7254e011b59f892d1947e6c11819281adac7069d)) - Avoid uncaught "SyntaxError: Unexpected token ͧ" error. ([0dc59cd6ee15d83a606acffa7909ee76176ae186](https://github.com/auth0/node-jsonwebtoken/commit/0dc59cd6ee15d83a606acffa7909ee76176ae186)) - Document complete option in README. ([ec32b20241a74d9681ea26e1a7024b4642468c00](https://github.com/auth0/node-jsonwebtoken/commit/ec32b20241a74d9681ea26e1a7024b4642468c00)) - Fix example in README, silence verbose logging. ([ba3174d10033c41e9c211a38f1cc67f74fbd7f69](https://github.com/auth0/node-jsonwebtoken/commit/ba3174d10033c41e9c211a38f1cc67f74fbd7f69)) - Fix link to auth0.com in README ([1b3c5ff72c9bc25e9271646e679f3080f2a042a0](https://github.com/auth0/node-jsonwebtoken/commit/1b3c5ff72c9bc25e9271646e679f3080f2a042a0)) - Immediate return if not decoded. ([851bda2b10168f3269c3da6e74d310742f31a193](https://github.com/auth0/node-jsonwebtoken/commit/851bda2b10168f3269c3da6e74d310742f31a193)) - Prevent throw on undefined/null secret ([0fdf78d4dbf609455f3277d6169a987aef0384d4](https://github.com/auth0/node-jsonwebtoken/commit/0fdf78d4dbf609455f3277d6169a987aef0384d4)) - Removed path from test ([d6240e24186732d368bffe21143becf44c38f0d6](https://github.com/auth0/node-jsonwebtoken/commit/d6240e24186732d368bffe21143becf44c38f0d6)) - Simplified checking for missing key ([f1cffd033bffc44f20558eda4a797c3fa2f4ee05](https://github.com/auth0/node-jsonwebtoken/commit/f1cffd033bffc44f20558eda4a797c3fa2f4ee05)) - Typo ([ffe68dbe0219bab535c1018448eb4c0b22f1f902](https://github.com/auth0/node-jsonwebtoken/commit/ffe68dbe0219bab535c1018448eb4c0b22f1f902)) - Update CHANGELOG.md ([927cce0dad1bc9aad75aeef53e276cf4cfc0d776](https://github.com/auth0/node-jsonwebtoken/commit/927cce0dad1bc9aad75aeef53e276cf4cfc0d776)) - Update CHANGELOG.md ([6879e0fdde222995c70a3a69a4af94993d9c667e](https://github.com/auth0/node-jsonwebtoken/commit/6879e0fdde222995c70a3a69a4af94993d9c667e)) - Update CHANGELOG.md ([c5596c10e8705727fa13e0394184a606083078bc](https://github.com/auth0/node-jsonwebtoken/commit/c5596c10e8705727fa13e0394184a606083078bc)) - Update CHANGELOG.md ([07541f0315f26d179e1cde92732b6124d6869b6f](https://github.com/auth0/node-jsonwebtoken/commit/07541f0315f26d179e1cde92732b6124d6869b6f)) - Update CHANGELOG.md ([e6465d48ddd1dc2c3297229b28c78fd5490a2ba9](https://github.com/auth0/node-jsonwebtoken/commit/e6465d48ddd1dc2c3297229b28c78fd5490a2ba9)) ## [5.0.0] - 2015-04-11 ### Changed - [sign] Only set defautl `iat` if the user does not specify that argument. https://github.com/auth0/node-jsonwebtoken/commit/e900282a8d2dff1d4dec815f7e6aa7782e867d91 https://github.com/auth0/node-jsonwebtoken/commit/35036b188b4ee6b42df553bbb93bc8a6b19eae9d https://github.com/auth0/node-jsonwebtoken/commit/954bd7a312934f03036b6bb6f00edd41f29e54d9 https://github.com/auth0/node-jsonwebtoken/commit/24a370080e0b75f11d4717cd2b11b2949d95fc2e https://github.com/auth0/node-jsonwebtoken/commit/a77df6d49d4ec688dfd0a1cc723586bffe753516 ### Security - [verify] Update to jws@^3.0.0 and renaming `header.alg` mismatch exception to `invalid algorithm` and adding more mismatch tests. As `jws@3.0.0` changed the verify method signature to be `jws.verify(signature, algorithm, secretOrKey)`, the token header must be decoded first in order to make sure that the `alg` field matches one of the allowed `options.algorithms`. After that, the now validated `header.alg` is passed to `jws.verify` As the order of steps has changed, the error that was thrown when the JWT was invalid is no longer the `jws` one: ``` { [Error: Invalid token: no header in signature 'a.b.c'] code: 'MISSING_HEADER', signature: 'a.b.c' } ``` That old error (removed from jws) has been replaced by a `JsonWebTokenError` with message `invalid token`. > Important: versions >= 4.2.2 this library are safe to use but we decided to deprecate everything `< 5.0.0` to prevent security warnings from library `node-jws` when doing `npm install`. https://github.com/auth0/node-jsonwebtoken/commit/634b8ed0ff5267dc25da5c808634208af109824e https://github.com/auth0/node-jsonwebtoken/commit/9f24ffd5791febb449d4d03ff58d7807da9b9b7e https://github.com/auth0/node-jsonwebtoken/commit/19e6cc6a1f2fd90356f89b074223b9665f2aa8a2 https://github.com/auth0/node-jsonwebtoken/commit/1e4623420159c6410616f02a44ed240f176287a9 https://github.com/auth0/node-jsonwebtoken/commit/954bd7a312934f03036b6bb6f00edd41f29e54d9 https://github.com/auth0/node-jsonwebtoken/commit/24a370080e0b75f11d4717cd2b11b2949d95fc2e https://github.com/auth0/node-jsonwebtoken/commit/a77df6d49d4ec688dfd0a1cc723586bffe753516 ## [4.2.2] - 2015-03-26 ### Fixed - [asymmetric-keys] Fix verify for RSAPublicKey formated keys (`jfromaniello - awlayton`) https://github.com/auth0/node-jsonwebtoken/commit/402794663b9521bf602fcc6f2e811e7d3912f9dc https://github.com/auth0/node-jsonwebtoken/commit/8df6aabbc7e1114c8fb3917931078254eb52c222 ## [4.2.1] - 2015-03-17 ### Fixed - [asymmetric-keys] Fixed issue when public key starts with BEING PUBLIC KEY (https://github.com/auth0/node-jsonwebtoken/issues/70) (`jfromaniello`) https://github.com/auth0/node-jsonwebtoken/commit/7017e74db9b194448ff488b3e16468ada60c4ee5 ## [4.2.0] - 2015-03-16 ### Security - [asymmetric-keys] Making sure a token signed with an asymmetric key will be verified using a asymmetric key. When the verification part was expecting a token digitally signed with an asymmetric key (RS/ES family) of algorithms an attacker could send a token signed with a symmetric algorithm (HS* family). The issue was caused because the same signature was used to verify both type of tokens (`verify` method parameter: `secretOrPublicKey`). This change adds a new parameter to the verify called `algorithms`. This can be used to specify a list of supported algorithms, but the default value depends on the secret used: if the secretOrPublicKey contains the string `BEGIN CERTIFICATE` the default is `[ 'RS256','RS384','RS512','ES256','ES384','ES512' ]` otherwise is `[ 'HS256','HS384','HS512' ]`. (`jfromaniello`) https://github.com/auth0/node-jsonwebtoken/commit/c2bf7b2cd7e8daf66298c2d168a008690bc4bdd3 https://github.com/auth0/node-jsonwebtoken/commit/1bb584bc382295eeb7ee8c4452a673a77a68b687 ## [4.1.0] - 2015-03-10 ### Changed - Assume the payload is JSON even when there is no `typ` property. [5290db1](https://github.com/auth0/node-jsonwebtoken/commit/5290db1bd74f74cd38c90b19e2355ef223a4d931) ## [4.0.0] - 2015-03-06 ### Changed - The default encoding is now utf8 instead of binary. [92d33bd](https://github.com/auth0/node-jsonwebtoken/commit/92d33bd99a3416e9e5a8897d9ad8ff7d70a00bfd) - Add `encoding` as a new option to `sign`. [1fc385e](https://github.com/auth0/node-jsonwebtoken/commit/1fc385ee10bd0018cd1441552dce6c2e5a16375f) - Add `ignoreExpiration` to `verify`. [8d4da27](https://github.com/auth0/node-jsonwebtoken/commit/8d4da279e1b351ac71ace276285c9255186d549f) - Add `expiresInSeconds` to `sign`. [dd156cc](https://github.com/auth0/node-jsonwebtoken/commit/dd156cc30f17028744e60aec0502897e34609329) ### Fixed - Fix wrong error message when the audience doesn't match. [44e3c8d](https://github.com/auth0/node-jsonwebtoken/commit/44e3c8d757e6b4e2a57a69a035f26b4abec3e327) - Fix wrong error message when the issuer doesn't match. [44e3c8d](https://github.com/auth0/node-jsonwebtoken/commit/44e3c8d757e6b4e2a57a69a035f26b4abec3e327) - Fix wrong `iat` and `exp` values when signing with `noTimestamp`. [331b7bc](https://github.com/auth0/node-jsonwebtoken/commit/331b7bc9cc335561f8806f2c4558e105cb53e0a6) ================================================ FILE: CODEOWNERS ================================================ * @auth0/project-iam-protocols-engineer-codeowner ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Auth0, Inc. (http://auth0.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # jsonwebtoken | **Build** | **Dependency** | |-----------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------| | [![Build Status](https://secure.travis-ci.org/auth0/node-jsonwebtoken.svg?branch=master)](http://travis-ci.org/auth0/node-jsonwebtoken) | [![Dependency Status](https://david-dm.org/auth0/node-jsonwebtoken.svg)](https://david-dm.org/auth0/node-jsonwebtoken) | An implementation of [JSON Web Tokens](https://tools.ietf.org/html/rfc7519). This was developed against `draft-ietf-oauth-json-web-token-08`. It makes use of [node-jws](https://github.com/brianloveswords/node-jws) # Install ```bash $ npm install jsonwebtoken ``` # Migration notes * [From v8 to v9](https://github.com/auth0/node-jsonwebtoken/wiki/Migration-Notes:-v8-to-v9) * [From v7 to v8](https://github.com/auth0/node-jsonwebtoken/wiki/Migration-Notes:-v7-to-v8) # Usage ### jwt.sign(payload, secretOrPrivateKey, [options, callback]) (Asynchronous) If a callback is supplied, the callback is called with the `err` or the JWT. (Synchronous) Returns the JsonWebToken as string `payload` could be an object literal, buffer or string representing valid JSON. > **Please _note_ that** `exp` or any other claim is only set if the payload is an object literal. Buffer or string payloads are not checked for JSON validity. > If `payload` is not a buffer or a string, it will be coerced into a string using `JSON.stringify`. `secretOrPrivateKey` is a string (utf-8 encoded), buffer, object, or KeyObject containing either the secret for HMAC algorithms or the PEM encoded private key for RSA and ECDSA. In case of a private key with passphrase an object `{ key, passphrase }` can be used (based on [crypto documentation](https://nodejs.org/api/crypto.html#crypto_sign_sign_private_key_output_format)), in this case be sure you pass the `algorithm` option. When signing with RSA algorithms the minimum modulus length is 2048 except when the allowInsecureKeySizes option is set to true. Private keys below this size will be rejected with an error. `options`: * `algorithm` (default: `HS256`) * `expiresIn`: expressed in seconds or a string describing a time span [vercel/ms](https://github.com/vercel/ms). > Eg: `60`, `"2 days"`, `"10h"`, `"7d"`. A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), otherwise milliseconds unit is used by default (`"120"` is equal to `"120ms"`). * `notBefore`: expressed in seconds or a string describing a time span [vercel/ms](https://github.com/vercel/ms). > Eg: `60`, `"2 days"`, `"10h"`, `"7d"`. A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), otherwise milliseconds unit is used by default (`"120"` is equal to `"120ms"`). * `audience` * `issuer` * `jwtid` * `subject` * `noTimestamp` * `header` * `keyid` * `mutatePayload`: if true, the sign function will modify the payload object directly. This is useful if you need a raw reference to the payload after claims have been applied to it but before it has been encoded into a token. * `allowInsecureKeySizes`: if true allows private keys with a modulus below 2048 to be used for RSA * `allowInvalidAsymmetricKeyTypes`: if true, allows asymmetric keys which do not match the specified algorithm. This option is intended only for backwards compatability and should be avoided. > There are no default values for `expiresIn`, `notBefore`, `audience`, `subject`, `issuer`. These claims can also be provided in the payload directly with `exp`, `nbf`, `aud`, `sub` and `iss` respectively, but you **_can't_** include in both places. Remember that `exp`, `nbf` and `iat` are **NumericDate**, see related [Token Expiration (exp claim)](#token-expiration-exp-claim) The header can be customized via the `options.header` object. Generated jwts will include an `iat` (issued at) claim by default unless `noTimestamp` is specified. If `iat` is inserted in the payload, it will be used instead of the real timestamp for calculating other things like `exp` given a timespan in `options.expiresIn`. Synchronous Sign with default (HMAC SHA256) ```js var jwt = require('jsonwebtoken'); var token = jwt.sign({ foo: 'bar' }, 'shhhhh'); ``` Synchronous Sign with RSA SHA256 ```js // sign with RSA SHA256 var privateKey = fs.readFileSync('private.key'); var token = jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256' }); ``` Sign asynchronously ```js jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256' }, function(err, token) { console.log(token); }); ``` Backdate a jwt 30 seconds ```js var older_token = jwt.sign({ foo: 'bar', iat: Math.floor(Date.now() / 1000) - 30 }, 'shhhhh'); ``` #### Token Expiration (exp claim) The standard for JWT defines an `exp` claim for expiration. The expiration is represented as a **NumericDate**: > A JSON numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap seconds. This is equivalent to the IEEE Std 1003.1, 2013 Edition [POSIX.1] definition "Seconds Since the Epoch", in which each day is accounted for by exactly 86400 seconds, other than that non-integer values can be represented. See RFC 3339 [RFC3339] for details regarding date/times in general and UTC in particular. This means that the `exp` field should contain the number of seconds since the epoch. Signing a token with 1 hour of expiration: ```javascript jwt.sign({ exp: Math.floor(Date.now() / 1000) + (60 * 60), data: 'foobar' }, 'secret'); ``` Another way to generate a token like this with this library is: ```javascript jwt.sign({ data: 'foobar' }, 'secret', { expiresIn: 60 * 60 }); //or even better: jwt.sign({ data: 'foobar' }, 'secret', { expiresIn: '1h' }); ``` ### jwt.verify(token, secretOrPublicKey, [options, callback]) (Asynchronous) If a callback is supplied, function acts asynchronously. The callback is called with the decoded payload if the signature is valid and optional expiration, audience, or issuer are valid. If not, it will be called with the error. (Synchronous) If a callback is not supplied, function acts synchronously. Returns the payload decoded if the signature is valid and optional expiration, audience, or issuer are valid. If not, it will throw the error. > __Warning:__ When the token comes from an untrusted source (e.g. user input or external requests), the returned decoded payload should be treated like any other user input; please make sure to sanitize and only work with properties that are expected `token` is the JsonWebToken string `secretOrPublicKey` is a string (utf-8 encoded), buffer, or KeyObject containing either the secret for HMAC algorithms, or the PEM encoded public key for RSA and ECDSA. If `jwt.verify` is called asynchronous, `secretOrPublicKey` can be a function that should fetch the secret or public key. See below for a detailed example As mentioned in [this comment](https://github.com/auth0/node-jsonwebtoken/issues/208#issuecomment-231861138), there are other libraries that expect base64 encoded secrets (random bytes encoded using base64), if that is your case you can pass `Buffer.from(secret, 'base64')`, by doing this the secret will be decoded using base64 and the token verification will use the original random bytes. `options` * `algorithms`: List of strings with the names of the allowed algorithms. For instance, `["HS256", "HS384"]`. > If not specified a defaults will be used based on the type of key provided > * secret - ['HS256', 'HS384', 'HS512'] > * rsa - ['RS256', 'RS384', 'RS512'] > * ec - ['ES256', 'ES384', 'ES512'] > * default - ['RS256', 'RS384', 'RS512'] * `audience`: if you want to check audience (`aud`), provide a value here. The audience can be checked against a string, a regular expression or a list of strings and/or regular expressions. > Eg: `"urn:foo"`, `/urn:f[o]{2}/`, `[/urn:f[o]{2}/, "urn:bar"]` * `complete`: return an object with the decoded `{ payload, header, signature }` instead of only the usual content of the payload. * `issuer` (optional): string or array of strings of valid values for the `iss` field. * `jwtid` (optional): if you want to check JWT ID (`jti`), provide a string value here. * `ignoreExpiration`: if `true` do not validate the expiration of the token. * `ignoreNotBefore`... * `subject`: if you want to check subject (`sub`), provide a value here * `clockTolerance`: number of seconds to tolerate when checking the `nbf` and `exp` claims, to deal with small clock differences among different servers * `maxAge`: the maximum allowed age for tokens to still be valid. It is expressed in seconds or a string describing a time span [vercel/ms](https://github.com/vercel/ms). > Eg: `1000`, `"2 days"`, `"10h"`, `"7d"`. A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), otherwise milliseconds unit is used by default (`"120"` is equal to `"120ms"`). * `clockTimestamp`: the time in seconds that should be used as the current time for all necessary comparisons. * `nonce`: if you want to check `nonce` claim, provide a string value here. It is used on Open ID for the ID Tokens. ([Open ID implementation notes](https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes)) * `allowInvalidAsymmetricKeyTypes`: if true, allows asymmetric keys which do not match the specified algorithm. This option is intended only for backwards compatability and should be avoided. ```js // verify a token symmetric - synchronous var decoded = jwt.verify(token, 'shhhhh'); console.log(decoded.foo) // bar // verify a token symmetric jwt.verify(token, 'shhhhh', function(err, decoded) { console.log(decoded.foo) // bar }); // invalid token - synchronous try { var decoded = jwt.verify(token, 'wrong-secret'); } catch(err) { // err } // invalid token jwt.verify(token, 'wrong-secret', function(err, decoded) { // err // decoded undefined }); // verify a token asymmetric var cert = fs.readFileSync('public.pem'); // get public key jwt.verify(token, cert, function(err, decoded) { console.log(decoded.foo) // bar }); // verify audience var cert = fs.readFileSync('public.pem'); // get public key jwt.verify(token, cert, { audience: 'urn:foo' }, function(err, decoded) { // if audience mismatch, err == invalid audience }); // verify issuer var cert = fs.readFileSync('public.pem'); // get public key jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer' }, function(err, decoded) { // if issuer mismatch, err == invalid issuer }); // verify jwt id var cert = fs.readFileSync('public.pem'); // get public key jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer', jwtid: 'jwtid' }, function(err, decoded) { // if jwt id mismatch, err == invalid jwt id }); // verify subject var cert = fs.readFileSync('public.pem'); // get public key jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer', jwtid: 'jwtid', subject: 'subject' }, function(err, decoded) { // if subject mismatch, err == invalid subject }); // alg mismatch var cert = fs.readFileSync('public.pem'); // get public key jwt.verify(token, cert, { algorithms: ['RS256'] }, function (err, payload) { // if token alg != RS256, err == invalid signature }); // Verify using getKey callback // Example uses https://github.com/auth0/node-jwks-rsa as a way to fetch the keys. var jwksClient = require('jwks-rsa'); var client = jwksClient({ jwksUri: 'https://sandrino.auth0.com/.well-known/jwks.json' }); function getKey(header, callback){ client.getSigningKey(header.kid, function(err, key) { var signingKey = key.publicKey || key.rsaPublicKey; callback(null, signingKey); }); } jwt.verify(token, getKey, options, function(err, decoded) { console.log(decoded.foo) // bar }); ```
Need to peek into a JWT without verifying it? (Click to expand) ### jwt.decode(token [, options]) (Synchronous) Returns the decoded payload without verifying if the signature is valid. > __Warning:__ This will __not__ verify whether the signature is valid. You should __not__ use this for untrusted messages. You most likely want to use `jwt.verify` instead. > __Warning:__ When the token comes from an untrusted source (e.g. user input or external request), the returned decoded payload should be treated like any other user input; please make sure to sanitize and only work with properties that are expected `token` is the JsonWebToken string `options`: * `json`: force JSON.parse on the payload even if the header doesn't contain `"typ":"JWT"`. * `complete`: return an object with the decoded payload and header. Example ```js // get the decoded payload ignoring signature, no secretOrPrivateKey needed var decoded = jwt.decode(token); // get the decoded payload and header var decoded = jwt.decode(token, {complete: true}); console.log(decoded.header); console.log(decoded.payload) ```
## Errors & Codes Possible thrown errors during verification. Error is the first argument of the verification callback. ### TokenExpiredError Thrown error if the token is expired. Error object: * name: 'TokenExpiredError' * message: 'jwt expired' * expiredAt: [ExpDate] ```js jwt.verify(token, 'shhhhh', function(err, decoded) { if (err) { /* err = { name: 'TokenExpiredError', message: 'jwt expired', expiredAt: 1408621000 } */ } }); ``` ### JsonWebTokenError Error object: * name: 'JsonWebTokenError' * message: * 'invalid token' - the header or payload could not be parsed * 'jwt malformed' - the token does not have three components (delimited by a `.`) * 'jwt signature is required' * 'invalid signature' * 'jwt audience invalid. expected: [OPTIONS AUDIENCE]' * 'jwt issuer invalid. expected: [OPTIONS ISSUER]' * 'jwt id invalid. expected: [OPTIONS JWT ID]' * 'jwt subject invalid. expected: [OPTIONS SUBJECT]' ```js jwt.verify(token, 'shhhhh', function(err, decoded) { if (err) { /* err = { name: 'JsonWebTokenError', message: 'jwt malformed' } */ } }); ``` ### NotBeforeError Thrown if current time is before the nbf claim. Error object: * name: 'NotBeforeError' * message: 'jwt not active' * date: 2018-10-04T16:10:44.000Z ```js jwt.verify(token, 'shhhhh', function(err, decoded) { if (err) { /* err = { name: 'NotBeforeError', message: 'jwt not active', date: 2018-10-04T16:10:44.000Z } */ } }); ``` ## Algorithms supported Array of supported algorithms. The following algorithms are currently supported. | alg Parameter Value | Digital Signature or MAC Algorithm | |---------------------|------------------------------------------------------------------------| | HS256 | HMAC using SHA-256 hash algorithm | | HS384 | HMAC using SHA-384 hash algorithm | | HS512 | HMAC using SHA-512 hash algorithm | | RS256 | RSASSA-PKCS1-v1_5 using SHA-256 hash algorithm | | RS384 | RSASSA-PKCS1-v1_5 using SHA-384 hash algorithm | | RS512 | RSASSA-PKCS1-v1_5 using SHA-512 hash algorithm | | PS256 | RSASSA-PSS using SHA-256 hash algorithm (only node ^6.12.0 OR >=8.0.0) | | PS384 | RSASSA-PSS using SHA-384 hash algorithm (only node ^6.12.0 OR >=8.0.0) | | PS512 | RSASSA-PSS using SHA-512 hash algorithm (only node ^6.12.0 OR >=8.0.0) | | ES256 | ECDSA using P-256 curve and SHA-256 hash algorithm | | ES384 | ECDSA using P-384 curve and SHA-384 hash algorithm | | ES512 | ECDSA using P-521 curve and SHA-512 hash algorithm | | none | No digital signature or MAC value included | ## Refreshing JWTs First of all, we recommend you to think carefully if auto-refreshing a JWT will not introduce any vulnerability in your system. We are not comfortable including this as part of the library, however, you can take a look at [this example](https://gist.github.com/ziluvatar/a3feb505c4c0ec37059054537b38fc48) to show how this could be accomplished. Apart from that example there are [an issue](https://github.com/auth0/node-jsonwebtoken/issues/122) and [a pull request](https://github.com/auth0/node-jsonwebtoken/pull/172) to get more knowledge about this topic. # TODO * X.509 certificate chain is not checked ## Issue Reporting If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. ## Author [Auth0](https://auth0.com) ## License This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info. ================================================ FILE: bin/changelog ================================================ #!/usr/bin/env node var changelog = require('conventional-changelog'); var semver_regex = /\bv?(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)(?:-[\da-z\-]+(?:\.[\da-z\-]+)*)?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?\b/ig; const commitPartial = ` - {{header}} {{~!-- commit hash --}} {{#if @root.linkReferences}}([{{hash}}]({{#if @root.host}}{{@root.host}}/{{/if}}{{#if @root.owner}}{{@root.owner}}/{{/if}}{{@root.repository}}/{{@root.commit}}/{{hash}})){{else}}{{hash~}}{{/if}} {{~!-- commit references --}}{{#if references}}, closes{{~#each references}} {{#if @root.linkReferences}}[{{#if this.owner}}{{this.owner}}/{{/if}}{{this.repository}}#{{this.issue}}]({{#if @root.host}}{{@root.host}}/{{/if}}{{#if this.repository}}{{#if this.owner}}{{this.owner}}/{{/if}}{{this.repository}}{{else}}{{#if @root.owner}}{{@root.owner}}/{{/if}}{{@root.repository}}{{/if}}/{{@root.issue}}/{{this.issue}}){{else}}{{#if this.owner}}{{this.owner}}/{{/if}}{{this.repository}}#{{this.issue}}{{/if}}{{/each}}{{/if}} `; const headerPartial = `## {{version}}{{#if title}} "{{title}}"{{/if}}{{#if date}} - {{date}}{{/if}} `; changelog({ releaseCount: 19, // preset: 'jshint' }, null, null, null, { transform: function (commit) { if (commit.header && semver_regex.exec(commit.header)) { return null; } return commit; }, commitPartial: commitPartial, headerPartial: headerPartial }).pipe(process.stdout); ================================================ FILE: decode.js ================================================ var jws = require('jws'); module.exports = function (jwt, options) { options = options || {}; var decoded = jws.decode(jwt, options); if (!decoded) { return null; } var payload = decoded.payload; //try parse the payload if(typeof payload === 'string') { try { var obj = JSON.parse(payload); if(obj !== null && typeof obj === 'object') { payload = obj; } } catch (e) { } } //return header if `complete` option is enabled. header includes claims //such as `kid` and `alg` used to select the key within a JWKS needed to //verify the signature if (options.complete === true) { return { header: decoded.header, payload: payload, signature: decoded.signature }; } return payload; }; ================================================ FILE: index.js ================================================ module.exports = { decode: require('./decode'), verify: require('./verify'), sign: require('./sign'), JsonWebTokenError: require('./lib/JsonWebTokenError'), NotBeforeError: require('./lib/NotBeforeError'), TokenExpiredError: require('./lib/TokenExpiredError'), }; ================================================ FILE: lib/JsonWebTokenError.js ================================================ var JsonWebTokenError = function (message, error) { Error.call(this, message); if(Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } this.name = 'JsonWebTokenError'; this.message = message; if (error) this.inner = error; }; JsonWebTokenError.prototype = Object.create(Error.prototype); JsonWebTokenError.prototype.constructor = JsonWebTokenError; module.exports = JsonWebTokenError; ================================================ FILE: lib/NotBeforeError.js ================================================ var JsonWebTokenError = require('./JsonWebTokenError'); var NotBeforeError = function (message, date) { JsonWebTokenError.call(this, message); this.name = 'NotBeforeError'; this.date = date; }; NotBeforeError.prototype = Object.create(JsonWebTokenError.prototype); NotBeforeError.prototype.constructor = NotBeforeError; module.exports = NotBeforeError; ================================================ FILE: lib/TokenExpiredError.js ================================================ var JsonWebTokenError = require('./JsonWebTokenError'); var TokenExpiredError = function (message, expiredAt) { JsonWebTokenError.call(this, message); this.name = 'TokenExpiredError'; this.expiredAt = expiredAt; }; TokenExpiredError.prototype = Object.create(JsonWebTokenError.prototype); TokenExpiredError.prototype.constructor = TokenExpiredError; module.exports = TokenExpiredError; ================================================ FILE: lib/asymmetricKeyDetailsSupported.js ================================================ const semver = require('semver'); module.exports = semver.satisfies(process.version, '>=15.7.0'); ================================================ FILE: lib/psSupported.js ================================================ var semver = require('semver'); module.exports = semver.satisfies(process.version, '^6.12.0 || >=8.0.0'); ================================================ FILE: lib/rsaPssKeyDetailsSupported.js ================================================ const semver = require('semver'); module.exports = semver.satisfies(process.version, '>=16.9.0'); ================================================ FILE: lib/timespan.js ================================================ var ms = require('ms'); module.exports = function (time, iat) { var timestamp = iat || Math.floor(Date.now() / 1000); if (typeof time === 'string') { var milliseconds = ms(time); if (typeof milliseconds === 'undefined') { return; } return Math.floor(timestamp + milliseconds / 1000); } else if (typeof time === 'number') { return timestamp + time; } else { return; } }; ================================================ FILE: lib/validateAsymmetricKey.js ================================================ const ASYMMETRIC_KEY_DETAILS_SUPPORTED = require('./asymmetricKeyDetailsSupported'); const RSA_PSS_KEY_DETAILS_SUPPORTED = require('./rsaPssKeyDetailsSupported'); const allowedAlgorithmsForKeys = { 'ec': ['ES256', 'ES384', 'ES512'], 'rsa': ['RS256', 'PS256', 'RS384', 'PS384', 'RS512', 'PS512'], 'rsa-pss': ['PS256', 'PS384', 'PS512'] }; const allowedCurves = { ES256: 'prime256v1', ES384: 'secp384r1', ES512: 'secp521r1', }; module.exports = function(algorithm, key) { if (!algorithm || !key) return; const keyType = key.asymmetricKeyType; if (!keyType) return; const allowedAlgorithms = allowedAlgorithmsForKeys[keyType]; if (!allowedAlgorithms) { throw new Error(`Unknown key type "${keyType}".`); } if (!allowedAlgorithms.includes(algorithm)) { throw new Error(`"alg" parameter for "${keyType}" key type must be one of: ${allowedAlgorithms.join(', ')}.`) } /* * Ignore the next block from test coverage because it gets executed * conditionally depending on the Node version. Not ignoring it would * prevent us from reaching the target % of coverage for versions of * Node under 15.7.0. */ /* istanbul ignore next */ if (ASYMMETRIC_KEY_DETAILS_SUPPORTED) { switch (keyType) { case 'ec': const keyCurve = key.asymmetricKeyDetails.namedCurve; const allowedCurve = allowedCurves[algorithm]; if (keyCurve !== allowedCurve) { throw new Error(`"alg" parameter "${algorithm}" requires curve "${allowedCurve}".`); } break; case 'rsa-pss': if (RSA_PSS_KEY_DETAILS_SUPPORTED) { const length = parseInt(algorithm.slice(-3), 10); const { hashAlgorithm, mgf1HashAlgorithm, saltLength } = key.asymmetricKeyDetails; if (hashAlgorithm !== `sha${length}` || mgf1HashAlgorithm !== hashAlgorithm) { throw new Error(`Invalid key for this operation, its RSA-PSS parameters do not meet the requirements of "alg" ${algorithm}.`); } if (saltLength !== undefined && saltLength > length >> 3) { throw new Error(`Invalid key for this operation, its RSA-PSS parameter saltLength does not meet the requirements of "alg" ${algorithm}.`) } } break; } } } ================================================ FILE: opslevel.yml ================================================ --- version: 1 repository: owner: iam_protocols tier: tags: ================================================ FILE: package.json ================================================ { "name": "jsonwebtoken", "version": "9.0.3", "description": "JSON Web Token implementation (symmetric and asymmetric)", "main": "index.js", "nyc": { "check-coverage": true, "lines": 95, "statements": 95, "functions": 100, "branches": 95, "exclude": [ "./test/**" ], "reporter": [ "json", "lcov", "text-summary" ] }, "scripts": { "lint": "eslint .", "coverage": "nyc mocha --use_strict", "test": "mocha", "prepare": "husky" }, "repository": { "type": "git", "url": "https://github.com/auth0/node-jsonwebtoken" }, "keywords": [ "jwt" ], "author": "auth0", "license": "MIT", "bugs": { "url": "https://github.com/auth0/node-jsonwebtoken/issues" }, "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" }, "devDependencies": { "@commitlint/cli": "^20.3.1", "@commitlint/config-conventional": "^20.3.1", "@semantic-release/exec": "^7.0.3", "atob": "^2.1.2", "chai": "^4.1.2", "conventional-changelog": "~1.1.0", "eslint": "^4.19.1", "husky": "^9.1.7", "mocha": "^5.2.0", "nsp": "^2.6.2", "nyc": "^11.9.0", "semantic-release": "^25.0.2", "sinon": "^6.0.0" }, "engines": { "npm": ">=6", "node": ">=12" }, "files": [ "lib", "decode.js", "sign.js", "verify.js" ] } ================================================ FILE: sign.js ================================================ const timespan = require('./lib/timespan'); const PS_SUPPORTED = require('./lib/psSupported'); const validateAsymmetricKey = require('./lib/validateAsymmetricKey'); const jws = require('jws'); const includes = require('lodash.includes'); const isBoolean = require('lodash.isboolean'); const isInteger = require('lodash.isinteger'); const isNumber = require('lodash.isnumber'); const isPlainObject = require('lodash.isplainobject'); const isString = require('lodash.isstring'); const once = require('lodash.once'); const { KeyObject, createSecretKey, createPrivateKey } = require('crypto') const SUPPORTED_ALGS = ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'none']; if (PS_SUPPORTED) { SUPPORTED_ALGS.splice(3, 0, 'PS256', 'PS384', 'PS512'); } const sign_options_schema = { expiresIn: { isValid: function(value) { return isInteger(value) || (isString(value) && value); }, message: '"expiresIn" should be a number of seconds or string representing a timespan' }, notBefore: { isValid: function(value) { return isInteger(value) || (isString(value) && value); }, message: '"notBefore" should be a number of seconds or string representing a timespan' }, audience: { isValid: function(value) { return isString(value) || Array.isArray(value); }, message: '"audience" must be a string or array' }, algorithm: { isValid: includes.bind(null, SUPPORTED_ALGS), message: '"algorithm" must be a valid string enum value' }, header: { isValid: isPlainObject, message: '"header" must be an object' }, encoding: { isValid: isString, message: '"encoding" must be a string' }, issuer: { isValid: isString, message: '"issuer" must be a string' }, subject: { isValid: isString, message: '"subject" must be a string' }, jwtid: { isValid: isString, message: '"jwtid" must be a string' }, noTimestamp: { isValid: isBoolean, message: '"noTimestamp" must be a boolean' }, keyid: { isValid: isString, message: '"keyid" must be a string' }, mutatePayload: { isValid: isBoolean, message: '"mutatePayload" must be a boolean' }, allowInsecureKeySizes: { isValid: isBoolean, message: '"allowInsecureKeySizes" must be a boolean'}, allowInvalidAsymmetricKeyTypes: { isValid: isBoolean, message: '"allowInvalidAsymmetricKeyTypes" must be a boolean'} }; const registered_claims_schema = { iat: { isValid: isNumber, message: '"iat" should be a number of seconds' }, exp: { isValid: isNumber, message: '"exp" should be a number of seconds' }, nbf: { isValid: isNumber, message: '"nbf" should be a number of seconds' } }; function validate(schema, allowUnknown, object, parameterName) { if (!isPlainObject(object)) { throw new Error('Expected "' + parameterName + '" to be a plain object.'); } Object.keys(object) .forEach(function(key) { const validator = schema[key]; if (!validator) { if (!allowUnknown) { throw new Error('"' + key + '" is not allowed in "' + parameterName + '"'); } return; } if (!validator.isValid(object[key])) { throw new Error(validator.message); } }); } function validateOptions(options) { return validate(sign_options_schema, false, options, 'options'); } function validatePayload(payload) { return validate(registered_claims_schema, true, payload, 'payload'); } const options_to_payload = { 'audience': 'aud', 'issuer': 'iss', 'subject': 'sub', 'jwtid': 'jti' }; const options_for_objects = [ 'expiresIn', 'notBefore', 'noTimestamp', 'audience', 'issuer', 'subject', 'jwtid', ]; module.exports = function (payload, secretOrPrivateKey, options, callback) { if (typeof options === 'function') { callback = options; options = {}; } else { options = options || {}; } const isObjectPayload = typeof payload === 'object' && !Buffer.isBuffer(payload); const header = Object.assign({ alg: options.algorithm || 'HS256', typ: isObjectPayload ? 'JWT' : undefined, kid: options.keyid }, options.header); function failure(err) { if (callback) { return callback(err); } throw err; } if (!secretOrPrivateKey && options.algorithm !== 'none') { return failure(new Error('secretOrPrivateKey must have a value')); } if (secretOrPrivateKey != null && !(secretOrPrivateKey instanceof KeyObject)) { try { secretOrPrivateKey = createPrivateKey(secretOrPrivateKey) } catch (_) { try { secretOrPrivateKey = createSecretKey(typeof secretOrPrivateKey === 'string' ? Buffer.from(secretOrPrivateKey) : secretOrPrivateKey) } catch (_) { return failure(new Error('secretOrPrivateKey is not valid key material')); } } } if (header.alg.startsWith('HS') && secretOrPrivateKey.type !== 'secret') { return failure(new Error((`secretOrPrivateKey must be a symmetric key when using ${header.alg}`))) } else if (/^(?:RS|PS|ES)/.test(header.alg)) { if (secretOrPrivateKey.type !== 'private') { return failure(new Error((`secretOrPrivateKey must be an asymmetric key when using ${header.alg}`))) } if (!options.allowInsecureKeySizes && !header.alg.startsWith('ES') && secretOrPrivateKey.asymmetricKeyDetails !== undefined && //KeyObject.asymmetricKeyDetails is supported in Node 15+ secretOrPrivateKey.asymmetricKeyDetails.modulusLength < 2048) { return failure(new Error(`secretOrPrivateKey has a minimum key size of 2048 bits for ${header.alg}`)); } } if (typeof payload === 'undefined') { return failure(new Error('payload is required')); } else if (isObjectPayload) { try { validatePayload(payload); } catch (error) { return failure(error); } if (!options.mutatePayload) { payload = Object.assign({},payload); } } else { const invalid_options = options_for_objects.filter(function (opt) { return typeof options[opt] !== 'undefined'; }); if (invalid_options.length > 0) { return failure(new Error('invalid ' + invalid_options.join(',') + ' option for ' + (typeof payload ) + ' payload')); } } if (typeof payload.exp !== 'undefined' && typeof options.expiresIn !== 'undefined') { return failure(new Error('Bad "options.expiresIn" option the payload already has an "exp" property.')); } if (typeof payload.nbf !== 'undefined' && typeof options.notBefore !== 'undefined') { return failure(new Error('Bad "options.notBefore" option the payload already has an "nbf" property.')); } try { validateOptions(options); } catch (error) { return failure(error); } if (!options.allowInvalidAsymmetricKeyTypes) { try { validateAsymmetricKey(header.alg, secretOrPrivateKey); } catch (error) { return failure(error); } } const timestamp = payload.iat || Math.floor(Date.now() / 1000); if (options.noTimestamp) { delete payload.iat; } else if (isObjectPayload) { payload.iat = timestamp; } if (typeof options.notBefore !== 'undefined') { try { payload.nbf = timespan(options.notBefore, timestamp); } catch (err) { return failure(err); } if (typeof payload.nbf === 'undefined') { return failure(new Error('"notBefore" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60')); } } if (typeof options.expiresIn !== 'undefined' && typeof payload === 'object') { try { payload.exp = timespan(options.expiresIn, timestamp); } catch (err) { return failure(err); } if (typeof payload.exp === 'undefined') { return failure(new Error('"expiresIn" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60')); } } Object.keys(options_to_payload).forEach(function (key) { const claim = options_to_payload[key]; if (typeof options[key] !== 'undefined') { if (typeof payload[claim] !== 'undefined') { return failure(new Error('Bad "options.' + key + '" option. The payload already has an "' + claim + '" property.')); } payload[claim] = options[key]; } }); const encoding = options.encoding || 'utf8'; if (typeof callback === 'function') { callback = callback && once(callback); jws.createSign({ header: header, privateKey: secretOrPrivateKey, payload: payload, encoding: encoding }).once('error', callback) .once('done', function (signature) { // TODO: Remove in favor of the modulus length check before signing once node 15+ is the minimum supported version if(!options.allowInsecureKeySizes && /^(?:RS|PS)/.test(header.alg) && signature.length < 256) { return callback(new Error(`secretOrPrivateKey has a minimum key size of 2048 bits for ${header.alg}`)) } callback(null, signature); }); } else { let signature = jws.sign({header: header, payload: payload, secret: secretOrPrivateKey, encoding: encoding}); // TODO: Remove in favor of the modulus length check before signing once node 15+ is the minimum supported version if(!options.allowInsecureKeySizes && /^(?:RS|PS)/.test(header.alg) && signature.length < 256) { throw new Error(`secretOrPrivateKey has a minimum key size of 2048 bits for ${header.alg}`) } return signature } }; ================================================ FILE: test/.eslintrc.json ================================================ { "env": { "mocha": true } } ================================================ FILE: test/async_sign.tests.js ================================================ var jwt = require('../index'); var expect = require('chai').expect; var jws = require('jws'); var PS_SUPPORTED = require('../lib/psSupported'); const {generateKeyPairSync} = require("crypto"); describe('signing a token asynchronously', function() { describe('when signing a token', function() { var secret = 'shhhhhh'; it('should return the same result as singing synchronously', function(done) { jwt.sign({ foo: 'bar' }, secret, { algorithm: 'HS256' }, function (err, asyncToken) { if (err) return done(err); var syncToken = jwt.sign({ foo: 'bar' }, secret, { algorithm: 'HS256' }); expect(asyncToken).to.be.a('string'); expect(asyncToken.split('.')).to.have.length(3); expect(asyncToken).to.equal(syncToken); done(); }); }); it('should work with empty options', function (done) { jwt.sign({abc: 1}, "secret", {}, function (err) { expect(err).to.be.null; done(); }); }); it('should work without options object at all', function (done) { jwt.sign({abc: 1}, "secret", function (err) { expect(err).to.be.null; done(); }); }); it('should work with none algorithm where secret is set', function(done) { jwt.sign({ foo: 'bar' }, 'secret', { algorithm: 'none' }, function(err, token) { expect(token).to.be.a('string'); expect(token.split('.')).to.have.length(3); done(); }); }); //Known bug: https://github.com/brianloveswords/node-jws/issues/62 //If you need this use case, you need to go for the non-callback-ish code style. it.skip('should work with none algorithm where secret is falsy', function(done) { jwt.sign({ foo: 'bar' }, undefined, { algorithm: 'none' }, function(err, token) { expect(token).to.be.a('string'); expect(token.split('.')).to.have.length(3); done(); }); }); it('should return error when secret is not a cert for RS256', function(done) { //this throw an error because the secret is not a cert and RS256 requires a cert. jwt.sign({ foo: 'bar' }, secret, { algorithm: 'RS256' }, function (err) { expect(err).to.be.ok; done(); }); }); it('should not work for RS algorithms when modulus length is less than 2048 when allowInsecureKeySizes is false or not set', function(done) { const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 1024 }); jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256' }, function (err) { expect(err).to.be.ok; done(); }); }); it('should work for RS algorithms when modulus length is less than 2048 when allowInsecureKeySizes is true', function(done) { const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 1024 }); jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256', allowInsecureKeySizes: true }, done); }); if (PS_SUPPORTED) { it('should return error when secret is not a cert for PS256', function(done) { //this throw an error because the secret is not a cert and PS256 requires a cert. jwt.sign({ foo: 'bar' }, secret, { algorithm: 'PS256' }, function (err) { expect(err).to.be.ok; done(); }); }); } it('should return error on wrong arguments', function(done) { //this throw an error because the secret is not a cert and RS256 requires a cert. jwt.sign({ foo: 'bar' }, secret, { notBefore: {} }, function (err) { expect(err).to.be.ok; done(); }); }); it('should return error on wrong arguments (2)', function(done) { jwt.sign('string', 'secret', {noTimestamp: true}, function (err) { expect(err).to.be.ok; expect(err).to.be.instanceof(Error); done(); }); }); it('should not stringify the payload', function (done) { jwt.sign('string', 'secret', {}, function (err, token) { if (err) { return done(err); } expect(jws.decode(token).payload).to.equal('string'); done(); }); }); describe('when mutatePayload is not set', function() { it('should not apply claims to the original payload object (mutatePayload defaults to false)', function(done) { var originalPayload = { foo: 'bar' }; jwt.sign(originalPayload, 'secret', { notBefore: 60, expiresIn: 600 }, function (err) { if (err) { return done(err); } expect(originalPayload).to.not.have.property('nbf'); expect(originalPayload).to.not.have.property('exp'); done(); }); }); }); describe('when mutatePayload is set to true', function() { it('should apply claims directly to the original payload object', function(done) { var originalPayload = { foo: 'bar' }; jwt.sign(originalPayload, 'secret', { notBefore: 60, expiresIn: 600, mutatePayload: true }, function (err) { if (err) { return done(err); } expect(originalPayload).to.have.property('nbf').that.is.a('number'); expect(originalPayload).to.have.property('exp').that.is.a('number'); done(); }); }); }); describe('secret must have a value', function(){ [undefined, '', 0].forEach(function(secret){ it('should return an error if the secret is falsy and algorithm is not set to none: ' + (typeof secret === 'string' ? '(empty string)' : secret), function(done) { // This is needed since jws will not answer for falsy secrets jwt.sign('string', secret, {}, function(err, token) { expect(err).to.exist; expect(err.message).to.equal('secretOrPrivateKey must have a value'); expect(token).to.not.exist; done(); }); }); }); }); }); }); ================================================ FILE: test/buffer.tests.js ================================================ var jwt = require("../."); var assert = require('chai').assert; describe('buffer payload', function () { it('should work', function () { var payload = new Buffer('TkJyotZe8NFpgdfnmgINqg==', 'base64'); var token = jwt.sign(payload, "signing key"); assert.equal(jwt.decode(token), payload.toString()); }); }); ================================================ FILE: test/claim-aud.test.js ================================================ 'use strict'; const jwt = require('../'); const expect = require('chai').expect; const util = require('util'); const testUtils = require('./test-utils'); function signWithAudience(audience, payload, callback) { const options = {algorithm: 'HS256'}; if (audience !== undefined) { options.audience = audience; } testUtils.signJWTHelper(payload, 'secret', options, callback); } function verifyWithAudience(token, audience, callback) { testUtils.verifyJWTHelper(token, 'secret', {audience}, callback); } describe('audience', function() { describe('`jwt.sign` "audience" option validation', function () { [ true, false, null, -1, 1, 0, -1.1, 1.1, -Infinity, Infinity, NaN, {}, {foo: 'bar'}, ].forEach((audience) => { it(`should error with with value ${util.inspect(audience)}`, function (done) { signWithAudience(audience, {}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', '"audience" must be a string or array'); }); }); }); }); // undefined needs special treatment because {} is not the same as {aud: undefined} it('should error with with value undefined', function (done) { testUtils.signJWTHelper({}, 'secret', {audience: undefined, algorithm: 'HS256'}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', '"audience" must be a string or array'); }); }); }); it('should error when "aud" is in payload', function (done) { signWithAudience('my_aud', {aud: ''}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property( 'message', 'Bad "options.audience" option. The payload already has an "aud" property.' ); }); }); }); it('should error with a string payload', function (done) { signWithAudience('my_aud', 'a string payload', (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', 'invalid audience option for string payload'); }); }); }); it('should error with a Buffer payload', function (done) { signWithAudience('my_aud', new Buffer('a Buffer payload'), (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', 'invalid audience option for object payload'); }); }); }); }); describe('when signing and verifying a token with "audience" option', function () { describe('with a "aud" of "urn:foo" in payload', function () { let token; beforeEach(function (done) { signWithAudience('urn:foo', {}, (err, t) => { token = t; done(err); }); }); [ undefined, 'urn:foo', /^urn:f[o]{2}$/, ['urn:no_match', 'urn:foo'], ['urn:no_match', /^urn:f[o]{2}$/], [/^urn:no_match$/, /^urn:f[o]{2}$/], [/^urn:no_match$/, 'urn:foo'] ].forEach((audience) =>{ it(`should verify and decode with verify "audience" option of ${util.inspect(audience)}`, function (done) { verifyWithAudience(token, audience, (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud', 'urn:foo'); }); }); }); }); it(`should error on no match with a string verify "audience" option`, function (done) { verifyWithAudience(token, 'urn:no-match', (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property('message', `jwt audience invalid. expected: urn:no-match`); }); }); }); it('should error on no match with an array of string verify "audience" option', function (done) { verifyWithAudience(token, ['urn:no-match-1', 'urn:no-match-2'], (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property('message', `jwt audience invalid. expected: urn:no-match-1 or urn:no-match-2`); }); }); }); it('should error on no match with a Regex verify "audience" option', function (done) { verifyWithAudience(token, /^urn:no-match$/, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property('message', `jwt audience invalid. expected: /^urn:no-match$/`); }); }); }); it('should error on no match with an array of Regex verify "audience" option', function (done) { verifyWithAudience(token, [/^urn:no-match-1$/, /^urn:no-match-2$/], (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property( 'message', `jwt audience invalid. expected: /^urn:no-match-1$/ or /^urn:no-match-2$/` ); }); }); }); it('should error on no match with an array of a Regex and a string in verify "audience" option', function (done) { verifyWithAudience(token, [/^urn:no-match$/, 'urn:no-match'], (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property( 'message', `jwt audience invalid. expected: /^urn:no-match$/ or urn:no-match` ); }); }); }); }); describe('with an array of ["urn:foo", "urn:bar"] for "aud" value in payload', function () { let token; beforeEach(function (done) { signWithAudience(['urn:foo', 'urn:bar'], {}, (err, t) => { token = t; done(err); }); }); [ undefined, 'urn:foo', /^urn:f[o]{2}$/, ['urn:no_match', 'urn:foo'], ['urn:no_match', /^urn:f[o]{2}$/], [/^urn:no_match$/, /^urn:f[o]{2}$/], [/^urn:no_match$/, 'urn:foo'] ].forEach((audience) =>{ it(`should verify and decode with verify "audience" option of ${util.inspect(audience)}`, function (done) { verifyWithAudience(token, audience, (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud').deep.equals(['urn:foo', 'urn:bar']); }); }); }); }); it(`should error on no match with a string verify "audience" option`, function (done) { verifyWithAudience(token, 'urn:no-match', (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property('message', `jwt audience invalid. expected: urn:no-match`); }); }); }); it('should error on no match with an array of string verify "audience" option', function (done) { verifyWithAudience(token, ['urn:no-match-1', 'urn:no-match-2'], (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property('message', `jwt audience invalid. expected: urn:no-match-1 or urn:no-match-2`); }); }); }); it('should error on no match with a Regex verify "audience" option', function (done) { verifyWithAudience(token, /^urn:no-match$/, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property('message', `jwt audience invalid. expected: /^urn:no-match$/`); }); }); }); it('should error on no match with an array of Regex verify "audience" option', function (done) { verifyWithAudience(token, [/^urn:no-match-1$/, /^urn:no-match-2$/], (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property( 'message', `jwt audience invalid. expected: /^urn:no-match-1$/ or /^urn:no-match-2$/` ); }); }); }); it('should error on no match with an array of a Regex and a string in verify "audience" option', function (done) { verifyWithAudience(token, [/^urn:no-match$/, 'urn:no-match'], (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property( 'message', `jwt audience invalid. expected: /^urn:no-match$/ or urn:no-match` ); }); }); }); describe('when checking for a matching on both "urn:foo" and "urn:bar"', function() { it('should verify with an array of stings verify "audience" option', function (done) { verifyWithAudience(token, ['urn:foo', 'urn:bar'], (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud').deep.equals(['urn:foo', 'urn:bar']); }); }); }); it('should verify with a Regex verify "audience" option', function (done) { verifyWithAudience(token, /^urn:[a-z]{3}$/, (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud').deep.equals(['urn:foo', 'urn:bar']); }); }); }); it('should verify with an array of Regex verify "audience" option', function (done) { verifyWithAudience(token, [/^urn:f[o]{2}$/, /^urn:b[ar]{2}$/], (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud').deep.equals(['urn:foo', 'urn:bar']); }); }); }); }); describe('when checking for a matching for "urn:foo"', function() { it('should verify with a string verify "audience"', function (done) { verifyWithAudience(token, 'urn:foo', (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud').deep.equals(['urn:foo', 'urn:bar']); }); }); }); it('should verify with a Regex verify "audience" option', function (done) { verifyWithAudience(token, /^urn:f[o]{2}$/, (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud').deep.equals(['urn:foo', 'urn:bar']); }); }); }); it('should verify with an array of Regex verify "audience"', function (done) { verifyWithAudience(token, [/^urn:no-match$/, /^urn:f[o]{2}$/], (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud').deep.equals(['urn:foo', 'urn:bar']); }); }); }); it('should verify with an array containing a string and a Regex verify "audience" option', function (done) { verifyWithAudience(token, ['urn:no_match', /^urn:f[o]{2}$/], (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud').deep.equals(['urn:foo', 'urn:bar']); }); }); }); it('should verify with an array containing a Regex and a string verify "audience" option', function (done) { verifyWithAudience(token, [/^urn:no-match$/, 'urn:foo'], (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud').deep.equals(['urn:foo', 'urn:bar']); }); }); }); }); describe('when checking matching for "urn:bar"', function() { it('should verify with a string verify "audience"', function (done) { verifyWithAudience(token, 'urn:bar', (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud').deep.equals(['urn:foo', 'urn:bar']); }); }); }); it('should verify with a Regex verify "audience" option', function (done) { verifyWithAudience(token, /^urn:b[ar]{2}$/, (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud').deep.equals(['urn:foo', 'urn:bar']); }); }); }); it('should verify with an array of Regex verify "audience" option', function (done) { verifyWithAudience(token, [/^urn:no-match$/, /^urn:b[ar]{2}$/], (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud').deep.equals(['urn:foo', 'urn:bar']); }); }); }); it('should verify with an array containing a string and a Regex verify "audience" option', function (done) { verifyWithAudience(token, ['urn:no_match', /^urn:b[ar]{2}$/], (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud').deep.equals(['urn:foo', 'urn:bar']); }); }); }); it('should verify with an array containing a Regex and a string verify "audience" option', function (done) { verifyWithAudience(token, [/^urn:no-match$/, 'urn:bar'], (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('aud').deep.equals(['urn:foo', 'urn:bar']); }); }); }); }); }); describe('without a "aud" value in payload', function () { let token; beforeEach(function (done) { signWithAudience(undefined, {}, (err, t) => { token = t; done(err); }); }); it('should verify and decode without verify "audience" option', function (done) { verifyWithAudience(token, undefined, (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.not.have.property('aud'); }); }); }); it('should error on no match with a string verify "audience" option', function (done) { verifyWithAudience(token, 'urn:no-match', (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property('message', 'jwt audience invalid. expected: urn:no-match'); }); }); }); it('should error on no match with an array of string verify "audience" option', function (done) { verifyWithAudience(token, ['urn:no-match-1', 'urn:no-match-2'], (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property('message', 'jwt audience invalid. expected: urn:no-match-1 or urn:no-match-2'); }); }); }); it('should error on no match with a Regex verify "audience" option', function (done) { verifyWithAudience(token, /^urn:no-match$/, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property('message', 'jwt audience invalid. expected: /^urn:no-match$/'); }); }); }); it('should error on no match with an array of Regex verify "audience" option', function (done) { verifyWithAudience(token, [/^urn:no-match-1$/, /^urn:no-match-2$/], (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property('message', 'jwt audience invalid. expected: /^urn:no-match-1$/ or /^urn:no-match-2$/'); }); }); }); it('should error on no match with an array of a Regex and a string in verify "audience" option', function (done) { verifyWithAudience(token, [/^urn:no-match$/, 'urn:no-match'], (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property('message', 'jwt audience invalid. expected: /^urn:no-match$/ or urn:no-match'); }); }); }); }); }); }); ================================================ FILE: test/claim-exp.test.js ================================================ 'use strict'; const jwt = require('../'); const expect = require('chai').expect; const sinon = require('sinon'); const util = require('util'); const testUtils = require('./test-utils'); const jws = require('jws'); function signWithExpiresIn(expiresIn, payload, callback) { const options = {algorithm: 'HS256'}; if (expiresIn !== undefined) { options.expiresIn = expiresIn; } testUtils.signJWTHelper(payload, 'secret', options, callback); } describe('expires', function() { describe('`jwt.sign` "expiresIn" option validation', function () { [ true, false, null, -1.1, 1.1, -Infinity, Infinity, NaN, ' ', '', 'invalid', [], ['foo'], {}, {foo: 'bar'}, ].forEach((expiresIn) => { it(`should error with with value ${util.inspect(expiresIn)}`, function (done) { signWithExpiresIn(expiresIn, {}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message') .match(/"expiresIn" should be a number of seconds or string representing a timespan/); }); }); }); }); // undefined needs special treatment because {} is not the same as {expiresIn: undefined} it('should error with with value undefined', function (done) { testUtils.signJWTHelper({}, 'secret', {expiresIn: undefined, algorithm: 'HS256'}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property( 'message', '"expiresIn" should be a number of seconds or string representing a timespan' ); }); }); }); it ('should error when "exp" is in payload', function(done) { signWithExpiresIn(100, {exp: 100}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property( 'message', 'Bad "options.expiresIn" option the payload already has an "exp" property.' ); }); }); }); it('should error with a string payload', function(done) { signWithExpiresIn(100, 'a string payload', (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', 'invalid expiresIn option for string payload'); }); }); }); it('should error with a Buffer payload', function(done) { signWithExpiresIn(100, Buffer.from('a Buffer payload'), (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', 'invalid expiresIn option for object payload'); }); }); }); }); describe('`jwt.sign` "exp" claim validation', function () { [ true, false, null, undefined, '', ' ', 'invalid', [], ['foo'], {}, {foo: 'bar'}, ].forEach((exp) => { it(`should error with with value ${util.inspect(exp)}`, function (done) { signWithExpiresIn(undefined, {exp}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', '"exp" should be a number of seconds'); }); }); }); }); }); describe('"exp" in payload validation', function () { [ true, false, null, -Infinity, Infinity, NaN, '', ' ', 'invalid', [], ['foo'], {}, {foo: 'bar'}, ].forEach((exp) => { it(`should error with with value ${util.inspect(exp)}`, function (done) { const header = { alg: 'HS256' }; const payload = { exp }; const token = jws.sign({ header, payload, secret: 'secret', encoding: 'utf8' }); testUtils.verifyJWTHelper(token, 'secret', { exp }, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property('message', 'invalid exp value'); }); }); }); }) }); describe('when signing and verifying a token with expires option', function () { let fakeClock; beforeEach(function() { fakeClock = sinon.useFakeTimers({now: 60000}); }); afterEach(function() { fakeClock.uninstall(); }); it('should set correct "exp" with negative number of seconds', function(done) { signWithExpiresIn(-10, {}, (e1, token) => { fakeClock.tick(-10001); testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('exp', 50); }); }) }); }); it('should set correct "exp" with positive number of seconds', function(done) { signWithExpiresIn(10, {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('exp', 70); }); }) }); }); it('should set correct "exp" with zero seconds', function(done) { signWithExpiresIn(0, {}, (e1, token) => { fakeClock.tick(-1); testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('exp', 60); }); }) }); }); it('should set correct "exp" with negative string timespan', function(done) { signWithExpiresIn('-10 s', {}, (e1, token) => { fakeClock.tick(-10001); testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('exp', 50); }); }) }); }); it('should set correct "exp" with positive string timespan', function(done) { signWithExpiresIn('10 s', {}, (e1, token) => { fakeClock.tick(-10001); testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('exp', 70); }); }) }); }); it('should set correct "exp" with zero string timespan', function(done) { signWithExpiresIn('0 s', {}, (e1, token) => { fakeClock.tick(-1); testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('exp', 60); }); }) }); }); // TODO an exp of -Infinity should fail validation it('should set null "exp" when given -Infinity', function (done) { signWithExpiresIn(undefined, {exp: -Infinity}, (err, token) => { const decoded = jwt.decode(token); testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('exp', null); }); }); }); // TODO an exp of Infinity should fail validation it('should set null "exp" when given value Infinity', function (done) { signWithExpiresIn(undefined, {exp: Infinity}, (err, token) => { const decoded = jwt.decode(token); testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('exp', null); }); }); }); // TODO an exp of NaN should fail validation it('should set null "exp" when given value NaN', function (done) { signWithExpiresIn(undefined, {exp: NaN}, (err, token) => { const decoded = jwt.decode(token); testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('exp', null); }); }); }); it('should set correct "exp" when "iat" is passed', function (done) { signWithExpiresIn(-10, {iat: 80}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('exp', 70); }); }) }); }); it('should verify "exp" using "clockTimestamp"', function (done) { signWithExpiresIn(10, {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {clockTimestamp: 69}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('iat', 60); expect(decoded).to.have.property('exp', 70); }); }) }); }); it('should verify "exp" using "clockTolerance"', function (done) { signWithExpiresIn(5, {}, (e1, token) => { fakeClock.tick(10000); testUtils.verifyJWTHelper(token, 'secret', {clockTimestamp: 6}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('iat', 60); expect(decoded).to.have.property('exp', 65); }); }) }); }); it('should ignore a expired token when "ignoreExpiration" is true', function (done) { signWithExpiresIn('-10 s', {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {ignoreExpiration: true}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('iat', 60); expect(decoded).to.have.property('exp', 50); }); }) }); }); it('should error on verify if "exp" is at current time', function(done) { signWithExpiresIn(undefined, {exp: 60}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.instanceOf(jwt.TokenExpiredError); expect(e2).to.have.property('message', 'jwt expired'); }); }); }); }); it('should error on verify if "exp" is before current time using clockTolerance', function (done) { signWithExpiresIn(-5, {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {clockTolerance: 5}, (e2) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.instanceOf(jwt.TokenExpiredError); expect(e2).to.have.property('message', 'jwt expired'); }); }); }); }); }); }); ================================================ FILE: test/claim-iat.test.js ================================================ 'use strict'; const jwt = require('../'); const expect = require('chai').expect; const sinon = require('sinon'); const util = require('util'); const testUtils = require('./test-utils'); const jws = require('jws'); function signWithIssueAt(issueAt, options, callback) { const payload = {}; if (issueAt !== undefined) { payload.iat = issueAt; } const opts = Object.assign({algorithm: 'HS256'}, options); // async calls require a truthy secret // see: https://github.com/brianloveswords/node-jws/issues/62 testUtils.signJWTHelper(payload, 'secret', opts, callback); } function verifyWithIssueAt(token, maxAge, options, secret, callback) { const opts = Object.assign({maxAge}, options); testUtils.verifyJWTHelper(token, secret, opts, callback); } describe('issue at', function() { describe('`jwt.sign` "iat" claim validation', function () { [ true, false, null, '', 'invalid', [], ['foo'], {}, {foo: 'bar'}, ].forEach((iat) => { it(`should error with iat of ${util.inspect(iat)}`, function (done) { signWithIssueAt(iat, {}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err.message).to.equal('"iat" should be a number of seconds'); }); }); }); }); // undefined needs special treatment because {} is not the same as {iat: undefined} it('should error with iat of undefined', function (done) { testUtils.signJWTHelper({iat: undefined}, 'secret', {algorithm: 'HS256'}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err.message).to.equal('"iat" should be a number of seconds'); }); }); }); }); describe('"iat" in payload with "maxAge" option validation', function () { [ true, false, null, undefined, -Infinity, Infinity, NaN, '', 'invalid', [], ['foo'], {}, {foo: 'bar'}, ].forEach((iat) => { it(`should error with iat of ${util.inspect(iat)}`, function (done) { const header = { alg: 'HS256' }; const payload = { iat }; const token = jws.sign({ header, payload, secret: 'secret', encoding: 'utf8' }); verifyWithIssueAt(token, '1 min', {}, 'secret', (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err.message).to.equal('iat required when maxAge is specified'); }); }); }); }) }); describe('when signing a token', function () { let fakeClock; beforeEach(function () { fakeClock = sinon.useFakeTimers({now: 60000}); }); afterEach(function () { fakeClock.uninstall(); }); [ { description: 'should default to current time for "iat"', iat: undefined, expectedIssueAt: 60, options: {} }, { description: 'should sign with provided time for "iat"', iat: 100, expectedIssueAt: 100, options: {} }, // TODO an iat of -Infinity should fail validation { description: 'should set null "iat" when given -Infinity', iat: -Infinity, expectedIssueAt: null, options: {} }, // TODO an iat of Infinity should fail validation { description: 'should set null "iat" when given Infinity', iat: Infinity, expectedIssueAt: null, options: {} }, // TODO an iat of NaN should fail validation { description: 'should set to current time for "iat" when given value NaN', iat: NaN, expectedIssueAt: 60, options: {} }, { description: 'should remove default "iat" with "noTimestamp" option', iat: undefined, expectedIssueAt: undefined, options: {noTimestamp: true} }, { description: 'should remove provided "iat" with "noTimestamp" option', iat: 10, expectedIssueAt: undefined, options: {noTimestamp: true} }, ].forEach((testCase) => { it(testCase.description, function (done) { signWithIssueAt(testCase.iat, testCase.options, (err, token) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(jwt.decode(token).iat).to.equal(testCase.expectedIssueAt); }); }); }); }); }); describe('when verifying a token', function() { let fakeClock; beforeEach(function() { fakeClock = sinon.useFakeTimers({now: 60000}); }); afterEach(function () { fakeClock.uninstall(); }); [ { description: 'should verify using "iat" before the "maxAge"', clockAdvance: 10000, maxAge: 11, options: {}, }, { description: 'should verify using "iat" before the "maxAge" with a provided "clockTimestamp', clockAdvance: 60000, maxAge: 11, options: {clockTimestamp: 70}, }, { description: 'should verify using "iat" after the "maxAge" but within "clockTolerance"', clockAdvance: 10000, maxAge: 9, options: {clockTimestamp: 2}, }, ].forEach((testCase) => { it(testCase.description, function (done) { const token = jwt.sign({}, 'secret', {algorithm: 'HS256'}); fakeClock.tick(testCase.clockAdvance); verifyWithIssueAt(token, testCase.maxAge, testCase.options, 'secret', (err, token) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(token).to.be.a('object'); }); }); }); }); [ { description: 'should throw using "iat" equal to the "maxAge"', clockAdvance: 10000, maxAge: 10, options: {}, expectedError: 'maxAge exceeded', expectedExpiresAt: 70000, }, { description: 'should throw using "iat" after the "maxAge"', clockAdvance: 10000, maxAge: 9, options: {}, expectedError: 'maxAge exceeded', expectedExpiresAt: 69000, }, { description: 'should throw using "iat" after the "maxAge" with a provided "clockTimestamp', clockAdvance: 60000, maxAge: 10, options: {clockTimestamp: 70}, expectedError: 'maxAge exceeded', expectedExpiresAt: 70000, }, { description: 'should throw using "iat" after the "maxAge" and "clockTolerance', clockAdvance: 10000, maxAge: 8, options: {clockTolerance: 2}, expectedError: 'maxAge exceeded', expectedExpiresAt: 68000, }, ].forEach((testCase) => { it(testCase.description, function(done) { const expectedExpiresAtDate = new Date(testCase.expectedExpiresAt); const token = jwt.sign({}, 'secret', {algorithm: 'HS256'}); fakeClock.tick(testCase.clockAdvance); verifyWithIssueAt(token, testCase.maxAge, testCase.options, 'secret', (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err.message).to.equal(testCase.expectedError); expect(err.expiredAt).to.deep.equal(expectedExpiresAtDate); }); }); }); }); }); describe('with string payload', function () { it('should not add iat to string', function (done) { const payload = 'string payload'; const options = {algorithm: 'HS256'}; testUtils.signJWTHelper(payload, 'secret', options, (err, token) => { const decoded = jwt.decode(token); testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.equal(payload); }); }); }); it('should not add iat to stringified object', function (done) { const payload = '{}'; const options = {algorithm: 'HS256', header: {typ: 'JWT'}}; testUtils.signJWTHelper(payload, 'secret', options, (err, token) => { const decoded = jwt.decode(token); testUtils.asyncCheck(done, () => { expect(err).to.equal(null); expect(JSON.stringify(decoded)).to.equal(payload); }); }); }); }); }); ================================================ FILE: test/claim-iss.test.js ================================================ 'use strict'; const jwt = require('../'); const expect = require('chai').expect; const util = require('util'); const testUtils = require('./test-utils'); function signWithIssuer(issuer, payload, callback) { const options = {algorithm: 'HS256'}; if (issuer !== undefined) { options.issuer = issuer; } testUtils.signJWTHelper(payload, 'secret', options, callback); } describe('issuer', function() { describe('`jwt.sign` "issuer" option validation', function () { [ true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, [], ['foo'], {}, {foo: 'bar'}, ].forEach((issuer) => { it(`should error with with value ${util.inspect(issuer)}`, function (done) { signWithIssuer(issuer, {}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', '"issuer" must be a string'); }); }); }); }); // undefined needs special treatment because {} is not the same as {issuer: undefined} it('should error with with value undefined', function (done) { testUtils.signJWTHelper({}, 'secret', {issuer: undefined, algorithm: 'HS256'}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', '"issuer" must be a string'); }); }); }); it('should error when "iss" is in payload', function (done) { signWithIssuer('foo', {iss: 'bar'}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property( 'message', 'Bad "options.issuer" option. The payload already has an "iss" property.' ); }); }); }); it('should error with a string payload', function (done) { signWithIssuer('foo', 'a string payload', (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property( 'message', 'invalid issuer option for string payload' ); }); }); }); it('should error with a Buffer payload', function (done) { signWithIssuer('foo', new Buffer('a Buffer payload'), (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property( 'message', 'invalid issuer option for object payload' ); }); }); }); }); describe('when signing and verifying a token', function () { it('should not verify "iss" if verify "issuer" option not provided', function(done) { signWithIssuer(undefined, {iss: 'foo'}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('iss', 'foo'); }); }) }); }); describe('with string "issuer" option', function () { it('should verify with a string "issuer"', function (done) { signWithIssuer('foo', {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {issuer: 'foo'}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('iss', 'foo'); }); }) }); }); it('should verify with a string "iss"', function (done) { signWithIssuer(undefined, {iss: 'foo'}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {issuer: 'foo'}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('iss', 'foo'); }); }) }); }); it('should error if "iss" does not match verify "issuer" option', function(done) { signWithIssuer(undefined, {iss: 'foobar'}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {issuer: 'foo'}, (e2) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.instanceOf(jwt.JsonWebTokenError); expect(e2).to.have.property('message', 'jwt issuer invalid. expected: foo'); }); }) }); }); it('should error without "iss" and with verify "issuer" option', function(done) { signWithIssuer(undefined, {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {issuer: 'foo'}, (e2) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.instanceOf(jwt.JsonWebTokenError); expect(e2).to.have.property('message', 'jwt issuer invalid. expected: foo'); }); }) }); }); }); describe('with array "issuer" option', function () { it('should verify with a string "issuer"', function (done) { signWithIssuer('bar', {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {issuer: ['foo', 'bar']}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('iss', 'bar'); }); }) }); }); it('should verify with a string "iss"', function (done) { signWithIssuer(undefined, {iss: 'foo'}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {issuer: ['foo', 'bar']}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('iss', 'foo'); }); }) }); }); it('should error if "iss" does not match verify "issuer" option', function(done) { signWithIssuer(undefined, {iss: 'foobar'}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {issuer: ['foo', 'bar']}, (e2) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.instanceOf(jwt.JsonWebTokenError); expect(e2).to.have.property('message', 'jwt issuer invalid. expected: foo,bar'); }); }) }); }); it('should error without "iss" and with verify "issuer" option', function(done) { signWithIssuer(undefined, {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {issuer: ['foo', 'bar']}, (e2) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.instanceOf(jwt.JsonWebTokenError); expect(e2).to.have.property('message', 'jwt issuer invalid. expected: foo,bar'); }); }) }); }); }); }); }); ================================================ FILE: test/claim-jti.test.js ================================================ 'use strict'; const jwt = require('../'); const expect = require('chai').expect; const util = require('util'); const testUtils = require('./test-utils'); function signWithJWTId(jwtid, payload, callback) { const options = {algorithm: 'HS256'}; if (jwtid !== undefined) { options.jwtid = jwtid; } testUtils.signJWTHelper(payload, 'secret', options, callback); } describe('jwtid', function() { describe('`jwt.sign` "jwtid" option validation', function () { [ true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, [], ['foo'], {}, {foo: 'bar'}, ].forEach((jwtid) => { it(`should error with with value ${util.inspect(jwtid)}`, function (done) { signWithJWTId(jwtid, {}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', '"jwtid" must be a string'); }); }); }); }); // undefined needs special treatment because {} is not the same as {jwtid: undefined} it('should error with with value undefined', function (done) { testUtils.signJWTHelper({}, 'secret', {jwtid: undefined, algorithm: 'HS256'}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', '"jwtid" must be a string'); }); }); }); it('should error when "jti" is in payload', function (done) { signWithJWTId('foo', {jti: 'bar'}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property( 'message', 'Bad "options.jwtid" option. The payload already has an "jti" property.' ); }); }); }); it('should error with a string payload', function (done) { signWithJWTId('foo', 'a string payload', (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property( 'message', 'invalid jwtid option for string payload' ); }); }); }); it('should error with a Buffer payload', function (done) { signWithJWTId('foo', new Buffer('a Buffer payload'), (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property( 'message', 'invalid jwtid option for object payload' ); }); }); }); }); describe('when signing and verifying a token', function () { it('should not verify "jti" if verify "jwtid" option not provided', function(done) { signWithJWTId(undefined, {jti: 'foo'}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('jti', 'foo'); }); }) }); }); describe('with "jwtid" option', function () { it('should verify with "jwtid" option', function (done) { signWithJWTId('foo', {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {jwtid: 'foo'}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('jti', 'foo'); }); }) }); }); it('should verify with "jti" in payload', function (done) { signWithJWTId(undefined, {jti: 'foo'}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {jetid: 'foo'}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('jti', 'foo'); }); }) }); }); it('should error if "jti" does not match verify "jwtid" option', function(done) { signWithJWTId(undefined, {jti: 'bar'}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {jwtid: 'foo'}, (e2) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.instanceOf(jwt.JsonWebTokenError); expect(e2).to.have.property('message', 'jwt jwtid invalid. expected: foo'); }); }) }); }); it('should error without "jti" and with verify "jwtid" option', function(done) { signWithJWTId(undefined, {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {jwtid: 'foo'}, (e2) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.instanceOf(jwt.JsonWebTokenError); expect(e2).to.have.property('message', 'jwt jwtid invalid. expected: foo'); }); }) }); }); }); }); }); ================================================ FILE: test/claim-nbf.test.js ================================================ 'use strict'; const jwt = require('../'); const expect = require('chai').expect; const sinon = require('sinon'); const util = require('util'); const testUtils = require('./test-utils'); const jws = require('jws'); function signWithNotBefore(notBefore, payload, callback) { const options = {algorithm: 'HS256'}; if (notBefore !== undefined) { options.notBefore = notBefore; } testUtils.signJWTHelper(payload, 'secret', options, callback); } describe('not before', function() { describe('`jwt.sign` "notBefore" option validation', function () { [ true, false, null, -1.1, 1.1, -Infinity, Infinity, NaN, '', ' ', 'invalid', [], ['foo'], {}, {foo: 'bar'}, ].forEach((notBefore) => { it(`should error with with value ${util.inspect(notBefore)}`, function (done) { signWithNotBefore(notBefore, {}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message') .match(/"notBefore" should be a number of seconds or string representing a timespan/); }); }); }); }); // undefined needs special treatment because {} is not the same as {notBefore: undefined} it('should error with with value undefined', function (done) { testUtils.signJWTHelper({}, 'secret', {notBefore: undefined, algorithm: 'HS256'}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property( 'message', '"notBefore" should be a number of seconds or string representing a timespan' ); }); }); }); it('should error when "nbf" is in payload', function (done) { signWithNotBefore(100, {nbf: 100}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property( 'message', 'Bad "options.notBefore" option the payload already has an "nbf" property.' ); }); }); }); it('should error with a string payload', function (done) { signWithNotBefore(100, 'a string payload', (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', 'invalid notBefore option for string payload'); }); }); }); it('should error with a Buffer payload', function (done) { signWithNotBefore(100, new Buffer('a Buffer payload'), (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', 'invalid notBefore option for object payload'); }); }); }); }); describe('`jwt.sign` "nbf" claim validation', function () { [ true, false, null, undefined, '', ' ', 'invalid', [], ['foo'], {}, {foo: 'bar'}, ].forEach((nbf) => { it(`should error with with value ${util.inspect(nbf)}`, function (done) { signWithNotBefore(undefined, {nbf}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', '"nbf" should be a number of seconds'); }); }); }); }); }); describe('"nbf" in payload validation', function () { [ true, false, null, -Infinity, Infinity, NaN, '', ' ', 'invalid', [], ['foo'], {}, {foo: 'bar'}, ].forEach((nbf) => { it(`should error with with value ${util.inspect(nbf)}`, function (done) { const header = { alg: 'HS256' }; const payload = { nbf }; const token = jws.sign({ header, payload, secret: 'secret', encoding: 'utf8' }); testUtils.verifyJWTHelper(token, 'secret', {nbf}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property('message', 'invalid nbf value'); }); }); }); }) }); describe('when signing and verifying a token with "notBefore" option', function () { let fakeClock; beforeEach(function () { fakeClock = sinon.useFakeTimers({now: 60000}); }); afterEach(function () { fakeClock.uninstall(); }); it('should set correct "nbf" with negative number of seconds', function (done) { signWithNotBefore(-10, {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('nbf', 50); }); }) }); }); it('should set correct "nbf" with positive number of seconds', function (done) { signWithNotBefore(10, {}, (e1, token) => { fakeClock.tick(10000); testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('nbf', 70); }); }) }); }); it('should set correct "nbf" with zero seconds', function (done) { signWithNotBefore(0, {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('nbf', 60); }); }) }); }); it('should set correct "nbf" with negative string timespan', function (done) { signWithNotBefore('-10 s', {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('nbf', 50); }); }) }); }); it('should set correct "nbf" with positive string timespan', function (done) { signWithNotBefore('10 s', {}, (e1, token) => { fakeClock.tick(10000); testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('nbf', 70); }); }) }); }); it('should set correct "nbf" with zero string timespan', function (done) { signWithNotBefore('0 s', {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('nbf', 60); }); }) }); }); // TODO an nbf of -Infinity should fail validation it('should set null "nbf" when given -Infinity', function (done) { signWithNotBefore(undefined, {nbf: -Infinity}, (err, token) => { const decoded = jwt.decode(token); testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('nbf', null); }); }); }); // TODO an nbf of Infinity should fail validation it('should set null "nbf" when given value Infinity', function (done) { signWithNotBefore(undefined, {nbf: Infinity}, (err, token) => { const decoded = jwt.decode(token); testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('nbf', null); }); }); }); // TODO an nbf of NaN should fail validation it('should set null "nbf" when given value NaN', function (done) { signWithNotBefore(undefined, {nbf: NaN}, (err, token) => { const decoded = jwt.decode(token); testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('nbf', null); }); }); }); it('should set correct "nbf" when "iat" is passed', function (done) { signWithNotBefore(-10, {iat: 40}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('nbf', 30); }); }) }); }); it('should verify "nbf" using "clockTimestamp"', function (done) { signWithNotBefore(10, {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {clockTimestamp: 70}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('iat', 60); expect(decoded).to.have.property('nbf', 70); }); }) }); }); it('should verify "nbf" using "clockTolerance"', function (done) { signWithNotBefore(5, {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {clockTolerance: 6}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('iat', 60); expect(decoded).to.have.property('nbf', 65); }); }) }); }); it('should ignore a not active token when "ignoreNotBefore" is true', function (done) { signWithNotBefore('10 s', {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {ignoreNotBefore: true}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('iat', 60); expect(decoded).to.have.property('nbf', 70); }); }) }); }); it('should error on verify if "nbf" is after current time', function (done) { signWithNotBefore(undefined, {nbf: 61}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.instanceOf(jwt.NotBeforeError); expect(e2).to.have.property('message', 'jwt not active'); }); }) }); }); it('should error on verify if "nbf" is after current time using clockTolerance', function (done) { signWithNotBefore(5, {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {clockTolerance: 4}, (e2) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.instanceOf(jwt.NotBeforeError); expect(e2).to.have.property('message', 'jwt not active'); }); }) }); }); }); }); ================================================ FILE: test/claim-private.tests.js ================================================ 'use strict'; const expect = require('chai').expect; const util = require('util'); const testUtils = require('./test-utils'); function signWithPayload(payload, callback) { testUtils.signJWTHelper(payload, 'secret', {algorithm: 'HS256'}, callback); } describe('with a private claim', function() { [ true, false, null, -1, 0, 1, -1.1, 1.1, '', 'private claim', 'UTF8 - José', [], ['foo'], {}, {foo: 'bar'}, ].forEach((privateClaim) => { it(`should sign and verify with claim of ${util.inspect(privateClaim)}`, function (done) { signWithPayload({privateClaim}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('privateClaim').to.deep.equal(privateClaim); }); }) }); }); }); // these values JSON.stringify to null [ -Infinity, Infinity, NaN, ].forEach((privateClaim) => { it(`should sign and verify with claim of ${util.inspect(privateClaim)}`, function (done) { signWithPayload({privateClaim}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('privateClaim', null); }); }) }); }); }); // private claims with value undefined are not added to the payload it(`should sign and verify with claim of undefined`, function (done) { signWithPayload({privateClaim: undefined}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.not.have.property('privateClaim'); }); }) }); }); }); ================================================ FILE: test/claim-sub.tests.js ================================================ 'use strict'; const jwt = require('../'); const expect = require('chai').expect; const util = require('util'); const testUtils = require('./test-utils'); function signWithSubject(subject, payload, callback) { const options = {algorithm: 'HS256'}; if (subject !== undefined) { options.subject = subject; } testUtils.signJWTHelper(payload, 'secret', options, callback); } describe('subject', function() { describe('`jwt.sign` "subject" option validation', function () { [ true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, [], ['foo'], {}, {foo: 'bar'}, ].forEach((subject) => { it(`should error with with value ${util.inspect(subject)}`, function (done) { signWithSubject(subject, {}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', '"subject" must be a string'); }); }); }); }); // undefined needs special treatment because {} is not the same as {subject: undefined} it('should error with with value undefined', function (done) { testUtils.signJWTHelper({}, 'secret', {subject: undefined, algorithm: 'HS256'}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', '"subject" must be a string'); }); }); }); it('should error when "sub" is in payload', function (done) { signWithSubject('foo', {sub: 'bar'}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property( 'message', 'Bad "options.subject" option. The payload already has an "sub" property.' ); }); }); }); it('should error with a string payload', function (done) { signWithSubject('foo', 'a string payload', (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property( 'message', 'invalid subject option for string payload' ); }); }); }); it('should error with a Buffer payload', function (done) { signWithSubject('foo', new Buffer('a Buffer payload'), (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property( 'message', 'invalid subject option for object payload' ); }); }); }); }); describe('when signing and verifying a token with "subject" option', function () { it('should verify with a string "subject"', function (done) { signWithSubject('foo', {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {subject: 'foo'}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('sub', 'foo'); }); }) }); }); it('should verify with a string "sub"', function (done) { signWithSubject(undefined, {sub: 'foo'}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {subject: 'foo'}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('sub', 'foo'); }); }) }); }); it('should not verify "sub" if verify "subject" option not provided', function(done) { signWithSubject(undefined, {sub: 'foo'}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {}, (e2, decoded) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.null; expect(decoded).to.have.property('sub', 'foo'); }); }) }); }); it('should error if "sub" does not match verify "subject" option', function(done) { signWithSubject(undefined, {sub: 'foo'}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {subject: 'bar'}, (e2) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.instanceOf(jwt.JsonWebTokenError); expect(e2).to.have.property('message', 'jwt subject invalid. expected: bar'); }); }) }); }); it('should error without "sub" and with verify "subject" option', function(done) { signWithSubject(undefined, {}, (e1, token) => { testUtils.verifyJWTHelper(token, 'secret', {subject: 'foo'}, (e2) => { testUtils.asyncCheck(done, () => { expect(e1).to.be.null; expect(e2).to.be.instanceOf(jwt.JsonWebTokenError); expect(e2).to.have.property('message', 'jwt subject invalid. expected: foo'); }); }) }); }); }); }); ================================================ FILE: test/decoding.tests.js ================================================ var jwt = require('../index'); var expect = require('chai').expect; describe('decoding', function() { it('should not crash when decoding a null token', function () { var decoded = jwt.decode("null"); expect(decoded).to.equal(null); }); }); ================================================ FILE: test/dsa-private.pem ================================================ -----BEGIN DSA PRIVATE KEY----- MIIGWAIBAAKCAgEArzbPbt//BQpsYsnoZR4R9nXgcuvcXoH8WZjRsb4ZPfVJGchG 7CfRMlG0HR34vcUpehNj5pAavErhfNnk1CEal0TyDsOkBY/+JG239zXgRzMYjSE6 ptX5kj5pGv0uXVoozSP/JZblI8/Spd6TZkblLNAYOl3ssfcUGN4NFDXlzmiWvP+q 6ZUgE8tD7CSryicICKmXcVQIa6AG8ultYa6mBAaewzMbiIt2TUo9smglpEqGeHoL CuLb3e7zLf0AhWDZOgTTfe1KFEiK6TXMe9HWYeP3MPuyKhS20GmT/Zcu5VN4wbr0 bP+mTWk700oLJ0OPQ6YgGkyqBmh/Bsi/TqnpJWS/mjRbJEe3E2NmNMwmP4jwJ79V JClp5Gg9kbM6hPkmGNnhbbFzn3kwY3pi9/AiqpGyr3GUPhXvP7fYwAu/A5ISKw8r 87j/EJntyIzm51fcm8Q0mq1IDt4tNkIOwJEIc45h9r7ZC1VAKkzlCa7XT04GguFo JMaJBYESYcOAmbKRojo8P/cN4fPuemuhQFQplkFIM6FtG9cJMo2ayp6ukH9Up8tn 8j7YgE/m9BL9SnUIbNlti9j0cNgeKVn24WC38hw9D8M0/sR5gYyclWh/OotCttoQ I8ySZzSvB4GARZHbexagvg1EdV93ctYyAWGLkpJYAzuiXbt7FayG7e2ifYkCIQDp IldsAFGVaiJRQdiKsWdReOSjzH6h8cw6Co3OCISiOQKCAgEAnSU29U65jK3W2BiA fKTlTBx2yDUCDFeqnla5arZ2njGsUKiP2nocArAPLQggwk9rfqufybQltM8+zjmE zeb4mUCVhSbTH7BvP903U0YEabZJCHLx80nTywq2RgQs0Qmn43vs2U5EidYR0xj8 CCNAH5gdzd9/CL1RYACHAf7zj4n68ZaNkAy9Jz1JjYXjP6IAxJh1W/Y0vsdFdIJ/ dnuxsyMCUCSwDvSNApSfATO/tw+DCVpGgKo4qE8b8lsfXKeihuMzyXuSe/D98YN2 UFWRTQ6gFxGrntg3LOn41RXSkXxzixgl7quacIJzm8jrFkDJSx4AZ8rgt/9JbThA XF9PVlCVv7GL1NztUs4cDK+zsJld4O1rlI3QOz5DWq9oA+Hj1MN3L9IW3Iv2Offo AaubXJhuv0xPWYmtCo06mPgSwkWPjDnGCbp1vuI8zPTsfyhsahuKeW0h8JttW4GB 6CTtC1AVWA1pJug5pBo36S5G24ihRsdG3Q5/aTlnke7t7H1Tkh2KuvV9hD5a5Xtw cnuiEcKjyR0FWR81RdsAKh+7QNI3Lx75c95i22Aupon5R/Qkb05VzHdd299bb78c x5mW8Dsg4tKLF7kpDAcWmx7JpkPHQ+5V9N766sfZ+z/PiVWfNAK8gzJRn/ceLQcK C6uOhcZgN0o4UYrmYEy9icxJ44wCggIBAIu+yagyVMS+C5OqOprmtteh/+MyaYI+ Q3oPXFR8eHLJftsBWev1kRfje1fdxzzx/k4SQMRbxxbMtGV74KNwRUzEWOkoyAHP AAjhMio1mxknPwAxRjWDOSE0drGJPyGpI9ZfpMUtvekQO7MCGqa45vPldY10RwZC VN66AIpxSF0MG1OEmgD+noHMI7moclw/nw+ZUPaIFxvPstlD4EsPDkdE0I6x3k3b UXlWAYAJFR6fNf8+Ki3xnjLjW9da3cU/p2H7+LrFDP+kPUGJpqr4bG606GUcV3Cl dznoqlgaudWgcQCQx0NPzi7k5O7PXr7C3UU0cg+5+GkviIzogaioxidvvchnG+UU 0y5nVuji6G69j5sUhlcFXte31Nte2VUb6P8umo+mbDT0UkZZZzoOsCpw+cJ8OHOV emFIhVphNHqQt20Tq6WVRBx+p4+YNWiThvmLtmLh0QghdnUrJZxyXx7/p8K5SE9/ +qU11t5dUvYS+53U1gJ2kgIFO4Zt6gaoOyexTt5f4Ganh9IcJ01wegl5WT58aDtf hmw0HnOrgbWt4lRkxOra281hL74xcgtgMZQ32PTOy8wTEVTk03mmqlIq/dV4jgBc Nh1FGQwGEeGlfbuNSB4nqgMN6zn1PmI7oCWLD9XLR6VZTebF7pGfpHtYczyivuxf e1YOro6e0mUqAiEAx4K3cPG3dxH91uU3L+sS2vzqXEVn2BmSMmkGczSOgn4= -----END DSA PRIVATE KEY----- ================================================ FILE: test/dsa-public.pem ================================================ -----BEGIN PUBLIC KEY----- MIIGSDCCBDoGByqGSM44BAEwggQtAoICAQCvNs9u3/8FCmxiyehlHhH2deBy69xe gfxZmNGxvhk99UkZyEbsJ9EyUbQdHfi9xSl6E2PmkBq8SuF82eTUIRqXRPIOw6QF j/4kbbf3NeBHMxiNITqm1fmSPmka/S5dWijNI/8lluUjz9Kl3pNmRuUs0Bg6Xeyx 9xQY3g0UNeXOaJa8/6rplSATy0PsJKvKJwgIqZdxVAhroAby6W1hrqYEBp7DMxuI i3ZNSj2yaCWkSoZ4egsK4tvd7vMt/QCFYNk6BNN97UoUSIrpNcx70dZh4/cw+7Iq FLbQaZP9ly7lU3jBuvRs/6ZNaTvTSgsnQ49DpiAaTKoGaH8GyL9OqeklZL+aNFsk R7cTY2Y0zCY/iPAnv1UkKWnkaD2RszqE+SYY2eFtsXOfeTBjemL38CKqkbKvcZQ+ Fe8/t9jAC78DkhIrDyvzuP8Qme3IjObnV9ybxDSarUgO3i02Qg7AkQhzjmH2vtkL VUAqTOUJrtdPTgaC4WgkxokFgRJhw4CZspGiOjw/9w3h8+56a6FAVCmWQUgzoW0b 1wkyjZrKnq6Qf1Sny2fyPtiAT+b0Ev1KdQhs2W2L2PRw2B4pWfbhYLfyHD0PwzT+ xHmBjJyVaH86i0K22hAjzJJnNK8HgYBFkdt7FqC+DUR1X3dy1jIBYYuSklgDO6Jd u3sVrIbt7aJ9iQIhAOkiV2wAUZVqIlFB2IqxZ1F45KPMfqHxzDoKjc4IhKI5AoIC AQCdJTb1TrmMrdbYGIB8pOVMHHbINQIMV6qeVrlqtnaeMaxQqI/aehwCsA8tCCDC T2t+q5/JtCW0zz7OOYTN5viZQJWFJtMfsG8/3TdTRgRptkkIcvHzSdPLCrZGBCzR Cafje+zZTkSJ1hHTGPwII0AfmB3N338IvVFgAIcB/vOPifrxlo2QDL0nPUmNheM/ ogDEmHVb9jS+x0V0gn92e7GzIwJQJLAO9I0ClJ8BM7+3D4MJWkaAqjioTxvyWx9c p6KG4zPJe5J78P3xg3ZQVZFNDqAXEaue2Dcs6fjVFdKRfHOLGCXuq5pwgnObyOsW QMlLHgBnyuC3/0ltOEBcX09WUJW/sYvU3O1SzhwMr7OwmV3g7WuUjdA7PkNar2gD 4ePUw3cv0hbci/Y59+gBq5tcmG6/TE9Zia0KjTqY+BLCRY+MOcYJunW+4jzM9Ox/ KGxqG4p5bSHwm21bgYHoJO0LUBVYDWkm6DmkGjfpLkbbiKFGx0bdDn9pOWeR7u3s fVOSHYq69X2EPlrle3Bye6IRwqPJHQVZHzVF2wAqH7tA0jcvHvlz3mLbYC6miflH 9CRvTlXMd13b31tvvxzHmZbwOyDi0osXuSkMBxabHsmmQ8dD7lX03vrqx9n7P8+J VZ80AryDMlGf9x4tBwoLq46FxmA3SjhRiuZgTL2JzEnjjAOCAgYAAoICAQCLvsmo MlTEvguTqjqa5rbXof/jMmmCPkN6D1xUfHhyyX7bAVnr9ZEX43tX3cc88f5OEkDE W8cWzLRle+CjcEVMxFjpKMgBzwAI4TIqNZsZJz8AMUY1gzkhNHaxiT8hqSPWX6TF Lb3pEDuzAhqmuObz5XWNdEcGQlTeugCKcUhdDBtThJoA/p6BzCO5qHJcP58PmVD2 iBcbz7LZQ+BLDw5HRNCOsd5N21F5VgGACRUenzX/Piot8Z4y41vXWt3FP6dh+/i6 xQz/pD1Biaaq+GxutOhlHFdwpXc56KpYGrnVoHEAkMdDT84u5OTuz16+wt1FNHIP ufhpL4iM6IGoqMYnb73IZxvlFNMuZ1bo4uhuvY+bFIZXBV7Xt9TbXtlVG+j/LpqP pmw09FJGWWc6DrAqcPnCfDhzlXphSIVaYTR6kLdtE6ullUQcfqePmDVok4b5i7Zi 4dEIIXZ1KyWccl8e/6fCuUhPf/qlNdbeXVL2Evud1NYCdpICBTuGbeoGqDsnsU7e X+Bmp4fSHCdNcHoJeVk+fGg7X4ZsNB5zq4G1reJUZMTq2tvNYS++MXILYDGUN9j0 zsvMExFU5NN5pqpSKv3VeI4AXDYdRRkMBhHhpX27jUgeJ6oDDes59T5iO6Aliw/V y0elWU3mxe6Rn6R7WHM8or7sX3tWDq6OntJlKg== -----END PUBLIC KEY----- ================================================ FILE: test/ecdsa-private.pem ================================================ -----BEGIN EC PARAMETERS----- MIH3AgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP////////// /////zBbBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY12Ko6 k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsDFQDEnTYIhucEk2pmeOETnSa3gZ9+ kARBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLeszoPShOUXYmMKWT+NC4v4af5uO5+tK fA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////AAAAAP//////////vOb6racXnoTz ucrC/GMlUQIBAQ== -----END EC PARAMETERS----- -----BEGIN EC PRIVATE KEY----- MIIBaAIBAQQgeg2m9tJJsnURyjTUihohiJahj9ETy3csUIt4EYrV+J2ggfowgfcC AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA//////////////// MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8 YyVRAgEBoUQDQgAEEWluurrkZECnq27UpNauq16f9+5DDMFJZ3HV43Ujc3tcXQ++ N1T/0CAA8ve286f32s7rkqX/pPokI/HBpP5p3g== -----END EC PRIVATE KEY----- ================================================ FILE: test/ecdsa-public-invalid.pem ================================================ -----BEGIN PUBLIC KEY----- MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA//// ///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5 RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA //////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABEfZiYJDbghTGQ+KGnHGSl6K yUqK/BL2uJIg7Z0bx48v6+L7Ve8MCS17eptkMT2e4l5B/ZGDVUHb6uZ5xFROLBw= -----END PUBLIC KEY----- ================================================ FILE: test/ecdsa-public-x509.pem ================================================ -----BEGIN CERTIFICATE----- MIIDGjCCAsKgAwIBAgIJANuPNBWwp6wzMAkGByqGSM49BAEwRTELMAkGA1UEBhMC QVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdp dHMgUHR5IEx0ZDAeFw0xNzA2MTAxMTAzMjJaFw0yNzA2MDgxMTAzMjJaMEUxCzAJ BgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l dCBXaWRnaXRzIFB0eSBMdGQwggFLMIIBAwYHKoZIzj0CATCB9wIBATAsBgcqhkjO PQEBAiEA/////wAAAAEAAAAAAAAAAAAAAAD///////////////8wWwQg/////wAA AAEAAAAAAAAAAAAAAAD///////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQaw zFOw9jvOPD4n0mBLAxUAxJ02CIbnBJNqZnjhE50mt4GffpAEQQRrF9Hy4SxCR/i8 5uVjpEDydwN9gS3rM6D0oTlF2JjClk/jQuL+Gn+bjufrSnwPnhYrzjNXazFezsu2 QGg3v1H1AiEA/////wAAAAD//////////7zm+q2nF56E87nKwvxjJVECAQEDQgAE EWluurrkZECnq27UpNauq16f9+5DDMFJZ3HV43Ujc3tcXQ++N1T/0CAA8ve286f3 2s7rkqX/pPokI/HBpP5p3qOBpzCBpDAdBgNVHQ4EFgQUAF43lnAvCztZZGaGMoxs cp6tpz8wdQYDVR0jBG4wbIAUAF43lnAvCztZZGaGMoxscp6tpz+hSaRHMEUxCzAJ BgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l dCBXaWRnaXRzIFB0eSBMdGSCCQDbjzQVsKesMzAMBgNVHRMEBTADAQH/MAkGByqG SM49BAEDRwAwRAIgV039oh2RtcSwywQ/0dWAwc20NHxrgmKoQ5A3AS5A9d0CIBCV 2AlKDFjmDC7zjldNhWbMcIlSSj71ghhhxeS0F8v1 -----END CERTIFICATE----- ================================================ FILE: test/ecdsa-public.pem ================================================ -----BEGIN PUBLIC KEY----- MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA//// ///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5 RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA //////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABBFpbrq65GRAp6tu1KTWrqte n/fuQwzBSWdx1eN1I3N7XF0PvjdU/9AgAPL3tvOn99rO65Kl/6T6JCPxwaT+ad4= -----END PUBLIC KEY----- ================================================ FILE: test/encoding.tests.js ================================================ var jwt = require('../index'); var expect = require('chai').expect; var atob = require('atob'); describe('encoding', function() { function b64_to_utf8 (str) { return decodeURIComponent(escape(atob( str ))); } it('should properly encode the token (utf8)', function () { var expected = 'José'; var token = jwt.sign({ name: expected }, 'shhhhh'); var decoded_name = JSON.parse(b64_to_utf8(token.split('.')[1])).name; expect(decoded_name).to.equal(expected); }); it('should properly encode the token (binary)', function () { var expected = 'José'; var token = jwt.sign({ name: expected }, 'shhhhh', { encoding: 'binary' }); var decoded_name = JSON.parse(atob(token.split('.')[1])).name; expect(decoded_name).to.equal(expected); }); it('should return the same result when decoding', function () { var username = '測試'; var token = jwt.sign({ username: username }, 'test'); var payload = jwt.verify(token, 'test'); expect(payload.username).to.equal(username); }); }); ================================================ FILE: test/expires_format.tests.js ================================================ var jwt = require('../index'); var expect = require('chai').expect; describe('expires option', function() { it('should throw on deprecated expiresInSeconds option', function () { expect(function () { jwt.sign({foo: 123}, '123', { expiresInSeconds: 5 }); }).to.throw('"expiresInSeconds" is not allowed'); }); }); ================================================ FILE: test/header-kid.test.js ================================================ 'use strict'; const jwt = require('../'); const expect = require('chai').expect; const util = require('util'); const testUtils = require('./test-utils'); function signWithKeyId(keyid, payload, callback) { const options = {algorithm: 'HS256'}; if (keyid !== undefined) { options.keyid = keyid; } testUtils.signJWTHelper(payload, 'secret', options, callback); } describe('keyid', function() { describe('`jwt.sign` "keyid" option validation', function () { [ true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, [], ['foo'], {}, {foo: 'bar'}, ].forEach((keyid) => { it(`should error with with value ${util.inspect(keyid)}`, function (done) { signWithKeyId(keyid, {}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', '"keyid" must be a string'); }); }); }); }); // undefined needs special treatment because {} is not the same as {keyid: undefined} it('should error with with value undefined', function (done) { testUtils.signJWTHelper({}, 'secret', {keyid: undefined, algorithm: 'HS256'}, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(Error); expect(err).to.have.property('message', '"keyid" must be a string'); }); }); }); }); describe('when signing a token', function () { it('should not add "kid" header when "keyid" option not provided', function(done) { signWithKeyId(undefined, {}, (err, token) => { testUtils.asyncCheck(done, () => { const decoded = jwt.decode(token, {complete: true}); expect(err).to.be.null; expect(decoded.header).to.not.have.property('kid'); }); }); }); it('should add "kid" header when "keyid" option is provided and an object payload', function(done) { signWithKeyId('foo', {}, (err, token) => { testUtils.asyncCheck(done, () => { const decoded = jwt.decode(token, {complete: true}); expect(err).to.be.null; expect(decoded.header).to.have.property('kid', 'foo'); }); }); }); it('should add "kid" header when "keyid" option is provided and a Buffer payload', function(done) { signWithKeyId('foo', new Buffer('a Buffer payload'), (err, token) => { testUtils.asyncCheck(done, () => { const decoded = jwt.decode(token, {complete: true}); expect(err).to.be.null; expect(decoded.header).to.have.property('kid', 'foo'); }); }); }); it('should add "kid" header when "keyid" option is provided and a string payload', function(done) { signWithKeyId('foo', 'a string payload', (err, token) => { testUtils.asyncCheck(done, () => { const decoded = jwt.decode(token, {complete: true}); expect(err).to.be.null; expect(decoded.header).to.have.property('kid', 'foo'); }); }); }); }); }); ================================================ FILE: test/invalid_exp.tests.js ================================================ var jwt = require('../index'); var expect = require('chai').expect; describe('invalid expiration', function() { it('should fail with string', function (done) { var broken_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxMjMiLCJmb28iOiJhZGFzIn0.cDa81le-pnwJMcJi3o3PBwB7cTJMiXCkizIhxbXAKRg'; jwt.verify(broken_token, '123', function (err) { expect(err.name).to.equal('JsonWebTokenError'); done(); }); }); it('should fail with 0', function (done) { var broken_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjAsImZvbyI6ImFkYXMifQ.UKxix5T79WwfqAA0fLZr6UrhU-jMES2unwCOFa4grEA'; jwt.verify(broken_token, '123', function (err) { expect(err.name).to.equal('TokenExpiredError'); done(); }); }); it('should fail with false', function (done) { var broken_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOmZhbHNlLCJmb28iOiJhZGFzIn0.iBn33Plwhp-ZFXqppCd8YtED77dwWU0h68QS_nEQL8I'; jwt.verify(broken_token, '123', function (err) { expect(err.name).to.equal('JsonWebTokenError'); done(); }); }); it('should fail with true', function (done) { var broken_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOnRydWUsImZvbyI6ImFkYXMifQ.eOWfZCTM5CNYHAKSdFzzk2tDkPQmRT17yqllO-ItIMM'; jwt.verify(broken_token, '123', function (err) { expect(err.name).to.equal('JsonWebTokenError'); done(); }); }); it('should fail with object', function (done) { var broken_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOnt9LCJmb28iOiJhZGFzIn0.1JjCTsWLJ2DF-CfESjLdLfKutUt3Ji9cC7ESlcoBHSY'; jwt.verify(broken_token, '123', function (err) { expect(err.name).to.equal('JsonWebTokenError'); done(); }); }); }); ================================================ FILE: test/invalid_pub.pem ================================================ -----BEGIN CERTIFICATE----- MIIDJjCCAg6gAwIBAgIJAMyz3mSPlaW4MA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV BAMUCyouYXV0aDAuY29tMB4XDTEzMDQxODE3MDE1MFoXDTI2MTIyNjE3MDE1MFow FjEUMBIGA1UEAxQLKi5hdXRoMC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDZq1Ua0/BGm+TaBFoftKWeYMWrQG9Fx3g7ikErxljmyOvlwqkiat3q ixX+Dxw9TFb5gbBjNJ+L3nt4YefJgLsYvsHqkOUxWsB+HM/ulJRVnVrZm1tI3Nbg xO1BQ7DrGfBpq2KCxtQCaQFRlQJw1+qS5LwrdIvihB7Kc142VElCFFHJ6+09eMUy jy00Z5pfQr4Am6W6eEOS9ObDbNs4XgKOcWe5khWXj3UStou+VgbAg40XcYht2IbY gMfKF+VUZOy3+e+aRTqPOBU3MAeb0tvCCPUQJbNAUHgSKVhAvNf8mRwttVsOLT70 anjjeCOd7RKS8fVKBwc2KtgNkghYdPY9AgMBAAGjdzB1MB0GA1UdDgQWBBSi4+X0 +MvCKDdd375mDhx/ZBbJ4DBGBgNVHSMEPzA9gBSi4+X0+MvCKDdd375mDhx/ZBbJ 4KEapBgwFjEUMBIGA1UEAxQLKi5hdXRoMC5jb22CCQDMs95kj5WluDAMBgNVHRME BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBi0qPe0DzlPSufq+Gdk2Fwf1pGEtjA D34IxxJ9SX6r1DS/NIP7IOLUnNU8cP8BQWl7i413v29jJsNV457pjdmqf8J7OE9O eF5Yz1x91gY/27561Iga/TQeIVOlFQAgx66eLfUFFoAig3hz2srZo5TzYBixMJsS fYMXHPiU7KoLUqYXvpSXIllstQCu51KCC6t9H7wZ92lTES1v76hFY4edQ30sftPo kjAYWGEhMjPo/r4THcdSMqKXoRtCGEun4pTXid7MJcTgdGDrAJddLWi6SxKecEVB MhMu4XfUCdxCwqQPjHeJ+zE49A1CUdBB2FN3BNLbmTTwEBgmuwyGRzhj -----END CERTIFICATE----- ================================================ FILE: test/issue_147.tests.js ================================================ var jwt = require('../index'); var expect = require('chai').expect; describe('issue 147 - signing with a sealed payload', function() { it('should put the expiration claim', function () { var token = jwt.sign(Object.seal({foo: 123}), '123', { expiresIn: 10 }); var result = jwt.verify(token, '123'); expect(result.exp).to.be.closeTo(Math.floor(Date.now() / 1000) + 10, 0.2); }); }); ================================================ FILE: test/issue_304.tests.js ================================================ var jwt = require('../index'); var expect = require('chai').expect; describe('issue 304 - verifying values other than strings', function() { it('should fail with numbers', function (done) { jwt.verify(123, 'foo', function (err) { expect(err.name).to.equal('JsonWebTokenError'); done(); }); }); it('should fail with objects', function (done) { jwt.verify({ foo: 'bar' }, 'biz', function (err) { expect(err.name).to.equal('JsonWebTokenError'); done(); }); }); it('should fail with arrays', function (done) { jwt.verify(['foo'], 'bar', function (err) { expect(err.name).to.equal('JsonWebTokenError'); done(); }); }); it('should fail with functions', function (done) { jwt.verify(function() {}, 'foo', function (err) { expect(err.name).to.equal('JsonWebTokenError'); done(); }); }); it('should fail with booleans', function (done) { jwt.verify(true, 'foo', function (err) { expect(err.name).to.equal('JsonWebTokenError'); done(); }); }); }); ================================================ FILE: test/issue_70.tests.js ================================================ var jwt = require('../'); describe('issue 70 - public key start with BEING PUBLIC KEY', function () { it('should work', function (done) { var fs = require('fs'); var cert_pub = fs.readFileSync(__dirname + '/rsa-public.pem'); var cert_priv = fs.readFileSync(__dirname + '/rsa-private.pem'); var token = jwt.sign({ foo: 'bar' }, cert_priv, { algorithm: 'RS256'}); jwt.verify(token, cert_pub, done); }); }); ================================================ FILE: test/jwt.asymmetric_signing.tests.js ================================================ const jwt = require('../index'); const PS_SUPPORTED = require('../lib/psSupported'); const fs = require('fs'); const path = require('path'); const expect = require('chai').expect; const assert = require('chai').assert; const ms = require('ms'); function loadKey(filename) { return fs.readFileSync(path.join(__dirname, filename)); } const algorithms = { RS256: { pub_key: loadKey('pub.pem'), priv_key: loadKey('priv.pem'), invalid_pub_key: loadKey('invalid_pub.pem') }, ES256: { // openssl ecparam -name secp256r1 -genkey -param_enc explicit -out ecdsa-private.pem priv_key: loadKey('ecdsa-private.pem'), // openssl ec -in ecdsa-private.pem -pubout -out ecdsa-public.pem pub_key: loadKey('ecdsa-public.pem'), invalid_pub_key: loadKey('ecdsa-public-invalid.pem') } }; if (PS_SUPPORTED) { algorithms.PS256 = { pub_key: loadKey('pub.pem'), priv_key: loadKey('priv.pem'), invalid_pub_key: loadKey('invalid_pub.pem') }; } describe('Asymmetric Algorithms', function() { Object.keys(algorithms).forEach(function (algorithm) { describe(algorithm, function () { const pub = algorithms[algorithm].pub_key; const priv = algorithms[algorithm].priv_key; // "invalid" means it is not the public key for the loaded "priv" key const invalid_pub = algorithms[algorithm].invalid_pub_key; describe('when signing a token', function () { const token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm }); it('should be syntactically valid', function () { expect(token).to.be.a('string'); expect(token.split('.')).to.have.length(3); }); context('asynchronous', function () { it('should validate with public key', function (done) { jwt.verify(token, pub, function (err, decoded) { assert.ok(decoded.foo); assert.equal('bar', decoded.foo); done(); }); }); it('should throw with invalid public key', function (done) { jwt.verify(token, invalid_pub, function (err, decoded) { assert.isUndefined(decoded); assert.isNotNull(err); done(); }); }); }); context('synchronous', function () { it('should validate with public key', function () { const decoded = jwt.verify(token, pub); assert.ok(decoded.foo); assert.equal('bar', decoded.foo); }); it('should throw with invalid public key', function () { const jwtVerify = jwt.verify.bind(null, token, invalid_pub) assert.throw(jwtVerify, 'invalid signature'); }); }); }); describe('when signing a token with expiration', function () { it('should be valid expiration', function (done) { const token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, expiresIn: '10m' }); jwt.verify(token, pub, function (err, decoded) { assert.isNotNull(decoded); assert.isNull(err); done(); }); }); it('should be invalid', function (done) { // expired token const token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, expiresIn: -1 * ms('10m') }); jwt.verify(token, pub, function (err, decoded) { assert.isUndefined(decoded); assert.isNotNull(err); assert.equal(err.name, 'TokenExpiredError'); assert.instanceOf(err.expiredAt, Date); assert.instanceOf(err, jwt.TokenExpiredError); done(); }); }); it('should NOT be invalid', function (done) { // expired token const token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm, expiresIn: -1 * ms('10m') }); jwt.verify(token, pub, { ignoreExpiration: true }, function (err, decoded) { assert.ok(decoded.foo); assert.equal('bar', decoded.foo); done(); }); }); }); describe('when verifying a malformed token', function () { it('should throw', function (done) { jwt.verify('fruit.fruit.fruit', pub, function (err, decoded) { assert.isUndefined(decoded); assert.isNotNull(err); assert.equal(err.name, 'JsonWebTokenError'); done(); }); }); }); describe('when decoding a jwt token with additional parts', function () { const token = jwt.sign({ foo: 'bar' }, priv, { algorithm: algorithm }); it('should throw', function (done) { jwt.verify(token + '.foo', pub, function (err, decoded) { assert.isUndefined(decoded); assert.isNotNull(err); done(); }); }); }); describe('when decoding a invalid jwt token', function () { it('should return null', function (done) { const payload = jwt.decode('whatever.token'); assert.isNull(payload); done(); }); }); describe('when decoding a valid jwt token', function () { it('should return the payload', function (done) { const obj = { foo: 'bar' }; const token = jwt.sign(obj, priv, { algorithm: algorithm }); const payload = jwt.decode(token); assert.equal(payload.foo, obj.foo); done(); }); it('should return the header and payload and signature if complete option is set', function (done) { const obj = { foo: 'bar' }; const token = jwt.sign(obj, priv, { algorithm: algorithm }); const decoded = jwt.decode(token, { complete: true }); assert.equal(decoded.payload.foo, obj.foo); assert.deepEqual(decoded.header, { typ: 'JWT', alg: algorithm }); assert.ok(typeof decoded.signature == 'string'); done(); }); }); }); }); describe('when signing a token with an unsupported private key type', function () { it('should throw an error', function() { const obj = { foo: 'bar' }; const key = loadKey('dsa-private.pem'); const algorithm = 'RS256'; expect(function() { jwt.sign(obj, key, { algorithm }); }).to.throw('Unknown key type "dsa".'); }); }); describe('when signing a token with an incorrect private key type', function () { it('should throw a validation error if key validation is enabled', function() { const obj = { foo: 'bar' }; const key = loadKey('rsa-private.pem'); const algorithm = 'ES256'; expect(function() { jwt.sign(obj, key, { algorithm }); }).to.throw(/"alg" parameter for "rsa" key type must be one of:/); }); it('should throw an unknown error if key validation is disabled', function() { const obj = { foo: 'bar' }; const key = loadKey('rsa-private.pem'); const algorithm = 'ES256'; expect(function() { jwt.sign(obj, key, { algorithm, allowInvalidAsymmetricKeyTypes: true }); }).to.not.throw(/"alg" parameter for "rsa" key type must be one of:/); }); }); }); ================================================ FILE: test/jwt.hs.tests.js ================================================ const jwt = require('../index'); const jws = require('jws'); const expect = require('chai').expect; const assert = require('chai').assert; const { generateKeyPairSync } = require('crypto') describe('HS256', function() { describe("when signing using HS256", function () { it('should throw if the secret is an asymmetric key', function () { const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 }); expect(function () { jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'HS256' }) }).to.throw(Error, 'must be a symmetric key') }) it('should throw if the payload is undefined', function () { expect(function () { jwt.sign(undefined, "secret", { algorithm: 'HS256' }) }).to.throw(Error, 'payload is required') }) it('should throw if options is not a plain object', function () { expect(function () { jwt.sign({ foo: 'bar' }, "secret", ['HS256']) }).to.throw(Error, 'Expected "options" to be a plain object') }) }) describe('with a token signed using HS256', function() { var secret = 'shhhhhh'; var token = jwt.sign({ foo: 'bar' }, secret, { algorithm: 'HS256' }); it('should be syntactically valid', function() { expect(token).to.be.a('string'); expect(token.split('.')).to.have.length(3); }); it('should be able to validate without options', function(done) { var callback = function(err, decoded) { assert.ok(decoded.foo); assert.equal('bar', decoded.foo); done(); }; callback.issuer = "shouldn't affect"; jwt.verify(token, secret, callback ); }); it('should validate with secret', function(done) { jwt.verify(token, secret, function(err, decoded) { assert.ok(decoded.foo); assert.equal('bar', decoded.foo); done(); }); }); it('should throw with invalid secret', function(done) { jwt.verify(token, 'invalid secret', function(err, decoded) { assert.isUndefined(decoded); assert.isNotNull(err); done(); }); }); it('should throw with secret and token not signed', function(done) { const header = { alg: 'none' }; const payload = { foo: 'bar' }; const token = jws.sign({ header, payload, secret: 'secret', encoding: 'utf8' }); jwt.verify(token, 'secret', function(err, decoded) { assert.isUndefined(decoded); assert.isNotNull(err); done(); }); }); it('should throw with falsy secret and token not signed', function(done) { const header = { alg: 'none' }; const payload = { foo: 'bar' }; const token = jws.sign({ header, payload, secret: null, encoding: 'utf8' }); jwt.verify(token, 'secret', function(err, decoded) { assert.isUndefined(decoded); assert.isNotNull(err); done(); }); }); it('should throw when verifying null', function(done) { jwt.verify(null, 'secret', function(err, decoded) { assert.isUndefined(decoded); assert.isNotNull(err); done(); }); }); it('should return an error when the token is expired', function(done) { var token = jwt.sign({ exp: 1 }, secret, { algorithm: 'HS256' }); jwt.verify(token, secret, { algorithm: 'HS256' }, function(err, decoded) { assert.isUndefined(decoded); assert.isNotNull(err); done(); }); }); it('should NOT return an error when the token is expired with "ignoreExpiration"', function(done) { var token = jwt.sign({ exp: 1, foo: 'bar' }, secret, { algorithm: 'HS256' }); jwt.verify(token, secret, { algorithm: 'HS256', ignoreExpiration: true }, function(err, decoded) { assert.ok(decoded.foo); assert.equal('bar', decoded.foo); assert.isNull(err); done(); }); }); it('should default to HS256 algorithm when no options are passed', function() { var token = jwt.sign({ foo: 'bar' }, secret); var verifiedToken = jwt.verify(token, secret); assert.ok(verifiedToken.foo); assert.equal('bar', verifiedToken.foo); }); }); describe('should fail verification gracefully with trailing space in the jwt', function() { var secret = 'shhhhhh'; var token = jwt.sign({ foo: 'bar' }, secret, { algorithm: 'HS256' }); it('should return the "invalid token" error', function(done) { var malformedToken = token + ' '; // corrupt the token by adding a space jwt.verify(malformedToken, secret, { algorithm: 'HS256', ignoreExpiration: true }, function(err) { assert.isNotNull(err); assert.equal('JsonWebTokenError', err.name); assert.equal('invalid token', err.message); done(); }); }); }); }); ================================================ FILE: test/jwt.malicious.tests.js ================================================ const jwt = require('../index'); const crypto = require("crypto"); const {expect} = require('chai'); const JsonWebTokenError = require("../lib/JsonWebTokenError"); describe('when verifying a malicious token', function () { // attacker has access to the public rsa key, but crafts the token as HS256 // with kid set to the id of the rsa key, instead of the id of the hmac secret. // const maliciousToken = jwt.sign( // {foo: 'bar'}, // pubRsaKey, // {algorithm: 'HS256', keyid: 'rsaKeyId'} // ); // consumer accepts self signed tokens (HS256) and third party tokens (RS256) const options = {algorithms: ['RS256', 'HS256']}; const { publicKey: pubRsaKey } = crypto.generateKeyPairSync('rsa', {modulusLength: 2048}); it('should not allow HMAC verification with an RSA key in KeyObject format', function () { const maliciousToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InJzYUtleUlkIn0.eyJmb28iOiJiYXIiLCJpYXQiOjE2NTk1MTA2MDh9.cOcHI1TXPbxTMlyVTfjArSWskrmezbrG8iR7uJHwtrQ'; expect(() => jwt.verify(maliciousToken, pubRsaKey, options)).to.throw(JsonWebTokenError, 'must be a symmetric key'); }) it('should not allow HMAC verification with an RSA key in PEM format', function () { const maliciousToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InJzYUtleUlkIn0.eyJmb28iOiJiYXIiLCJpYXQiOjE2NTk1MTA2MDh9.cOcHI1TXPbxTMlyVTfjArSWskrmezbrG8iR7uJHwtrQ'; expect(() => jwt.verify(maliciousToken, pubRsaKey.export({type: 'spki', format: 'pem'}), options)).to.throw(JsonWebTokenError, 'must be a symmetric key'); }) it('should not allow arbitrary execution from malicious Buffers containing objects with overridden toString functions', function () { const token = jwt.sign({"foo": "bar"}, 'secret') const maliciousBuffer = {toString: () => {throw new Error("Arbitrary Code Execution")}} expect(() => jwt.verify(token, maliciousBuffer)).to.throw(Error, 'not valid key material'); }) }) ================================================ FILE: test/noTimestamp.tests.js ================================================ var jwt = require('../index'); var expect = require('chai').expect; describe('noTimestamp', function() { it('should work with string', function () { var token = jwt.sign({foo: 123}, '123', { expiresIn: '5m' , noTimestamp: true }); var result = jwt.verify(token, '123'); expect(result.exp).to.be.closeTo(Math.floor(Date.now() / 1000) + (5*60), 0.5); }); }); ================================================ FILE: test/non_object_values.tests.js ================================================ var jwt = require('../index'); var expect = require('chai').expect; describe('non_object_values values', function() { it('should work with string', function () { var token = jwt.sign('hello', '123'); var result = jwt.verify(token, '123'); expect(result).to.equal('hello'); }); it('should work with number', function () { var token = jwt.sign(123, '123'); var result = jwt.verify(token, '123'); expect(result).to.equal('123'); }); }); ================================================ FILE: test/option-complete.test.js ================================================ 'use strict'; const jws = require('jws'); const expect = require('chai').expect; const path = require('path'); const fs = require('fs'); const testUtils = require('./test-utils') describe('complete option', function () { const secret = fs.readFileSync(path.join(__dirname, 'priv.pem')); const pub = fs.readFileSync(path.join(__dirname, 'pub.pem')); const header = { alg: 'RS256' }; const payload = { iat: Math.floor(Date.now() / 1000 ) }; const signed = jws.sign({ header, payload, secret, encoding: 'utf8' }); const signature = jws.decode(signed).signature; [ { description: 'should return header, payload and signature', complete: true, }, ].forEach((testCase) => { it(testCase.description, function (done) { testUtils.verifyJWTHelper(signed, pub, { typ: 'JWT', complete: testCase.complete }, (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded.header).to.have.property('alg', header.alg); expect(decoded.payload).to.have.property('iat', payload.iat); expect(decoded).to.have.property('signature', signature); }); }); }); }); [ { description: 'should return payload', complete: false, }, ].forEach((testCase) => { it(testCase.description, function (done) { testUtils.verifyJWTHelper(signed, pub, { typ: 'JWT', complete: testCase.complete }, (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded.header).to.be.undefined; expect(decoded.payload).to.be.undefined; expect(decoded.signature).to.be.undefined; expect(decoded).to.have.property('iat', payload.iat); }); }); }); }); }); ================================================ FILE: test/option-maxAge.test.js ================================================ 'use strict'; const jwt = require('../'); const expect = require('chai').expect; const sinon = require('sinon'); const util = require('util'); describe('maxAge option', function() { let token; let fakeClock; beforeEach(function() { fakeClock = sinon.useFakeTimers({now: 60000}); token = jwt.sign({iat: 70}, 'secret', {algorithm: 'HS256'}); }); afterEach(function() { fakeClock.uninstall(); }); [ { description: 'should work with a positive string value', maxAge: '3s', }, { description: 'should work with a negative string value', maxAge: '-3s', }, { description: 'should work with a positive numeric value', maxAge: 3, }, { description: 'should work with a negative numeric value', maxAge: -3, }, ].forEach((testCase) => { it(testCase.description, function (done) { expect(jwt.verify(token, 'secret', {maxAge: '3s', algorithm: 'HS256'})).to.not.throw; jwt.verify(token, 'secret', {maxAge: testCase.maxAge, algorithm: 'HS256'}, (err) => { expect(err).to.be.null; done(); }) }); }); [ true, 'invalid', [], ['foo'], {}, {foo: 'bar'}, ].forEach((maxAge) => { it(`should error with value ${util.inspect(maxAge)}`, function (done) { expect(() => jwt.verify(token, 'secret', {maxAge, algorithm: 'HS256'})).to.throw( jwt.JsonWebTokenError, '"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60' ); jwt.verify(token, 'secret', {maxAge, algorithm: 'HS256'}, (err) => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err.message).to.equal( '"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60' ); done(); }) }); }); }); ================================================ FILE: test/option-nonce.test.js ================================================ 'use strict'; const jwt = require('../'); const expect = require('chai').expect; const util = require('util'); const testUtils = require('./test-utils') describe('nonce option', function () { let token; beforeEach(function () { token = jwt.sign({ nonce: 'abcde' }, 'secret', { algorithm: 'HS256' }); }); [ { description: 'should work with a string', nonce: 'abcde', }, ].forEach((testCase) => { it(testCase.description, function (done) { testUtils.verifyJWTHelper(token, 'secret', { nonce: testCase.nonce }, (err, decoded) => { testUtils.asyncCheck(done, () => { expect(err).to.be.null; expect(decoded).to.have.property('nonce', 'abcde'); }); }); }); }); [ true, false, null, -1, 0, 1, -1.1, 1.1, -Infinity, Infinity, NaN, '', ' ', [], ['foo'], {}, { foo: 'bar' }, ].forEach((nonce) => { it(`should error with value ${util.inspect(nonce)}`, function (done) { testUtils.verifyJWTHelper(token, 'secret', { nonce }, (err) => { testUtils.asyncCheck(done, () => { expect(err).to.be.instanceOf(jwt.JsonWebTokenError); expect(err).to.have.property('message', 'nonce must be a non-empty string') }); }); }); }); }); ================================================ FILE: test/prime256v1-private.pem ================================================ -----BEGIN EC PRIVATE KEY----- MHcCAQEEIMP1Xt/ic2jAHJva2Pll866d1jYL+dk3VdLytEU1+LFmoAoGCCqGSM49 AwEHoUQDQgAEvIywoA1H1a2XpPPTqsRxSk6YnNRVsu4E+wTvb7uV6Yttvko9zWar jmtM3LHDXk/nHn+Pva0KD+lby8gb2daHGg== -----END EC PRIVATE KEY----- ================================================ FILE: test/priv.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAvtH4wKLYlIXZlfYQFJtXZVC3fD8XMarzwvb/fHUyJ6NvNStN +H7GHp3/QhZbSaRyqK5hu5xXtFLgnI0QG8oE1NlXbczjH45LeHWhPIdc2uHSpzXi c78kOugMY1vng4J10PF6+T2FNaiv0iXeIQq9xbwwPYpflViQyJnzGCIZ7VGan6Gb RKzyTKcB58yx24pJq+CviLXEY52TIW1l5imcjGvLtlCp1za9qBZa4XGoVqHi1kRX kdDSHty6lZWj3KxoRvTbiaBCH+75U7rifS6fR9lqjWE57bCGoz7+BBu9YmPKtI1K kyHFqWpxaJc/AKf9xgg+UumeqVcirUmAsHJrMwIDAQABAoIBAQCYKw05YSNhXVPk eHLeW/pXuwR3OkCexPrakOmwMC0s2vIF7mChN0d6hvhVlUp68X7V8SnS2JxAGo8v iHY+Et3DdwZ3cxnzwh+BEhzgDfoIOmkoGppZPyX/K6klWtbGUrTtSISOWXbvEXQU G0qGAvDOzIGTsdMDX7slnU70Ac23JybPY5qBSiE+ky8U4dm2fUHMroWub4QP5vA/ nqyWqX2FB/MEAbcujaknDQrFCtbmtUYlBbJCKGd9V3cGEqp6H7oH+ah2ofMc91gJ mCHk3YyWZB/bcVXH3CA+s1ywvCOVDBZ3Nw7Pt9zIcv6Rl9UKIy+Nx0QjXxR90Hla Tr0GHIShAoGBAPsD7uXm+0ksnGyKRYgvlVad8Z8FUFT6bf4B+vboDbx40FO8O/5V PraBPC5z8YRSBOQ/WfccPQzakkA28F2pXlRpXu5JcErVWnyyUiKpX5sw6iPenQR2 JO9hY/GFbKiwUhVHpvWMcXFqFLSQu2A86jPnFFEfG48ZT4IhTzINKJVZAoGBAMKc B3YGfVfY9qiRFXzYRdSRLg5c8p/HzuWwXc9vfJ4kQTDkPXe/+nqD67rzeT54uVec jKoIrsCu4BfEaoyvOT+1KmUfdEpBgYZuuEC4CZf7dgKbXOpPVvZDMyJ/e7HyqTpw mvIYJLPm2fNAcAsnbrNX5mhLwwzEIltbplUUeRdrAoGBAKhZgPYsLkhrZRXevreR wkTvdUfD1pbHxtFfHqROCjhnhsFCM7JmFcNtdaFqHYczQxiZ7IqxI7jlNsVek2Md 3qgaa5LBKlDmOuP67N9WXUrGSaJ5ATIm0qrB1Lf9VlzktIiVH8L7yHHaRby8fQ8U i7b3ukaV6HPW895A3M6iyJ8xAoGAInp4S+3MaTL0SFsj/nFmtcle6oaHKc3BlyoP BMBQyMfNkPbu+PdXTjtvGTknouzKkX4X4cwWAec5ppxS8EffEa1sLGxNMxa19vZI yJaShI21k7Ko3I5f7tNrDNKfPKCsYMEwgnHKluDwfktNTnyW/Uk2dgXuMaXSHHN5 XZt59K8CgYArGVOWK7LUmf3dkTIs3tXBm4/IMtUZmWmcP9C8Xe/Dg/IdQhK5CIx4 VXl8rgZNeX/5/4nJ8Q3LrdLau1Iz620trNRGU6sGMs3x4WQbSq93RRbFzfG1oK74 IOo5yIBxImQOSk5jz31gF9RJb15SDBIxonuWv8qAERyUfvrmEwR0kg== -----END RSA PRIVATE KEY----- ================================================ FILE: test/pub.pem ================================================ -----BEGIN CERTIFICATE----- MIIDtTCCAp2gAwIBAgIJAMKR/NsyfcazMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQwHhcNMTIxMTEyMjM0MzQxWhcNMTYxMjIxMjM0MzQxWjBF MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAvtH4wKLYlIXZlfYQFJtXZVC3fD8XMarzwvb/fHUyJ6NvNStN+H7GHp3/ QhZbSaRyqK5hu5xXtFLgnI0QG8oE1NlXbczjH45LeHWhPIdc2uHSpzXic78kOugM Y1vng4J10PF6+T2FNaiv0iXeIQq9xbwwPYpflViQyJnzGCIZ7VGan6GbRKzyTKcB 58yx24pJq+CviLXEY52TIW1l5imcjGvLtlCp1za9qBZa4XGoVqHi1kRXkdDSHty6 lZWj3KxoRvTbiaBCH+75U7rifS6fR9lqjWE57bCGoz7+BBu9YmPKtI1KkyHFqWpx aJc/AKf9xgg+UumeqVcirUmAsHJrMwIDAQABo4GnMIGkMB0GA1UdDgQWBBTs83nk LtoXFlmBUts3EIxcVvkvcjB1BgNVHSMEbjBsgBTs83nkLtoXFlmBUts3EIxcVvkv cqFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAMKR/NsyfcazMAwGA1UdEwQF MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBABw7w/5k4d5dVDgd/OOOmXdaaCIKvt7d 3ntlv1SSvAoKT8d8lt97Dm5RrmefBI13I2yivZg5bfTge4+vAV6VdLFdWeFp1b/F OZkYUv6A8o5HW0OWQYVX26zIqBcG2Qrm3reiSl5BLvpj1WSpCsYvs5kaO4vFpMak /ICgdZD+rxwxf8Vb/6fntKywWSLgwKH3mJ+Z0kRlpq1g1oieiOm1/gpZ35s0Yuor XZba9ptfLCYSggg/qc3d3d0tbHplKYkwFm7f5ORGHDSD5SJm+gI7RPE+4bO8q79R PAfbG1UGuJ0b/oigagciHhJp851SQRYf3JuNSc17BnK2L5IEtzjqr+Q= -----END CERTIFICATE----- ================================================ FILE: test/rsa-private.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAvzoCEC2rpSpJQaWZbUmlsDNwp83Jr4fi6KmBWIwnj1MZ6CUQ 7rBasuLI8AcfX5/10scSfQNCsTLV2tMKQaHuvyrVfwY0dINk+nkqB74QcT2oCCH9 XduJjDuwWA4xLqAKuF96FsIes52opEM50W7/W7DZCKXkC8fFPFj6QF5ZzApDw2Qs u3yMRmr7/W9uWeaTwfPx24YdY7Ah+fdLy3KN40vXv9c4xiSafVvnx9BwYL7H1Q8N iK9LGEN6+JSWfgckQCs6UUBOXSZdreNN9zbQCwyzee7bOJqXUDAuLcFARzPw1EsZ AyjVtGCKIQ0/btqK+jFunT2NBC8RItanDZpptQIDAQABAoIBAQCsssO4Pra8hFMC gX7tr0x+tAYy1ewmpW8stiDFilYT33YPLKJ9HjHbSms0MwqHftwwTm8JDc/GXmW6 qUui+I64gQOtIzpuW1fvyUtHEMSisI83QRMkF6fCSQm6jJ6oQAtOdZO6R/gYOPNb 3gayeS8PbMilQcSRSwp6tNTVGyC33p43uUUKAKHnpvAwUSc61aVOtw2wkD062XzM hJjYpHm65i4V31AzXo8HF42NrAtZ8K/AuQZne5F/6F4QFVlMKzUoHkSUnTp60XZx X77GuyDeDmCgSc2J7xvR5o6VpjsHMo3ek0gJk5ZBnTgkHvnpbULCRxTmDfjeVPue v3NN2TBFAoGBAPxbqNEsXPOckGTvG3tUOAAkrK1hfW3TwvrW/7YXg1/6aNV4sklc vqn/40kCK0v9xJIv9FM/l0Nq+CMWcrb4sjLeGwHAa8ASfk6hKHbeiTFamA6FBkvQ //7GP5khD+y62RlWi9PmwJY21lEkn2mP99THxqvZjQiAVNiqlYdwiIc7AoGBAMH8 f2Ay7Egc2KYRYU2qwa5E/Cljn/9sdvUnWM+gOzUXpc5sBi+/SUUQT8y/rY4AUVW6 YaK7chG9YokZQq7ZwTCsYxTfxHK2pnG/tXjOxLFQKBwppQfJcFSRLbw0lMbQoZBk S+zb0ufZzxc2fJfXE+XeJxmKs0TS9ltQuJiSqCPPAoGBALEc84K7DBG+FGmCl1sb ZKJVGwwknA90zCeYtadrIT0/VkxchWSPvxE5Ep+u8gxHcqrXFTdILjWW4chefOyF 5ytkTrgQAI+xawxsdyXWUZtd5dJq8lxLtx9srD4gwjh3et8ZqtFx5kCHBCu29Fr2 PA4OmBUMfrs0tlfKgV+pT2j5AoGBAKnA0Z5XMZlxVM0OTH3wvYhI6fk2Kx8TxY2G nxsh9m3hgcD/mvJRjEaZnZto6PFoqcRBU4taSNnpRr7+kfH8sCht0k7D+l8AIutL ffx3xHv9zvvGHZqQ1nHKkaEuyjqo+5kli6N8QjWNzsFbdvBQ0CLJoqGhVHsXuWnz W3Z4cBbVAoGAEtnwY1OJM7+R2u1CW0tTjqDlYU2hUNa9t1AbhyGdI2arYp+p+umA b5VoYLNsdvZhqjVFTrYNEuhTJFYCF7jAiZLYvYm0C99BqcJnJPl7JjWynoNHNKw3 9f6PIOE1rAmPE8Cfz/GFF5115ZKVlq+2BY8EKNxbCIy2d/vMEvisnXI= -----END RSA PRIVATE KEY----- ================================================ FILE: test/rsa-pss-invalid-salt-length-private.pem ================================================ -----BEGIN PRIVATE KEY----- MIIE8gIBADBCBgkqhkiG9w0BAQowNaAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZI hvcNAQEIMA0GCWCGSAFlAwQCAQUAogQCAgQABIIEpzCCBKMCAQACggEBAJy3FuDR 1qKXsC8o+0xDJbuJCnysT71EFDGQY2/b3cZmxW3rzDYLyE65t2Go1jeK5Kxs+kwS 1VxfefD8DifeDZN66wjRse4iWLcxmQB5FfishXOdozciimgXNvXJNS8X//feSofl vDQaTUI0NJnw1qQ2CB0pgGInwajsRKpWnDOhfk3NA/cmGlmfhTtDSTxq0ReytUie TjY7gy+S9YYm4bAgBcMeoup0GEPzYccK4+1yCmWzQZGFcrY1cuB9bL+vT7ajQFhe WVKlp6z35GyBF2zI7gJSkHpUHaWV5+Z9aTr6+YP6U7xuCRvXQ/l6BEOUjt4Es2YG 3frgxeVbOs1gAakCAwEAAQKCAQAMvFxhnOwCfq1Ux9HUWsigOvzdMOuyB+xUMtXB 625Uh1mYG0eXRNHcg/9BMoVmMiVvVdPphsZMIX45dWJ5HvSffafIKbJ6FdR73s3+ WdjNQsf9o1v2SRpSZ0CSLO3ji+HDdQ89iBAJc/G/ZZq4v/fRlIqIRC0ozO5SGhFi fnNnRqH78d2KeJMX/g9jBZM8rJQCi+pb0keHmFmLJ5gZa4HokE8rWQJQY46PVYUH W2BwEJToMl3MPC7D95soWVuFt3KHnIWhuma/tnCmd2AUvcMrdWq0CwStH3vuX4LB vJug0toWkobt1tzZgzzCASb2EpzJj8UNxP1CzTQWsvl8OephAoGBAMVnmZeLHoh2 kxn/+rXetZ4Msjgu19MHNQAtlMvqzwZLan0K/BhnHprJLy4SDOuQYIs+PYJuXdT7 Yv2mp9kwTPz8glP9LAto4MDeDfCu0cyXmZb2VQcT/lqVyrwfx3Psqxm/Yxg62YKr aQE8WqgZGUdOvU9dYU+7EmPlYpdGpPVlAoGBAMs7ks+12oE6kci3WApdnt0kk5+f 8fbQ0lp2vR3tEw8DURa5FnHWA4o46XvcMcuXwZBrpxANPNAxJJjMBs1hSkc8h4hd 4vjtRNYJpj+uBdDIRmdqTzbpWv+hv8Xpiol5EVgnMVs2UZWDjoxQ+mYa1R8tAUfj ojzV2KBMWGCoHgj1AoGALki6JGQEBq72kpQILnhHUQVdC/s/s0TvUlldl+o4HBu2 nhbjQL182YHuQ/kLenfhiwRO27QQ4A0JCrv2gt/mTTLPQ+4KU6qFd/MYhaQXoMay xkh/aydu7cJNRIqW80E8ZM8Q5u91bEPQXO/PubYYzTVTAba9SDpud2mjEiEIMFkC gYEAxINEQEgtkkuZ76UpIkzIcjkN7YlxJCFjZUnvL+KvTRL986TgyQ4RujOxwKx4 Ec8ZwZX2opTKOt1p771IzorGkf87ZmayM9TpfLUz5dtVkD43pYOsOQKHlStIDgz2 gltoo/6xwOrTFGlzCsa6eMR1U4Hm/SZlF8IHh2iLBFtLP4kCgYBqTi1XeWeVQVSA y9Wolv9kMoRh/Xh6F2D8bTTybGshDVO+P4YLM4lLxh5UDZAd/VOkdf3ZIcUGv022 lxrYbLbIEGckMCpkdHeZH/1/iuJUeiCrXeyNlQsXBrmJKr/0lENniJHGpiSEyvY5 D8Oafyjd7ZjUmyBFvS4heQEC6Pjo3Q== -----END PRIVATE KEY----- ================================================ FILE: test/rsa-pss-private.pem ================================================ -----BEGIN PRIVATE KEY----- MIIE8QIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZI hvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAEggSnMIIEowIBAAKCAQEA00tEqqyF VnyvcVA2ewVoSicCMdQXmWyYM82sBWX0wcnn0WUuZp1zjux4xTvQ71Lhx95OJCQZ 7r7b2192Im5ca37wNRbI6DhyXNdNVFXLFYlNAvgP+V0gIwlr6NgopdJqHCjYVv/g GOoesRZaDdtV1A3O9CXdJ34x2HZh7nhwYK5hqZDhUW4rd+5GzIIzwCJfwgTQpkIc 18UeMMEoKJ6A0ixdpf43HqJ5fAB5nsbYFhyHpfiX1UO2EFJtSdbKEIbRmqcbNjG1 tu1tjt6u8LI2coetLh/IYMbMfkyQz+eAUHLQCUb2R8BqLOL3hRqEsVTBo93UJlOs VWC1fKaq+HOEWQIDAQABAoIBAAet23PagPQTjwAZcAlzjlvs5AMHQsj5gznqwSmR ut3/e7SGrrOIXbv1iIQejZQ3w8CS/0MH/ttIRiRIaWTh9EDsjvKsU9FAxUNDiJTG k3LCbTFCQ7kGiJWiu4XDCWMmwmLTRzLjlMjtr/+JS5eSVPcNKMGDI3D9K0xDLSxQ u0DVigYgWOCWlejHCEU4yi6vBO0HlumWjVPelWb9GmihBDwCLUJtG0JA6H6rw+KS i6SNXcMGVKfjEghChRp+HaMvLvMgU44Ptnj8jhlfBctXInBY1is1FfDSWxXdVbUM 1HdKXfV4A50GXSvJLiWP9ZZsaZ7NiBJK8IiJBXD72EFOzwECgYEA3RjnTJn9emzG 84eIHZQujWWt4Tk/wjeLJYOYtAZpF7R3/fYLVypX9Bsw1IbwZodq/jChTjMaUkYt //FgUjF/t0uakEg1i+THPZvktNB8Q1E9NwHerB8HF/AD/jMALD+ejdLQ11Z4VScw zyNmSvD9I84/sgpms5YVKSH9sqww2RkCgYEA9KYws3sTfRLc1hlsS25V6+Zg3ZCk iGcp+zrxGC1gb2/PpRvEDBucZO21KbSRuQDavWIOZYl4fGu7s8wo2oF8RxOsHQsM LJyjklruvtjnvuoft/bGAv2zLQkNaj+f7IgK6965gIxcLYL66UPCZZkTfL5CoJis V0v2hBh1ES5bLUECgYEAuONeaLxNL9dO989akAGefDePFExfePYhshk91S2XLG+J +CGMkjOioUsrpk3BMrwDSNU5zr8FP8/YH7OlrJYgCxN6CTWZMYb65hY7RskhYNnK qvkxUBYSRH49mJDlkBsTZ93nLmvs7Kh9NHqRzBGCXjLXKPdxsrPKtj7qfENqBeEC gYAC9dPXCCE3PTgw2wPlccNWZGY9qBdlkyH96TurmDj3gDnZ/JkFsHvW+M1dYNL2 kx0Sd5JHBj/P+Zm+1jSUWEbBsWo+u7h8/bQ4/CKxanx7YefaWQESXjGB1P81jumH einvqrVB6fDfmBsjIW/DvPNwafjyaoaDU+b6uDUKbS4rQQKBgCe0pvDl5lO8FM81 NP7GoCIu1gKBS+us1sgYE65ZFmVXJ6b5DckvobXSjM60G2N5w2xaXEXJsnwMApf1 SClQUsgNWcSXRwL+w0pIdyFKS25BSfwUNQ9n7QLJcYgmflbARTfB3He/10vbFzTp G6ZAiKUp9bKFPzviII40AEPL2hPX -----END PRIVATE KEY----- ================================================ FILE: test/rsa-public-key.pem ================================================ -----BEGIN RSA PUBLIC KEY----- MIIBCgKCAQEAvzoCEC2rpSpJQaWZbUmlsDNwp83Jr4fi6KmBWIwnj1MZ6CUQ7rBa suLI8AcfX5/10scSfQNCsTLV2tMKQaHuvyrVfwY0dINk+nkqB74QcT2oCCH9XduJ jDuwWA4xLqAKuF96FsIes52opEM50W7/W7DZCKXkC8fFPFj6QF5ZzApDw2Qsu3yM Rmr7/W9uWeaTwfPx24YdY7Ah+fdLy3KN40vXv9c4xiSafVvnx9BwYL7H1Q8NiK9L GEN6+JSWfgckQCs6UUBOXSZdreNN9zbQCwyzee7bOJqXUDAuLcFARzPw1EsZAyjV tGCKIQ0/btqK+jFunT2NBC8RItanDZpptQIDAQAB -----END RSA PUBLIC KEY----- ================================================ FILE: test/rsa-public-key.tests.js ================================================ const jwt = require('../'); const PS_SUPPORTED = require('../lib/psSupported'); const expect = require('chai').expect; const {generateKeyPairSync} = require('crypto') describe('public key start with BEGIN RSA PUBLIC KEY', function () { it('should work for RS family of algorithms', function (done) { var fs = require('fs'); var cert_pub = fs.readFileSync(__dirname + '/rsa-public-key.pem'); var cert_priv = fs.readFileSync(__dirname + '/rsa-private.pem'); var token = jwt.sign({ foo: 'bar' }, cert_priv, { algorithm: 'RS256'}); jwt.verify(token, cert_pub, done); }); it('should not work for RS algorithms when modulus length is less than 2048 when allowInsecureKeySizes is false or not set', function (done) { const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 1024 }); expect(function() { jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256'}) }).to.throw(Error, 'minimum key size'); done() }); it('should work for RS algorithms when modulus length is less than 2048 when allowInsecureKeySizes is true', function (done) { const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 1024 }); jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256', allowInsecureKeySizes: true}, done) }); if (PS_SUPPORTED) { it('should work for PS family of algorithms', function (done) { var fs = require('fs'); var cert_pub = fs.readFileSync(__dirname + '/rsa-public-key.pem'); var cert_priv = fs.readFileSync(__dirname + '/rsa-private.pem'); var token = jwt.sign({ foo: 'bar' }, cert_priv, { algorithm: 'PS256'}); jwt.verify(token, cert_pub, done); }); } }); ================================================ FILE: test/rsa-public.pem ================================================ -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvzoCEC2rpSpJQaWZbUml sDNwp83Jr4fi6KmBWIwnj1MZ6CUQ7rBasuLI8AcfX5/10scSfQNCsTLV2tMKQaHu vyrVfwY0dINk+nkqB74QcT2oCCH9XduJjDuwWA4xLqAKuF96FsIes52opEM50W7/ W7DZCKXkC8fFPFj6QF5ZzApDw2Qsu3yMRmr7/W9uWeaTwfPx24YdY7Ah+fdLy3KN 40vXv9c4xiSafVvnx9BwYL7H1Q8NiK9LGEN6+JSWfgckQCs6UUBOXSZdreNN9zbQ Cwyzee7bOJqXUDAuLcFARzPw1EsZAyjVtGCKIQ0/btqK+jFunT2NBC8RItanDZpp tQIDAQAB -----END PUBLIC KEY----- ================================================ FILE: test/schema.tests.js ================================================ var jwt = require('../index'); var expect = require('chai').expect; var fs = require('fs'); var PS_SUPPORTED = require('../lib/psSupported'); describe('schema', function() { describe('sign options', function() { var cert_rsa_priv = fs.readFileSync(__dirname + '/rsa-private.pem'); var cert_ecdsa_priv = fs.readFileSync(__dirname + '/ecdsa-private.pem'); var cert_secp384r1_priv = fs.readFileSync(__dirname + '/secp384r1-private.pem'); var cert_secp521r1_priv = fs.readFileSync(__dirname + '/secp521r1-private.pem'); function sign(options, secretOrPrivateKey) { jwt.sign({foo: 123}, secretOrPrivateKey, options); } it('should validate algorithm', function () { expect(function () { sign({ algorithm: 'foo' }, cert_rsa_priv); }).to.throw(/"algorithm" must be a valid string enum value/); sign({ algorithm: 'none' }, null); sign({algorithm: 'RS256'}, cert_rsa_priv); sign({algorithm: 'RS384'}, cert_rsa_priv); sign({algorithm: 'RS512'}, cert_rsa_priv); if (PS_SUPPORTED) { sign({algorithm: 'PS256'}, cert_rsa_priv); sign({algorithm: 'PS384'}, cert_rsa_priv); sign({algorithm: 'PS512'}, cert_rsa_priv); } sign({algorithm: 'ES256'}, cert_ecdsa_priv); sign({algorithm: 'ES384'}, cert_secp384r1_priv); sign({algorithm: 'ES512'}, cert_secp521r1_priv); sign({algorithm: 'HS256'}, 'superSecret'); sign({algorithm: 'HS384'}, 'superSecret'); sign({algorithm: 'HS512'}, 'superSecret'); }); it('should validate header', function () { expect(function () { sign({ header: 'foo' }, 'superSecret'); }).to.throw(/"header" must be an object/); sign({header: {}}, 'superSecret'); }); it('should validate encoding', function () { expect(function () { sign({ encoding: 10 }, 'superSecret'); }).to.throw(/"encoding" must be a string/); sign({encoding: 'utf8'},'superSecret'); }); it('should validate noTimestamp', function () { expect(function () { sign({ noTimestamp: 10 }, 'superSecret'); }).to.throw(/"noTimestamp" must be a boolean/); sign({noTimestamp: true}, 'superSecret'); }); }); describe('sign payload registered claims', function() { function sign(payload) { jwt.sign(payload, 'foo123'); } it('should validate exp', function () { expect(function () { sign({ exp: '1 monkey' }); }).to.throw(/"exp" should be a number of seconds/); sign({ exp: 10.1 }); }); }); }); ================================================ FILE: test/secp384r1-private.pem ================================================ -----BEGIN EC PRIVATE KEY----- MIGkAgEBBDCez58vZHVp+ArI7/fe835GAtRzE0AtrxGgQAY1U/uk2SQOaSw1ph61 3Unr0ygS172gBwYFK4EEACKhZANiAARtwlnIqYqZxfiWR+/EM35nKHuLpOjUHiX1 kEpSS03C9XlrBLNwLQfgjpYx9Qvqh26XAzTe74DYjcc748R+zZD2YAd3lV+OcdRE U+DWm4j5E6dlOXzvmw/3qxUcg3rRgR4= -----END EC PRIVATE KEY----- ================================================ FILE: test/secp521r1-private.pem ================================================ -----BEGIN EC PRIVATE KEY----- MIHcAgEBBEIBlWXKBKKCgTgf7+NS09TMv7/NO3RtMBn9xTe+46oNNNK405lrZ9mz WYtlsYvkdsc2Cx3v5V8JegaCOM+XtAZ0MNKgBwYFK4EEACOhgYkDgYYABAFNzaM7 Zb9ug0p5KaZb5mjHrIshoVJSHaOXGtcjLVUakYVk0v9VsE+FKqyuLYcORUuAZdxl ITAlC5e5JZ0o8NEKbAE+8oOrePrItR3IFBtWO15p7qiRa2dBB8oQklFrmQaJYn4K fDV0hYpfu6ahpRNu2akR7aMXL/vXrptCH/n64q9KjA== -----END EC PRIVATE KEY----- ================================================ FILE: test/set_headers.tests.js ================================================ var jwt = require('../index'); var expect = require('chai').expect; describe('set header', function() { it('should add the header', function () { var token = jwt.sign({foo: 123}, '123', { header: { foo: 'bar' } }); var decoded = jwt.decode(token, {complete: true}); expect(decoded.header.foo).to.equal('bar'); }); it('should allow overriding header', function () { var token = jwt.sign({foo: 123}, '123', { header: { alg: 'HS512' } }); var decoded = jwt.decode(token, {complete: true}); expect(decoded.header.alg).to.equal('HS512'); }); }); ================================================ FILE: test/test-utils.js ================================================ 'use strict'; const jwt = require('../'); const expect = require('chai').expect; const sinon = require('sinon'); /** * Correctly report errors that occur in an asynchronous callback * @param {function(err): void} done The mocha callback * @param {function(): void} testFunction The assertions function */ function asyncCheck(done, testFunction) { try { testFunction(); done(); } catch(err) { done(err); } } /** * Assert that two errors are equal * @param e1 {Error} The first error * @param e2 {Error} The second error */ // chai does not do deep equality on errors: https://github.com/chaijs/chai/issues/1009 function expectEqualError(e1, e2) { // message and name are not always enumerable, so manually reference them expect(e1.message, 'Async/Sync Error equality: message').to.equal(e2.message); expect(e1.name, 'Async/Sync Error equality: name').to.equal(e2.name); // compare other enumerable error properties for(const propertyName in e1) { expect(e1[propertyName], `Async/Sync Error equality: ${propertyName}`).to.deep.equal(e2[propertyName]); } } /** * Base64-url encode a string * @param str {string} The string to encode * @returns {string} The encoded string */ function base64UrlEncode(str) { return Buffer.from(str).toString('base64') .replace(/[=]/g, "") .replace(/\+/g, "-") .replace(/\//g, "_") ; } /** * Verify a JWT, ensuring that the asynchronous and synchronous calls to `verify` have the same result * @param {string} jwtString The JWT as a string * @param {string} secretOrPrivateKey The shared secret or private key * @param {object} options Verify options * @param {function(err, token):void} callback */ function verifyJWTHelper(jwtString, secretOrPrivateKey, options, callback) { // freeze the time to ensure the clock remains stable across the async and sync calls const fakeClock = sinon.useFakeTimers({now: Date.now()}); let error; let syncVerified; try { syncVerified = jwt.verify(jwtString, secretOrPrivateKey, options); } catch (err) { error = err; } jwt.verify(jwtString, secretOrPrivateKey, options, (err, asyncVerifiedToken) => { try { if (error) { expectEqualError(err, error); callback(err); } else { expect(syncVerified, 'Async/Sync token equality').to.deep.equal(asyncVerifiedToken); callback(null, syncVerified); } } finally { if (fakeClock) { fakeClock.restore(); } } }); } /** * Sign a payload to create a JWT, ensuring that the asynchronous and synchronous calls to `sign` have the same result * @param {object} payload The JWT payload * @param {string} secretOrPrivateKey The shared secret or private key * @param {object} options Sign options * @param {function(err, token):void} callback */ function signJWTHelper(payload, secretOrPrivateKey, options, callback) { // freeze the time to ensure the clock remains stable across the async and sync calls const fakeClock = sinon.useFakeTimers({now: Date.now()}); let error; let syncSigned; try { syncSigned = jwt.sign(payload, secretOrPrivateKey, options); } catch (err) { error = err; } jwt.sign(payload, secretOrPrivateKey, options, (err, asyncSigned) => { fakeClock.restore(); if (error) { expectEqualError(err, error); callback(err); } else { expect(syncSigned, 'Async/Sync token equality').to.equal(asyncSigned); callback(null, syncSigned); } }); } module.exports = { asyncCheck, base64UrlEncode, signJWTHelper, verifyJWTHelper, }; ================================================ FILE: test/undefined_secretOrPublickey.tests.js ================================================ var jwt = require('../index'); var JsonWebTokenError = require('../lib/JsonWebTokenError'); var expect = require('chai').expect; var TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-IDcSemACt8x4iTMCda8Yhe3iZaWbvV5XKSTbuAn0M'; describe('verifying without specified secret or public key', function () { it('should not verify null', function () { expect(function () { jwt.verify(TOKEN, null); }).to.throw(JsonWebTokenError, /secret or public key must be provided/); }); it('should not verify undefined', function () { expect(function () { jwt.verify(TOKEN); }).to.throw(JsonWebTokenError, /secret or public key must be provided/); }); }); ================================================ FILE: test/validateAsymmetricKey.tests.js ================================================ const validateAsymmetricKey = require('../lib/validateAsymmetricKey'); const PS_SUPPORTED = require('../lib/psSupported'); const ASYMMETRIC_KEY_DETAILS_SUPPORTED = require('../lib/asymmetricKeyDetailsSupported'); const RSA_PSS_KEY_DETAILS_SUPPORTED = require('../lib/rsaPssKeyDetailsSupported'); const fs = require('fs'); const path = require('path'); const { createPrivateKey } = require('crypto'); const expect = require('chai').expect; function loadKey(filename) { return createPrivateKey( fs.readFileSync(path.join(__dirname, filename)) ); } const algorithmParams = { RS256: { invalidPrivateKey: loadKey('secp384r1-private.pem') }, ES256: { invalidPrivateKey: loadKey('priv.pem') } }; if (PS_SUPPORTED) { algorithmParams.PS256 = { invalidPrivateKey: loadKey('secp384r1-private.pem') }; } describe('Asymmetric key validation', function() { Object.keys(algorithmParams).forEach(function(algorithm) { describe(algorithm, function() { const keys = algorithmParams[algorithm]; describe('when validating a key with an invalid private key type', function () { it('should throw an error', function () { const expectedErrorMessage = /"alg" parameter for "[\w\d-]+" key type must be one of:/; expect(function() { validateAsymmetricKey(algorithm, keys.invalidPrivateKey); }).to.throw(expectedErrorMessage); }); }); }); }); describe('when the function has missing parameters', function() { it('should pass the validation if no key has been provided', function() { const algorithm = 'ES256'; validateAsymmetricKey(algorithm); }); it('should pass the validation if no algorithm has been provided', function() { const key = loadKey('dsa-private.pem'); validateAsymmetricKey(null, key); }); }); describe('when validating a key with an unsupported type', function () { it('should throw an error', function() { const algorithm = 'RS256'; const key = loadKey('dsa-private.pem'); const expectedErrorMessage = 'Unknown key type "dsa".'; expect(function() { validateAsymmetricKey(algorithm, key); }).to.throw(expectedErrorMessage); }); }); describe('Elliptic curve algorithms', function () { const curvesAlgorithms = [ { algorithm: 'ES256', curve: 'prime256v1' }, { algorithm: 'ES384', curve: 'secp384r1' }, { algorithm: 'ES512', curve: 'secp521r1' }, ]; const curvesKeys = [ { curve: 'prime256v1', key: loadKey('prime256v1-private.pem') }, { curve: 'secp384r1', key: loadKey('secp384r1-private.pem') }, { curve: 'secp521r1', key: loadKey('secp521r1-private.pem') } ]; describe('when validating keys generated using Elliptic Curves', function () { curvesAlgorithms.forEach(function(curveAlgorithm) { curvesKeys .forEach((curveKeys) => { if (curveKeys.curve !== curveAlgorithm.curve) { if (ASYMMETRIC_KEY_DETAILS_SUPPORTED) { it(`should throw an error when validating an ${curveAlgorithm.algorithm} token for key with curve ${curveKeys.curve}`, function() { expect(() => { validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key); }).to.throw(`"alg" parameter "${curveAlgorithm.algorithm}" requires curve "${curveAlgorithm.curve}".`); }); } else { it(`should pass the validation for incorrect keys if the Node version does not support checking the key's curve name`, function() { expect(() => { validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key); }).not.to.throw(); }); } } else { it(`should accept an ${curveAlgorithm.algorithm} token for key with curve ${curveKeys.curve}`, function() { expect(() => { validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key); }).not.to.throw(); }); } }); }); }); }); if (RSA_PSS_KEY_DETAILS_SUPPORTED) { describe('RSA-PSS algorithms', function () { const key = loadKey('rsa-pss-private.pem'); it(`it should throw an error when validating a key with wrong RSA-RSS parameters`, function () { const algorithm = 'PS512'; expect(function() { validateAsymmetricKey(algorithm, key); }).to.throw('Invalid key for this operation, its RSA-PSS parameters do not meet the requirements of "alg" PS512') }); it(`it should throw an error when validating a key with invalid salt length`, function () { const algorithm = 'PS256'; const shortSaltKey = loadKey('rsa-pss-invalid-salt-length-private.pem'); expect(function() { validateAsymmetricKey(algorithm, shortSaltKey); }).to.throw('Invalid key for this operation, its RSA-PSS parameter saltLength does not meet the requirements of "alg" PS256.') }); it(`it should pass the validation when the key matches all the requirements for the algorithm`, function () { expect(function() { const algorithm = 'PS256'; validateAsymmetricKey(algorithm, key); }).not.to.throw() }); }); } }); ================================================ FILE: test/verify.tests.js ================================================ const jwt = require('../index'); const jws = require('jws'); const fs = require('fs'); const path = require('path'); const sinon = require('sinon'); const JsonWebTokenError = require('../lib/JsonWebTokenError'); const assert = require('chai').assert; const expect = require('chai').expect; describe('verify', function() { const pub = fs.readFileSync(path.join(__dirname, 'pub.pem')); const priv = fs.readFileSync(path.join(__dirname, 'priv.pem')); it('should first assume JSON claim set', function (done) { const header = { alg: 'RS256' }; const payload = { iat: Math.floor(Date.now() / 1000 ) }; const signed = jws.sign({ header: header, payload: payload, secret: priv, encoding: 'utf8' }); jwt.verify(signed, pub, {typ: 'JWT'}, function(err, p) { assert.isNull(err); assert.deepEqual(p, payload); done(); }); }); it('should not be able to verify unsigned token', function () { const header = { alg: 'none' }; const payload = { iat: Math.floor(Date.now() / 1000 ) }; const signed = jws.sign({ header: header, payload: payload, secret: 'secret', encoding: 'utf8' }); expect(function () { jwt.verify(signed, 'secret', {typ: 'JWT'}); }).to.throw(JsonWebTokenError, /jwt signature is required/); }); it('should not be able to verify unsigned token', function () { const header = { alg: 'none' }; const payload = { iat: Math.floor(Date.now() / 1000 ) }; const signed = jws.sign({ header: header, payload: payload, secret: 'secret', encoding: 'utf8' }); expect(function () { jwt.verify(signed, undefined, {typ: 'JWT'}); }).to.throw(JsonWebTokenError, /please specify "none" in "algorithms" to verify unsigned tokens/); }); it('should be able to verify unsigned token when none is specified', function (done) { const header = { alg: 'none' }; const payload = { iat: Math.floor(Date.now() / 1000 ) }; const signed = jws.sign({ header: header, payload: payload, secret: 'secret', encoding: 'utf8' }); jwt.verify(signed, null, {typ: 'JWT', algorithms: ['none']}, function(err, p) { assert.isNull(err); assert.deepEqual(p, payload); done(); }); }); it('should not mutate options', function (done) { const header = { alg: 'HS256' }; const payload = { iat: Math.floor(Date.now() / 1000 ) }; const options = { typ: 'JWT' }; const signed = jws.sign({ header: header, payload: payload, secret: 'secret', encoding: 'utf8' }); jwt.verify(signed, 'secret', options, function(err) { assert.isNull(err); assert.deepEqual(Object.keys(options).length, 1); done(); }); }); describe('secret or token as callback', function () { const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU5Mn0.3aR3vocmgRpG05rsI9MpR6z2T_BGtMQaPq2YR6QaroU'; const key = 'key'; const payload = { foo: 'bar', iat: 1437018582, exp: 1437018592 }; const options = {algorithms: ['HS256'], ignoreExpiration: true}; it('without callback', function (done) { jwt.verify(token, key, options, function (err, p) { assert.isNull(err); assert.deepEqual(p, payload); done(); }); }); it('simple callback', function (done) { const keyFunc = function(header, callback) { assert.deepEqual(header, { alg: 'HS256', typ: 'JWT' }); callback(undefined, key); }; jwt.verify(token, keyFunc, options, function (err, p) { assert.isNull(err); assert.deepEqual(p, payload); done(); }); }); it('should error if called synchronously', function (done) { const keyFunc = function(header, callback) { callback(undefined, key); }; expect(function () { jwt.verify(token, keyFunc, options); }).to.throw(JsonWebTokenError, /verify must be called asynchronous if secret or public key is provided as a callback/); done(); }); it('simple error', function (done) { const keyFunc = function(header, callback) { callback(new Error('key not found')); }; jwt.verify(token, keyFunc, options, function (err, p) { assert.equal(err.name, 'JsonWebTokenError'); assert.match(err.message, /error in secret or public key callback/); assert.isUndefined(p); done(); }); }); it('delayed callback', function (done) { const keyFunc = function(header, callback) { setTimeout(function() { callback(undefined, key); }, 25); }; jwt.verify(token, keyFunc, options, function (err, p) { assert.isNull(err); assert.deepEqual(p, payload); done(); }); }); it('delayed error', function (done) { const keyFunc = function(header, callback) { setTimeout(function() { callback(new Error('key not found')); }, 25); }; jwt.verify(token, keyFunc, options, function (err, p) { assert.equal(err.name, 'JsonWebTokenError'); assert.match(err.message, /error in secret or public key callback/); assert.isUndefined(p); done(); }); }); }); describe('expiration', function () { // { foo: 'bar', iat: 1437018582, exp: 1437018592 } const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU5Mn0.3aR3vocmgRpG05rsI9MpR6z2T_BGtMQaPq2YR6QaroU'; const key = 'key'; let clock; afterEach(function () { try { clock.restore(); } catch (e) {} }); it('should error on expired token', function (done) { clock = sinon.useFakeTimers(1437018650000); // iat + 58s, exp + 48s const options = {algorithms: ['HS256']}; jwt.verify(token, key, options, function (err, p) { assert.equal(err.name, 'TokenExpiredError'); assert.equal(err.message, 'jwt expired'); assert.equal(err.expiredAt.constructor.name, 'Date'); assert.equal(Number(err.expiredAt), 1437018592000); assert.isUndefined(p); done(); }); }); it('should not error on expired token within clockTolerance interval', function (done) { clock = sinon.useFakeTimers(1437018594000); // iat + 12s, exp + 2s const options = {algorithms: ['HS256'], clockTolerance: 5 } jwt.verify(token, key, options, function (err, p) { assert.isNull(err); assert.equal(p.foo, 'bar'); done(); }); }); describe('option: clockTimestamp', function () { const clockTimestamp = 1000000000; it('should verify unexpired token relative to user-provided clockTimestamp', function (done) { const token = jwt.sign({foo: 'bar', iat: clockTimestamp, exp: clockTimestamp + 1}, key); jwt.verify(token, key, {clockTimestamp: clockTimestamp}, function (err) { assert.isNull(err); done(); }); }); it('should error on expired token relative to user-provided clockTimestamp', function (done) { const token = jwt.sign({foo: 'bar', iat: clockTimestamp, exp: clockTimestamp + 1}, key); jwt.verify(token, key, {clockTimestamp: clockTimestamp + 1}, function (err, p) { assert.equal(err.name, 'TokenExpiredError'); assert.equal(err.message, 'jwt expired'); assert.equal(err.expiredAt.constructor.name, 'Date'); assert.equal(Number(err.expiredAt), (clockTimestamp + 1) * 1000); assert.isUndefined(p); done(); }); }); it('should verify clockTimestamp is a number', function (done) { const token = jwt.sign({foo: 'bar', iat: clockTimestamp, exp: clockTimestamp + 1}, key); jwt.verify(token, key, {clockTimestamp: 'notANumber'}, function (err, p) { assert.equal(err.name, 'JsonWebTokenError'); assert.equal(err.message,'clockTimestamp must be a number'); assert.isUndefined(p); done(); }); }); }); describe('option: maxAge and clockTimestamp', function () { // { foo: 'bar', iat: 1437018582, exp: 1437018800 } exp = iat + 218s const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODgwMH0.AVOsNC7TiT-XVSpCpkwB1240izzCIJ33Lp07gjnXVpA'; it('cannot be more permissive than expiration', function (done) { const clockTimestamp = 1437018900; // iat + 318s (exp: iat + 218s) const options = {algorithms: ['HS256'], clockTimestamp: clockTimestamp, maxAge: '1000y'}; jwt.verify(token, key, options, function (err, p) { // maxAge not exceded, but still expired assert.equal(err.name, 'TokenExpiredError'); assert.equal(err.message, 'jwt expired'); assert.equal(err.expiredAt.constructor.name, 'Date'); assert.equal(Number(err.expiredAt), 1437018800000); assert.isUndefined(p); done(); }); }); }); }); describe('when verifying a token with an unsupported public key type', function () { it('should throw an error', function() { const token = 'eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2Njk5OTAwMDN9.YdjFWJtPg_9nccMnTfQyesWQ0UX-GsWrfCGit_HqjeIkNjoV6dkAJ8AtbnVEhA4oxwqSXx6ilMOfHEjmMlPtyyyVKkWKQHcIWYnqPbNSEv8a7Men8KhJTIWb4sf5YbhgSCpNvU_VIZjLO1Z0PzzgmEikp0vYbxZFAbCAlZCvUlcIc-kdjIRCnDJe0BBrYRxNLEJtYsf7D1yFIFIqw8-VP87yZdExA4eHsTaE84SgnL24ZK5h5UooDx-IRNd_rrMyio8kNy63grVxCWOtkXZ26iZk6v-HMsnBqxvUwR6-8wfaWrcpADkyUO1q3SNsoTdwtflbvfwgjo3uve0IvIzHMw'; const key = fs.readFileSync(path.join(__dirname, 'dsa-public.pem')); expect(function() { jwt.verify(token, key); }).to.throw('Unknown key type "dsa".'); }); }); describe('when verifying a token with an incorrect public key type', function () { it('should throw a validation error if key validation is enabled', function() { const token = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXkiOiJsb2FkIiwiaWF0IjoxNjcwMjMwNDE2fQ.7TYP8SB_9Tw1fNIfuG60b4tvoLPpDAVBQpV1oepnuKwjUz8GOw4fRLzclo0Q2YAXisJ3zIYMEFsHpYrflfoZJQ'; const key = fs.readFileSync(path.join(__dirname, 'rsa-public.pem')); expect(function() { jwt.verify(token, key, { algorithms: ['ES256'] }); }).to.throw('"alg" parameter for "rsa" key type must be one of: RS256, PS256, RS384, PS384, RS512, PS512.'); }); it('should throw an unknown error if key validation is disabled', function() { const token = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXkiOiJsb2FkIiwiaWF0IjoxNjcwMjMwNDE2fQ.7TYP8SB_9Tw1fNIfuG60b4tvoLPpDAVBQpV1oepnuKwjUz8GOw4fRLzclo0Q2YAXisJ3zIYMEFsHpYrflfoZJQ'; const key = fs.readFileSync(path.join(__dirname, 'rsa-public.pem')); expect(function() { jwt.verify(token, key, { algorithms: ['ES256'], allowInvalidAsymmetricKeyTypes: true }); }).to.not.throw('"alg" parameter for "rsa" key type must be one of: RS256, PS256, RS384, PS384, RS512, PS512.'); }); }); }); ================================================ FILE: test/wrong_alg.tests.js ================================================ var fs = require('fs'); var path = require('path'); var jwt = require('../index'); var JsonWebTokenError = require('../lib/JsonWebTokenError'); var PS_SUPPORTED = require('../lib/psSupported'); var expect = require('chai').expect; var pub = fs.readFileSync(path.join(__dirname, 'pub.pem'), 'utf8'); // priv is never used // var priv = fs.readFileSync(path.join(__dirname, 'priv.pem')); var TOKEN = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MjY1NDY5MTl9.ETgkTn8BaxIX4YqvUWVFPmum3moNZ7oARZtSBXb_vP4'; describe('when setting a wrong `header.alg`', function () { describe('signing with pub key as symmetric', function () { it('should not verify', function () { expect(function () { jwt.verify(TOKEN, pub); }).to.throw(JsonWebTokenError, /invalid algorithm/); }); }); describe('signing with pub key as HS256 and whitelisting only RS256', function () { it('should not verify', function () { expect(function () { jwt.verify(TOKEN, pub, {algorithms: ['RS256']}); }).to.throw(JsonWebTokenError, /invalid algorithm/); }); }); if (PS_SUPPORTED) { describe('signing with pub key as HS256 and whitelisting only PS256', function () { it('should not verify', function () { expect(function () { jwt.verify(TOKEN, pub, {algorithms: ['PS256']}); }).to.throw(JsonWebTokenError, /invalid algorithm/); }); }); } describe('signing with HS256 and checking with HS384', function () { it('should not verify', function () { expect(function () { var token = jwt.sign({foo: 'bar'}, 'secret', {algorithm: 'HS256'}); jwt.verify(token, 'some secret', {algorithms: ['HS384']}); }).to.throw(JsonWebTokenError, /invalid algorithm/); }); }); }); ================================================ FILE: verify.js ================================================ const JsonWebTokenError = require('./lib/JsonWebTokenError'); const NotBeforeError = require('./lib/NotBeforeError'); const TokenExpiredError = require('./lib/TokenExpiredError'); const decode = require('./decode'); const timespan = require('./lib/timespan'); const validateAsymmetricKey = require('./lib/validateAsymmetricKey'); const PS_SUPPORTED = require('./lib/psSupported'); const jws = require('jws'); const {KeyObject, createSecretKey, createPublicKey} = require("crypto"); const PUB_KEY_ALGS = ['RS256', 'RS384', 'RS512']; const EC_KEY_ALGS = ['ES256', 'ES384', 'ES512']; const RSA_KEY_ALGS = ['RS256', 'RS384', 'RS512']; const HS_ALGS = ['HS256', 'HS384', 'HS512']; if (PS_SUPPORTED) { PUB_KEY_ALGS.splice(PUB_KEY_ALGS.length, 0, 'PS256', 'PS384', 'PS512'); RSA_KEY_ALGS.splice(RSA_KEY_ALGS.length, 0, 'PS256', 'PS384', 'PS512'); } module.exports = function (jwtString, secretOrPublicKey, options, callback) { if ((typeof options === 'function') && !callback) { callback = options; options = {}; } if (!options) { options = {}; } //clone this object since we are going to mutate it. options = Object.assign({}, options); let done; if (callback) { done = callback; } else { done = function(err, data) { if (err) throw err; return data; }; } if (options.clockTimestamp && typeof options.clockTimestamp !== 'number') { return done(new JsonWebTokenError('clockTimestamp must be a number')); } if (options.nonce !== undefined && (typeof options.nonce !== 'string' || options.nonce.trim() === '')) { return done(new JsonWebTokenError('nonce must be a non-empty string')); } if (options.allowInvalidAsymmetricKeyTypes !== undefined && typeof options.allowInvalidAsymmetricKeyTypes !== 'boolean') { return done(new JsonWebTokenError('allowInvalidAsymmetricKeyTypes must be a boolean')); } const clockTimestamp = options.clockTimestamp || Math.floor(Date.now() / 1000); if (!jwtString){ return done(new JsonWebTokenError('jwt must be provided')); } if (typeof jwtString !== 'string') { return done(new JsonWebTokenError('jwt must be a string')); } const parts = jwtString.split('.'); if (parts.length !== 3){ return done(new JsonWebTokenError('jwt malformed')); } let decodedToken; try { decodedToken = decode(jwtString, { complete: true }); } catch(err) { return done(err); } if (!decodedToken) { return done(new JsonWebTokenError('invalid token')); } const header = decodedToken.header; let getSecret; if(typeof secretOrPublicKey === 'function') { if(!callback) { return done(new JsonWebTokenError('verify must be called asynchronous if secret or public key is provided as a callback')); } getSecret = secretOrPublicKey; } else { getSecret = function(header, secretCallback) { return secretCallback(null, secretOrPublicKey); }; } return getSecret(header, function(err, secretOrPublicKey) { if(err) { return done(new JsonWebTokenError('error in secret or public key callback: ' + err.message)); } const hasSignature = parts[2].trim() !== ''; if (!hasSignature && secretOrPublicKey){ return done(new JsonWebTokenError('jwt signature is required')); } if (hasSignature && !secretOrPublicKey) { return done(new JsonWebTokenError('secret or public key must be provided')); } if (!hasSignature && !options.algorithms) { return done(new JsonWebTokenError('please specify "none" in "algorithms" to verify unsigned tokens')); } if (secretOrPublicKey != null && !(secretOrPublicKey instanceof KeyObject)) { try { secretOrPublicKey = createPublicKey(secretOrPublicKey); } catch (_) { try { secretOrPublicKey = createSecretKey(typeof secretOrPublicKey === 'string' ? Buffer.from(secretOrPublicKey) : secretOrPublicKey); } catch (_) { return done(new JsonWebTokenError('secretOrPublicKey is not valid key material')) } } } if (!options.algorithms) { if (secretOrPublicKey.type === 'secret') { options.algorithms = HS_ALGS; } else if (['rsa', 'rsa-pss'].includes(secretOrPublicKey.asymmetricKeyType)) { options.algorithms = RSA_KEY_ALGS } else if (secretOrPublicKey.asymmetricKeyType === 'ec') { options.algorithms = EC_KEY_ALGS } else { options.algorithms = PUB_KEY_ALGS } } if (options.algorithms.indexOf(decodedToken.header.alg) === -1) { return done(new JsonWebTokenError('invalid algorithm')); } if (header.alg.startsWith('HS') && secretOrPublicKey.type !== 'secret') { return done(new JsonWebTokenError((`secretOrPublicKey must be a symmetric key when using ${header.alg}`))) } else if (/^(?:RS|PS|ES)/.test(header.alg) && secretOrPublicKey.type !== 'public') { return done(new JsonWebTokenError((`secretOrPublicKey must be an asymmetric key when using ${header.alg}`))) } if (!options.allowInvalidAsymmetricKeyTypes) { try { validateAsymmetricKey(header.alg, secretOrPublicKey); } catch (e) { return done(e); } } let valid; try { valid = jws.verify(jwtString, decodedToken.header.alg, secretOrPublicKey); } catch (e) { return done(e); } if (!valid) { return done(new JsonWebTokenError('invalid signature')); } const payload = decodedToken.payload; if (typeof payload.nbf !== 'undefined' && !options.ignoreNotBefore) { if (typeof payload.nbf !== 'number') { return done(new JsonWebTokenError('invalid nbf value')); } if (payload.nbf > clockTimestamp + (options.clockTolerance || 0)) { return done(new NotBeforeError('jwt not active', new Date(payload.nbf * 1000))); } } if (typeof payload.exp !== 'undefined' && !options.ignoreExpiration) { if (typeof payload.exp !== 'number') { return done(new JsonWebTokenError('invalid exp value')); } if (clockTimestamp >= payload.exp + (options.clockTolerance || 0)) { return done(new TokenExpiredError('jwt expired', new Date(payload.exp * 1000))); } } if (options.audience) { const audiences = Array.isArray(options.audience) ? options.audience : [options.audience]; const target = Array.isArray(payload.aud) ? payload.aud : [payload.aud]; const match = target.some(function (targetAudience) { return audiences.some(function (audience) { return audience instanceof RegExp ? audience.test(targetAudience) : audience === targetAudience; }); }); if (!match) { return done(new JsonWebTokenError('jwt audience invalid. expected: ' + audiences.join(' or '))); } } if (options.issuer) { const invalid_issuer = (typeof options.issuer === 'string' && payload.iss !== options.issuer) || (Array.isArray(options.issuer) && options.issuer.indexOf(payload.iss) === -1); if (invalid_issuer) { return done(new JsonWebTokenError('jwt issuer invalid. expected: ' + options.issuer)); } } if (options.subject) { if (payload.sub !== options.subject) { return done(new JsonWebTokenError('jwt subject invalid. expected: ' + options.subject)); } } if (options.jwtid) { if (payload.jti !== options.jwtid) { return done(new JsonWebTokenError('jwt jwtid invalid. expected: ' + options.jwtid)); } } if (options.nonce) { if (payload.nonce !== options.nonce) { return done(new JsonWebTokenError('jwt nonce invalid. expected: ' + options.nonce)); } } if (options.maxAge) { if (typeof payload.iat !== 'number') { return done(new JsonWebTokenError('iat required when maxAge is specified')); } const maxAgeTimestamp = timespan(options.maxAge, payload.iat); if (typeof maxAgeTimestamp === 'undefined') { return done(new JsonWebTokenError('"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60')); } if (clockTimestamp >= maxAgeTimestamp + (options.clockTolerance || 0)) { return done(new TokenExpiredError('maxAge exceeded', new Date(maxAgeTimestamp * 1000))); } } if (options.complete === true) { const signature = decodedToken.signature; return done(null, { header: header, payload: payload, signature: signature }); } return done(null, payload); }); };