├── .version ├── .semgrepignore ├── .prettierrc ├── .github ├── CODEOWNERS ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── Feature Request.yml │ └── Bug Report.yml ├── actions │ ├── get-version │ │ └── action.yml │ ├── build │ │ └── action.yml │ ├── get-prerelease │ │ └── action.yml │ ├── tag-exists │ │ └── action.yml │ ├── release-create │ │ └── action.yml │ ├── npm-publish │ │ └── action.yml │ └── get-release-notes │ │ └── action.yml ├── workflows │ ├── release.yml │ ├── snyk.yml │ ├── semgrep.yml │ ├── codeql.yml │ ├── test.yml │ └── npm-release.yml ├── stale.yml └── PULL_REQUEST_TEMPLATE.md ├── .DS_Store ├── src ├── telemetry.js ├── helpers │ ├── dummy-cache.js │ ├── error.js │ ├── jwks.js │ ├── base64.js │ └── rsa-verifier.js └── index.js ├── opslevel.yml ├── docs ├── fonts │ ├── OpenSans-Bold-webfont.eot │ ├── OpenSans-Bold-webfont.woff │ ├── OpenSans-Light-webfont.eot │ ├── OpenSans-Italic-webfont.eot │ ├── OpenSans-Italic-webfont.woff │ ├── OpenSans-Light-webfont.woff │ ├── OpenSans-Regular-webfont.eot │ ├── OpenSans-Regular-webfont.woff │ ├── OpenSans-Semibold-webfont.eot │ ├── OpenSans-Semibold-webfont.ttf │ ├── OpenSans-BoldItalic-webfont.eot │ ├── OpenSans-Semibold-webfont.woff │ ├── OpenSans-BoldItalic-webfont.woff │ ├── OpenSans-LightItalic-webfont.eot │ ├── OpenSans-LightItalic-webfont.woff │ ├── OpenSans-SemiboldItalic-webfont.eot │ ├── OpenSans-SemiboldItalic-webfont.ttf │ └── OpenSans-SemiboldItalic-webfont.woff ├── scripts │ ├── linenumber.js │ └── prettify │ │ ├── lang-css.js │ │ ├── Apache-License-2.0.txt │ │ └── prettify.js ├── styles │ ├── prettify-jsdoc.css │ ├── prettify-tomorrow.css │ └── jsdoc-default.css ├── index.html ├── IdTokenVerifier.html ├── index.js.html └── global.html ├── .babelrc ├── .shiprc ├── .nycrc ├── codecov.yml ├── scripts ├── jsdocs.js └── print-bundle-size.js ├── test ├── mock │ ├── async-cache.js │ └── cache-mock.js ├── base64.test.js ├── helper │ ├── token-validation.js │ └── jwt.js └── jwks.test.js ├── .jsdoc.json ├── .gitignore ├── LICENSE ├── Jenkinsfile ├── package.json ├── README.md ├── types └── index.d.ts └── CHANGELOG.md /.version: -------------------------------------------------------------------------------- 1 | v2.2.4 -------------------------------------------------------------------------------- /.semgrepignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | test/ 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @auth0/project-dx-sdks-engineer-codeowner 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/.DS_Store -------------------------------------------------------------------------------- /src/telemetry.js: -------------------------------------------------------------------------------- 1 | export default { version: '2.2.4', name: 'idtoken-verifier' }; 2 | -------------------------------------------------------------------------------- /opslevel.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 1 3 | repository: 4 | owner: dx_sdks 5 | tier: 6 | tags: 7 | -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-Light-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-Light-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-Semibold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-Semibold-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Semibold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-Semibold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-LightItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-LightItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-SemiboldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-SemiboldItalic-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/OpenSans-SemiboldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0/idtoken-verifier/HEAD/docs/fonts/OpenSans-SemiboldItalic-webfont.woff -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'daily' 7 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "plugins": ["rewire", "babel-plugin-istanbul"], 5 | "presets": ["@babel/preset-env"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.shiprc: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "src/telemetry.js": [], 4 | ".version": [] 5 | }, 6 | "postbump": "npm run build && node scripts/jsdocs.js" 7 | } 8 | -------------------------------------------------------------------------------- /src/helpers/dummy-cache.js: -------------------------------------------------------------------------------- 1 | export default class DummyCache { 2 | get() { 3 | return null; 4 | } 5 | has() { 6 | return null; 7 | } 8 | set() { 9 | return null; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Auth0 Community 4 | url: https://community.auth0.com 5 | about: Discuss this library in the Auth0 Community forums 6 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "reporter" : ["text-summary", "lcov"], 3 | "include" : ["src/**/*.js"], 4 | "require" : ["@babel/register"], 5 | "sourceMap" : false, 6 | "instrument" : false, 7 | "all" : true 8 | } -------------------------------------------------------------------------------- /src/helpers/error.js: -------------------------------------------------------------------------------- 1 | export function ConfigurationError(message) { 2 | this.name = 'ConfigurationError'; 3 | this.message = message || ''; 4 | } 5 | ConfigurationError.prototype = Error.prototype; 6 | 7 | export function TokenValidationError(message) { 8 | this.name = 'TokenValidationError'; 9 | this.message = message || ''; 10 | } 11 | TokenValidationError.prototype = Error.prototype; 12 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: down 4 | range: '70...100' 5 | status: 6 | patch: 7 | default: 8 | if_no_uploads: error 9 | threshold: 50 10 | changes: true 11 | project: 12 | default: 13 | target: auto 14 | if_no_uploads: error 15 | threshold: 2 16 | ignore: 17 | - src/helpers/rsa-verifier.js 18 | - scripts/ 19 | - node_modules/ 20 | comment: false 21 | -------------------------------------------------------------------------------- /.github/actions/get-version/action.yml: -------------------------------------------------------------------------------- 1 | name: Return the version extracted from the branch name 2 | 3 | # 4 | # Returns the version from the .version file. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | 9 | outputs: 10 | version: 11 | value: ${{ steps.get_version.outputs.VERSION }} 12 | 13 | runs: 14 | using: composite 15 | 16 | steps: 17 | - id: get_version 18 | shell: bash 19 | run: | 20 | VERSION=$(head -1 .version) 21 | echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT 22 | -------------------------------------------------------------------------------- /scripts/jsdocs.js: -------------------------------------------------------------------------------- 1 | if (process.platform === 'win32') { 2 | console.error('Must be run on a Unix OS'); 3 | process.exit(1); 4 | } 5 | 6 | var library = require('../package.json'); 7 | var execSync = require('child_process').execSync; 8 | var fs = require('fs'); 9 | 10 | execSync('npm run jsdoc:generate', { stdio: 'inherit' }); 11 | if (fs.existsSync('docs')) { 12 | execSync('rm -r docs', { stdio: 'inherit' }); 13 | } 14 | execSync(`mv out/idtoken-verifier/${library.version}/ docs`, { 15 | stdio: 'inherit' 16 | }); 17 | execSync('git add docs'); 18 | -------------------------------------------------------------------------------- /scripts/print-bundle-size.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const Table = require('cli-table'); 3 | const gzipSize = require('gzip-size'); 4 | 5 | const toKb = b => `${(b / Math.pow(1024, 1)).toFixed(2)} kb`; 6 | 7 | const table = new Table({ 8 | head: ['File', 'Size', 'GZIP size'] 9 | }); 10 | 11 | fs.readdirSync('./build') 12 | .filter(f => f.endsWith('.js')) 13 | .forEach(f => { 14 | const path = `./build/${f}`; 15 | table.push([ 16 | f, 17 | toKb(fs.statSync(path).size), 18 | toKb(gzipSize.fileSync(path)) 19 | ]); 20 | }); 21 | 22 | console.log(table.toString()); 23 | -------------------------------------------------------------------------------- /test/mock/async-cache.js: -------------------------------------------------------------------------------- 1 | export function TimeoutPromise(ms) { 2 | return new Promise(function (resolve) { 3 | setTimeout(resolve, ms); 4 | }); 5 | } 6 | 7 | export function AsyncCache(cache) { 8 | this.cache = cache; 9 | } 10 | 11 | AsyncCache.prototype.get = function(key) { 12 | return TimeoutPromise(10) 13 | .then(this.cache.get(key)); 14 | }; 15 | 16 | AsyncCache.prototype.has = function(key) { 17 | return TimeoutPromise(10) 18 | .then(this.cache.has(key)); 19 | }; 20 | 21 | AsyncCache.prototype.set = function(key, value) { 22 | return TimeoutPromise(10) 23 | .then(this.cache.set(key)); 24 | }; 25 | -------------------------------------------------------------------------------- /.github/actions/build/action.yml: -------------------------------------------------------------------------------- 1 | name: Build package 2 | description: Build the SDK package 3 | 4 | inputs: 5 | node: 6 | description: The Node version to use 7 | required: false 8 | default: 18 9 | 10 | runs: 11 | using: composite 12 | 13 | steps: 14 | - name: Setup Node 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: ${{ inputs.node }} 18 | cache: npm 19 | 20 | - name: Install dependencies 21 | shell: bash 22 | run: npm ci 23 | env: 24 | NODE_ENV: development 25 | 26 | - name: Build package 27 | shell: bash 28 | run: npm run build 29 | -------------------------------------------------------------------------------- /docs/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (function() { 3 | var source = document.getElementsByClassName('prettyprint source linenums'); 4 | var i = 0; 5 | var lineNumber = 0; 6 | var lineId; 7 | var lines; 8 | var totalLines; 9 | var anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = 'line' + lineNumber; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /.jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true, 4 | "dictionaries": [ 5 | "jsdoc" 6 | ] 7 | }, 8 | "source": { 9 | "include": [ 10 | "src", 11 | "package.json", 12 | "README.md" 13 | ], 14 | "includePattern": ".js$", 15 | "excludePattern": "(node_modules/|docs|test|build)" 16 | }, 17 | "plugins": [ 18 | "./node_modules/jsdoc/plugins/markdown" 19 | ], 20 | "templates": { 21 | "cleverLinks": false, 22 | "monospaceLinks": true, 23 | "useLongnameInNav": false 24 | }, 25 | "opts": { 26 | "destination": "./out/", 27 | "encoding": "utf8", 28 | "private": false, 29 | "recurse": true, 30 | "template": "./node_modules/minami" 31 | } 32 | } -------------------------------------------------------------------------------- /.github/actions/get-prerelease/action.yml: -------------------------------------------------------------------------------- 1 | name: Return a boolean indicating if the version contains prerelease identifiers 2 | 3 | # 4 | # Returns a simple true/false boolean indicating whether the version indicates it's a prerelease or not. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | 9 | inputs: 10 | version: 11 | required: true 12 | 13 | outputs: 14 | prerelease: 15 | value: ${{ steps.get_prerelease.outputs.PRERELEASE }} 16 | 17 | runs: 18 | using: composite 19 | 20 | steps: 21 | - id: get_prerelease 22 | shell: bash 23 | run: | 24 | if [[ "${VERSION}" == *"beta"* || "${VERSION}" == *"alpha"* ]]; then 25 | echo "PRERELEASE=true" >> $GITHUB_OUTPUT 26 | else 27 | echo "PRERELEASE=false" >> $GITHUB_OUTPUT 28 | fi 29 | env: 30 | VERSION: ${{ inputs.version }} 31 | -------------------------------------------------------------------------------- /docs/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create GitHub Release 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - closed 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: write 11 | id-token: write # For publishing to npm using --provenance 12 | 13 | ### TODO: Replace instances of './.github/workflows/' w/ `auth0/dx-sdk-actions/workflows/` and append `@latest` after the common `dx-sdk-actions` repo is made public. 14 | ### TODO: Also remove `get-prerelease`, `get-release-notes`, `get-version`, `npm-publish`, `release-create`, and `tag-exists` actions from this repo's .github/actions folder once the repo is public. 15 | ### TODO: Also remove `npm-release` workflow from this repo's .github/workflows folder once the repo is public. 16 | 17 | jobs: 18 | release: 19 | uses: ./.github/workflows/npm-release.yml 20 | with: 21 | node-version: 18 22 | require-build: false 23 | secrets: 24 | npm-token: ${{ secrets.NPM_TOKEN }} 25 | github-token: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Dependency directories 28 | node_modules 29 | jspm_packages 30 | 31 | # Optional npm cache directory 32 | .npm 33 | 34 | # Optional eslint cache 35 | .eslintcache 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | # Output of 'npm pack' 41 | *.tgz 42 | 43 | # Yarn Integrity file 44 | .yarn-integrity 45 | 46 | # Example folder 47 | example 48 | 49 | # Build folder 50 | /build 51 | 52 | # Release process 53 | .release 54 | .release-tmp-*/ 55 | out/ 56 | 57 | # Yarn lock file 58 | yarn.lock 59 | 60 | .DS_Store 61 | test-results.xml 62 | -------------------------------------------------------------------------------- /.github/actions/tag-exists/action.yml: -------------------------------------------------------------------------------- 1 | name: Return a boolean indicating if a tag already exists for the repository 2 | 3 | # 4 | # Returns a simple true/false boolean indicating whether the tag exists or not. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | 9 | inputs: 10 | token: 11 | required: true 12 | tag: 13 | required: true 14 | 15 | outputs: 16 | exists: 17 | description: 'Whether the tag exists or not' 18 | value: ${{ steps.tag-exists.outputs.EXISTS }} 19 | 20 | runs: 21 | using: composite 22 | 23 | steps: 24 | - id: tag-exists 25 | shell: bash 26 | run: | 27 | GET_API_URL="https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG_NAME}" 28 | http_status_code=$(curl -LI $GET_API_URL -o /dev/null -w '%{http_code}\n' -s -H "Authorization: token ${GITHUB_TOKEN}") 29 | if [ "$http_status_code" -ne "404" ] ; then 30 | echo "EXISTS=true" >> $GITHUB_OUTPUT 31 | else 32 | echo "EXISTS=false" >> $GITHUB_OUTPUT 33 | fi 34 | env: 35 | TAG_NAME: ${{ inputs.tag }} 36 | GITHUB_TOKEN: ${{ inputs.token }} 37 | -------------------------------------------------------------------------------- /.github/workflows/snyk.yml: -------------------------------------------------------------------------------- 1 | name: Snyk 2 | 3 | on: 4 | merge_group: 5 | workflow_dispatch: 6 | pull_request: 7 | types: 8 | - opened 9 | - synchronize 10 | push: 11 | branches: 12 | - master 13 | schedule: 14 | - cron: '30 0 1,15 * *' 15 | 16 | permissions: 17 | contents: read 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 21 | cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} 22 | 23 | jobs: 24 | check: 25 | name: Check for Vulnerabilities 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - if: github.actor == 'dependabot[bot]' || github.event_name == 'merge_group' 30 | run: exit 0 # Skip unnecessary test runs for dependabot and merge queues. Artifically flag as successful, as this is a required check for branch protection. 31 | 32 | - uses: actions/checkout@v4 33 | with: 34 | ref: ${{ github.event.pull_request.head.sha || github.ref }} 35 | 36 | - run: npm install -g snyk 37 | 38 | - run: snyk test 39 | env: 40 | SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} 41 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | name: Semgrep 2 | 3 | on: 4 | merge_group: 5 | pull_request: 6 | types: 7 | - opened 8 | - synchronize 9 | push: 10 | branches: 11 | - master 12 | schedule: 13 | - cron: '30 0 1,15 * *' 14 | 15 | permissions: 16 | contents: read 17 | 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 20 | cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} 21 | 22 | jobs: 23 | run: 24 | name: Check for Vulnerabilities 25 | runs-on: ubuntu-latest 26 | 27 | container: 28 | image: returntocorp/semgrep 29 | 30 | steps: 31 | - if: github.actor == 'dependabot[bot]' || github.event_name == 'merge_group' 32 | run: exit 0 # Skip unnecessary test runs for dependabot and merge queues. Artifically flag as successful, as this is a required check for branch protection. 33 | 34 | - uses: actions/checkout@v4 35 | with: 36 | ref: ${{ github.event.pull_request.head.sha || github.ref }} 37 | 38 | - run: semgrep ci 39 | env: 40 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Auth0, Inc. (http://auth0.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 30 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | daysUntilClose: 7 8 | 9 | # Only issues or pull requests with all of these labels are considered by StaleBot. Defaults to `[]` (disabled) 10 | onlyLabels: 11 | - 'more info needed' 12 | 13 | # Ignore issues in projects 14 | exemptProjects: true 15 | 16 | # Ignore issues and PRs in milestones 17 | exemptMilestones: true 18 | 19 | # Set to true to ignore issues with an assignee 20 | exemptAssignees: true 21 | 22 | # Label to use when marking as stale 23 | staleLabel: closed:stale 24 | 25 | # Comment to post when marking as stale. Set to `false` to disable 26 | markComment: > 27 | This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️ 28 | -------------------------------------------------------------------------------- /.github/actions/release-create/action.yml: -------------------------------------------------------------------------------- 1 | name: Create a GitHub release 2 | 3 | # 4 | # Creates a GitHub release with the given version. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | 9 | inputs: 10 | token: 11 | required: true 12 | files: 13 | required: false 14 | name: 15 | required: true 16 | body: 17 | required: true 18 | tag: 19 | required: true 20 | commit: 21 | required: true 22 | draft: 23 | default: false 24 | required: false 25 | prerelease: 26 | default: false 27 | required: false 28 | fail_on_unmatched_files: 29 | default: true 30 | required: false 31 | 32 | runs: 33 | using: composite 34 | 35 | steps: 36 | - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 37 | with: 38 | body: ${{ inputs.body }} 39 | name: ${{ inputs.name }} 40 | tag_name: ${{ inputs.tag }} 41 | target_commitish: ${{ inputs.commit }} 42 | draft: ${{ inputs.draft }} 43 | prerelease: ${{ inputs.prerelease }} 44 | fail_on_unmatched_files: ${{ inputs.fail_on_unmatched_files }} 45 | files: ${{ inputs.files }} 46 | env: 47 | GITHUB_TOKEN: ${{ inputs.token }} 48 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Changes 2 | 3 | Please describe both what is changing and why this is important. Include: 4 | 5 | - Endpoints added, deleted, deprecated, or changed 6 | - Classes and methods added, deleted, deprecated, or changed 7 | - Screenshots of new or changed UI, if applicable 8 | - A summary of usage if this is a new feature or change to a public API (this should also be added to relevant documentation once released) 9 | - Any alternative designs or approaches considered 10 | 11 | ### References 12 | 13 | Please include relevant links supporting this change such as a: 14 | 15 | - support ticket 16 | - community post 17 | - StackOverflow post 18 | - support forum thread 19 | 20 | ### Testing 21 | 22 | Please describe how this can be tested by reviewers. Be specific about anything not tested and reasons why. If this library has unit and/or integration testing, tests should be added for new functionality and existing tests should complete without errors. 23 | 24 | - [ ] This change adds test coverage 25 | 26 | ### Checklist 27 | 28 | - [ ] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) 29 | - [ ] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md) 30 | - [ ] All existing and new tests complete without errors 31 | -------------------------------------------------------------------------------- /.github/actions/npm-publish/action.yml: -------------------------------------------------------------------------------- 1 | name: Publish release to npm 2 | 3 | inputs: 4 | node-version: 5 | required: true 6 | npm-token: 7 | required: true 8 | version: 9 | required: true 10 | require-build: 11 | default: true 12 | release-directory: 13 | default: './' 14 | 15 | runs: 16 | using: composite 17 | 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup Node 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ inputs.node-version }} 26 | cache: 'npm' 27 | registry-url: 'https://registry.npmjs.org' 28 | 29 | - name: Install dependencies 30 | shell: bash 31 | run: npm ci --include=dev 32 | 33 | - name: Build package 34 | if: inputs.require-build == 'true' 35 | shell: bash 36 | run: npm run build 37 | 38 | - name: Publish release to NPM 39 | shell: bash 40 | working-directory: ${{ inputs.release-directory }} 41 | run: | 42 | if [[ "${VERSION}" == *"beta"* ]]; then 43 | TAG="beta" 44 | elif [[ "${VERSION}" == *"alpha"* ]]; then 45 | TAG="alpha" 46 | else 47 | TAG="latest" 48 | fi 49 | npm publish --provenance --tag $TAG 50 | env: 51 | NODE_AUTH_TOKEN: ${{ inputs.npm-token }} 52 | VERSION: ${{ inputs.version }} 53 | -------------------------------------------------------------------------------- /.github/actions/get-release-notes/action.yml: -------------------------------------------------------------------------------- 1 | name: Return the release notes extracted from the body of the PR associated with the release. 2 | 3 | # 4 | # Returns the release notes from the content of a pull request linked to a release branch. It expects the branch name to be in the format release/vX.Y.Z, release/X.Y.Z, release/vX.Y.Z-beta.N. etc. 5 | # 6 | # TODO: Remove once the common repo is public. 7 | # 8 | inputs: 9 | version: 10 | required: true 11 | repo_name: 12 | required: false 13 | repo_owner: 14 | required: true 15 | token: 16 | required: true 17 | 18 | outputs: 19 | release-notes: 20 | value: ${{ steps.get_release_notes.outputs.RELEASE_NOTES }} 21 | 22 | runs: 23 | using: composite 24 | 25 | steps: 26 | - uses: actions/github-script@v7 27 | id: get_release_notes 28 | with: 29 | result-encoding: string 30 | script: | 31 | const { data: pulls } = await github.rest.pulls.list({ 32 | owner: process.env.REPO_OWNER, 33 | repo: process.env.REPO_NAME, 34 | state: 'all', 35 | head: `${process.env.REPO_OWNER}:release/${process.env.VERSION}`, 36 | }); 37 | core.setOutput('RELEASE_NOTES', pulls[0].body); 38 | env: 39 | GITHUB_TOKEN: ${{ inputs.token }} 40 | REPO_OWNER: ${{ inputs.repo_owner }} 41 | REPO_NAME: ${{ inputs.repo_name }} 42 | VERSION: ${{ inputs.version }} 43 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL 2 | 3 | on: 4 | merge_group: 5 | pull_request: 6 | types: 7 | - opened 8 | - synchronize 9 | push: 10 | branches: 11 | - master 12 | schedule: 13 | - cron: '37 10 * * 2' 14 | 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | concurrency: 21 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 22 | cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} 23 | 24 | jobs: 25 | analyze: 26 | name: Check for Vulnerabilities 27 | runs-on: ubuntu-latest 28 | 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | language: [javascript] 33 | 34 | steps: 35 | - if: github.actor == 'dependabot[bot]' || github.event_name == 'merge_group' 36 | run: exit 0 # Skip unnecessary test runs for dependabot and merge queues. Artifically flag as successful, as this is a required check for branch protection. 37 | 38 | - name: Checkout 39 | uses: actions/checkout@v4 40 | 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v3 43 | with: 44 | languages: ${{ matrix.language }} 45 | queries: +security-and-quality 46 | 47 | - name: Autobuild 48 | uses: github/codeql-action/autobuild@v3 49 | 50 | - name: Perform CodeQL Analysis 51 | uses: github/codeql-action/analyze@v3 52 | with: 53 | category: '/language:${{ matrix.language }}' 54 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | stages { 4 | stage('Installing dependencies...') { 5 | steps { 6 | sh 'yarn install' 7 | } 8 | } 9 | 10 | stage('Running tests...') { 11 | steps { 12 | sh 'yarn test' 13 | } 14 | } 15 | 16 | stage('Uploading to CDN...') { 17 | steps { 18 | sh 'yarn upload' 19 | } 20 | } 21 | } 22 | 23 | post { 24 | // Always runs. And it runs before any of the other post conditions. 25 | always { 26 | // Let's wipe out the workspace before we finish! 27 | deleteDir() 28 | } 29 | 30 | success { 31 | slackSend channel: '#crew-brucke-feed', 32 | color: 'good', 33 | message: "Upload of ${currentBuild.fullDisplayName} completed successfully." 34 | } 35 | 36 | failure { 37 | slackSend channel: '#crew-brucke-feed', 38 | color: 'error', 39 | message: "Upload of ${currentBuild.fullDisplayName} has failed." 40 | 41 | } 42 | } 43 | 44 | // The options directive is for configuration that applies to the whole job. 45 | options { 46 | // For example, we'd like to make sure we only keep 10 builds at a time, so 47 | // we don't fill up our storage! 48 | buildDiscarder(logRotator(numToKeepStr:'10')) 49 | 50 | // And we'd really like to be sure that this build doesn't hang forever, so 51 | // let's time it out after an hour. 52 | timeout(time: 60, unit: 'MINUTES') 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/helpers/jwks.js: -------------------------------------------------------------------------------- 1 | import urljoin from 'url-join'; 2 | import * as base64 from './base64'; 3 | import unfetch from 'unfetch'; 4 | 5 | export function process(jwks) { 6 | var modulus = base64.decodeToHEX(jwks.n); 7 | var exp = base64.decodeToHEX(jwks.e); 8 | 9 | return { 10 | modulus: modulus, 11 | exp: exp 12 | }; 13 | } 14 | 15 | function checkStatus(response) { 16 | if (response.ok) { 17 | return response.json(); 18 | } 19 | var error = new Error(response.statusText); 20 | error.response = response; 21 | return Promise.reject(error); 22 | } 23 | 24 | export function getJWKS(options, cb) { 25 | var url = options.jwksURI || urljoin(options.iss, '.well-known', 'jwks.json'); 26 | var localFetch = fetch == 'undefined' ? unfetch : fetch; 27 | return localFetch(url) 28 | .then(checkStatus) 29 | .then(function(data) { 30 | var matchingKey = null; 31 | var a; 32 | var key; 33 | // eslint-disable-next-line no-plusplus 34 | for (a = 0; a < data.keys.length && matchingKey === null; a++) { 35 | key = data.keys[a]; 36 | 37 | if (key.kid === options.kid) { 38 | matchingKey = key; 39 | } 40 | } 41 | if (!matchingKey) { 42 | throw new Error( 43 | 'Could not find a public key for Key ID (kid) "' + options.kid + '"' 44 | ); 45 | } 46 | if (cb) { 47 | return cb(null, process(matchingKey)); 48 | } else { 49 | return process(matchingKey); 50 | } 51 | }) 52 | .catch(function(e) { 53 | if (cb) { 54 | cb(e); 55 | } else { 56 | throw e; 57 | } 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /test/base64.test.js: -------------------------------------------------------------------------------- 1 | const { assert } = require('@sinonjs/referee-sinon'); 2 | 3 | import * as base64 from '../src/helpers/base64'; 4 | 5 | describe('helpers base64 url', function() { 6 | it('string to byte array', function() { 7 | var result = base64.stringToByteArray('tes'); 8 | 9 | assert.contains(result, 116); 10 | assert.contains(result, 101); 11 | assert.contains(result, 115); 12 | 13 | assert.equals(result.length, 3); 14 | }); 15 | 16 | it('byte array to string', function() { 17 | assert.equals(base64.byteArrayToString([116, 101, 115, 116]), 'test'); 18 | }); 19 | 20 | it('encode string', function() { 21 | assert.equals(base64.encodeString('test'), 'dGVzdA=='); 22 | assert.equals(base64.encodeString('寨åéüæØ'), 'w6XDhsOYw6XDqcO8w6bDmA=='); 23 | }); 24 | 25 | it('decode string', function() { 26 | assert.equals(base64.decodeToString('dGVzdA=='), 'test'); 27 | assert.equals( 28 | base64.decodeToString('w6XDhsOYw6XDqcO8w6bDmA=='), 29 | '寨åéüæØ' 30 | ); 31 | }); 32 | 33 | it('padding', function() { 34 | assert.equals(base64.padding(''), ''); 35 | assert.equals(base64.padding('a'), 'a==='); 36 | assert.equals(base64.padding('ab'), 'ab=='); 37 | assert.equals(base64.padding('abc'), 'abc='); 38 | assert.equals(base64.padding('abcd'), 'abcd'); 39 | assert.equals(base64.padding('abced'), 'abced==='); 40 | assert.equals(base64.padding(base64.padding('abc')), 'abc='); 41 | }); 42 | 43 | it('decode to hex', function() { 44 | assert.equals(base64.decodeToHEX('AQAB'), '010001'); 45 | }); 46 | 47 | it('base64ToBase64Url', function() { 48 | assert.equals(base64.base64ToBase64Url('aa/bb+cc='), 'aa_bb-cc'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/helper/token-validation.js: -------------------------------------------------------------------------------- 1 | const { assert } = require('@sinonjs/referee-sinon'); 2 | 3 | import IdTokenVerifier from '../../src/index'; 4 | import * as error from '../../src/helpers/error'; 5 | 6 | export const DEFAULT_CONFIG = { 7 | issuer: '__TEST_ISSUER__', 8 | audience: '__TEST_AUDIENCE__' 9 | }; 10 | 11 | function assertTokenValidationError( 12 | configuration, 13 | nonce, 14 | message, 15 | id_token, 16 | done 17 | ) { 18 | var verifier = new IdTokenVerifier(configuration); 19 | 20 | verifier.verify(id_token, nonce, function(err, result) { 21 | assert.hasPrototype(err, error.TokenValidationError.prototype); 22 | assert.equals(err.message, message); 23 | assert.isNull(result); 24 | 25 | done(); 26 | }); 27 | } 28 | 29 | function assertValidatorInitalizationError(configuration, message, done) { 30 | assert.exception( 31 | function() { 32 | new IdTokenVerifier(configuration); 33 | }, 34 | function(err) { 35 | assert.hasPrototype(err, error.ConfigurationError.prototype); 36 | assert.equals(err.message, message); 37 | 38 | done(); 39 | } 40 | ); 41 | } 42 | 43 | function assertTokenValid(token, configuration, nonce, done) { 44 | var verifier = new IdTokenVerifier(configuration); 45 | 46 | verifier.verify(token, nonce, function(err, result) { 47 | assert.isNull(err); 48 | 49 | assert.equals(result, { 50 | iss: 'https://wptest.auth0.com/', 51 | sub: 'auth0|55d48c57d5b0ad0223c408d7', 52 | aud: 'gYSNlU4YC4V1YPdqq8zPQcup6rJw1Mbt', 53 | exp: 1482969031, 54 | iat: 1482933031, 55 | nonce: 'asfd' 56 | }); 57 | 58 | done(); 59 | }); 60 | } 61 | 62 | export default { 63 | assertValidatorInitalizationError: assertValidatorInitalizationError, 64 | assertTokenValidationError: assertTokenValidationError, 65 | assertTokenValid: assertTokenValid 66 | }; 67 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature Request.yml: -------------------------------------------------------------------------------- 1 | name: 🧩 Feature request 2 | description: Suggest an idea or a feature for this library 3 | labels: ["feature request"] 4 | 5 | body: 6 | - type: checkboxes 7 | id: checklist 8 | attributes: 9 | label: Checklist 10 | options: 11 | - label: I have looked into the [Readme](https://github.com/auth0/idtoken-verifier#readme) and have not found a suitable solution or answer. 12 | required: true 13 | - label: I have searched the [issues](https://github.com/auth0/idtoken-verifier/issues) and have not found a suitable solution or answer. 14 | required: true 15 | - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer. 16 | required: true 17 | - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). 18 | required: true 19 | 20 | - type: textarea 21 | id: description 22 | attributes: 23 | label: Describe the problem you'd like to have solved 24 | description: A clear and concise description of what the problem is. 25 | placeholder: I'm always frustrated when... 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | id: ideal-solution 31 | attributes: 32 | label: Describe the ideal solution 33 | description: A clear and concise description of what you want to happen. 34 | validations: 35 | required: true 36 | 37 | - type: textarea 38 | id: alternatives-and-workarounds 39 | attributes: 40 | label: Alternatives and current workarounds 41 | description: A clear and concise description of any alternatives you've considered or any workarounds that are currently in place. 42 | validations: 43 | required: false 44 | 45 | - type: textarea 46 | id: additional-context 47 | attributes: 48 | label: Additional context 49 | description: Add any other context or screenshots about the feature request here. 50 | validations: 51 | required: false 52 | -------------------------------------------------------------------------------- /docs/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: hsl(104, 100%, 24%); 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /src/helpers/base64.js: -------------------------------------------------------------------------------- 1 | import base64 from 'base64-js'; 2 | 3 | export function padding(str) { 4 | var mod = str.length % 4; 5 | var pad = 4 - mod; 6 | 7 | if (mod === 0) { 8 | return str; 9 | } 10 | 11 | return str + new Array(1 + pad).join('='); 12 | } 13 | 14 | export function byteArrayToString(array) { 15 | var result = ''; 16 | for (var i = 0; i < array.length; i++) { 17 | result += String.fromCharCode(array[i]); 18 | } 19 | return result; 20 | } 21 | 22 | export function stringToByteArray(str) { 23 | var arr = new Array(str.length); 24 | for (var a = 0; a < str.length; a++) { 25 | arr[a] = str.charCodeAt(a); 26 | } 27 | return arr; 28 | } 29 | 30 | export function byteArrayToHex(raw) { 31 | var HEX = ''; 32 | 33 | for (var i = 0; i < raw.length; i++) { 34 | var _hex = raw[i].toString(16); 35 | HEX += _hex.length === 2 ? _hex : '0' + _hex; 36 | } 37 | 38 | return HEX; 39 | } 40 | 41 | export function encodeString(str) { 42 | return base64 43 | .fromByteArray( 44 | stringToByteArray( 45 | encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) { 46 | return String.fromCharCode('0x' + p1); 47 | }) 48 | ) 49 | ) 50 | .replace(/\+/g, '-') // Convert '+' to '-' 51 | .replace(/\//g, '_'); // Convert '/' to '_'; 52 | } 53 | 54 | export function decodeToString(str) { 55 | str = padding(str) 56 | .replace(/\-/g, '+') // Convert '-' to '+' 57 | .replace(/_/g, '/'); // Convert '_' to '/' 58 | 59 | return decodeURIComponent( 60 | byteArrayToString(base64.toByteArray(str)) 61 | .split('') 62 | .map(function(c) { 63 | return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); 64 | }) 65 | .join('') 66 | ); 67 | } 68 | 69 | export function decodeToHEX(str) { 70 | return byteArrayToHex(base64.toByteArray(padding(str))); 71 | } 72 | 73 | export function base64ToBase64Url(base64String) { 74 | var SAFE_URL_ENCODING_MAPPING = { 75 | '+': '-', 76 | '/': '_', 77 | '=': '' 78 | }; 79 | 80 | return base64String.replace(/[+/=]/g, function(m) { 81 | return SAFE_URL_ENCODING_MAPPING[m]; 82 | }); 83 | } 84 | -------------------------------------------------------------------------------- /test/helper/jwt.js: -------------------------------------------------------------------------------- 1 | import pem from 'pem'; 2 | import jwt from 'jsonwebtoken'; 3 | 4 | export const defaultExp = 1482969031; // this is the exp in the defaultToken below 👇 5 | 6 | export const defaultExpDate = new Date(0); 7 | defaultExpDate.setUTCSeconds(defaultExp + 60); // default leeway 8 | 9 | export const defaultToken = 10 | 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlF6RTROMFpCTTBWRFF6RTJSVVUwTnpJMVF6WTFNelE0UVRrMU16QXdNRUk0UkRneE56RTRSZyJ9.eyJpc3MiOiJodHRwczovL3dwdGVzdC5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NTVkNDhjNTdkNWIwYWQwMjIzYzQwOGQ3IiwiYXVkIjoiZ1lTTmxVNFlDNFYxWVBkcXE4elBRY3VwNnJKdzFNYnQiLCJleHAiOjE0ODI5NjkwMzEsImlhdCI6MTQ4MjkzMzAzMSwibm9uY2UiOiJhc2ZkIn0.PPoh-pITcZ8qbF5l5rMZwXiwk5efbESuqZ0IfMUcamB6jdgLwTxq-HpOT_x5q6-sO1PBHchpSo1WHeDYMlRrOFd9bh741sUuBuXdPQZ3Zb0i2sNOAC2RFB1E11mZn7uNvVPGdPTg-Y5xppz30GSXoOJLbeBszfrVDCmPhpHKGGMPL1N6HV-3EEF77L34YNAi2JQ-b70nFK_dnYmmv0cYTGUxtGTHkl64UEDLi3u7bV-kbGky3iOOCzXKzDDY6BBKpCRTc2KlbrkO2A2PuDn27WVv1QCNEFHvJN7HxiDDzXOsaUmjrQ3sfrHhzD7S9BcCRkekRfD9g95SKD5J0Fj8NA'; 11 | 12 | const symmetricSecret = 'secret key'; 13 | 14 | const createCertificate = () => 15 | new Promise((res, rej) => { 16 | pem.createCertificate({ days: 1, selfSigned: true }, function(err, keys) { 17 | if (err) { 18 | return rej(err); 19 | } 20 | pem.getPublicKey(keys.certificate, function(e, p) { 21 | if (e) { 22 | return rej(e); 23 | } 24 | res({ 25 | serviceKey: keys.serviceKey, 26 | certificate: keys.certificate, 27 | publicKey: p.publicKey 28 | }); 29 | }); 30 | }); 31 | }); 32 | 33 | export const DEFAULT_PAYLOAD = { 34 | sub: 'id|123', 35 | nonce: 'asfd' 36 | }; 37 | 38 | export const DEFAULT_OPTIONS = { 39 | expiresIn: '1h', 40 | audience: '__TEST_AUDIENCE__', 41 | issuer: '__TEST_ISSUER__' 42 | }; 43 | 44 | export const createJWT = ( 45 | payload = DEFAULT_PAYLOAD, 46 | options = DEFAULT_OPTIONS 47 | ) => { 48 | return createCertificate().then(cert => { 49 | const key = 50 | options.algorithm === 'HS256' ? symmetricSecret : cert.serviceKey; 51 | 52 | return jwt.sign(payload, key, { 53 | algorithm: 'RS256', 54 | keyid: 'QzE4N0ZBM0VDQzE2RUU0NzI1QzY1MzQ4QTk1MzAwMEI4RDgxNzE4Rg', 55 | ...options 56 | }); 57 | }); 58 | }; 59 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | merge_group: 5 | workflow_dispatch: 6 | pull_request: 7 | branches: 8 | - master 9 | push: 10 | branches: 11 | - master 12 | 13 | permissions: 14 | contents: read 15 | 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 18 | cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} 19 | 20 | env: 21 | NODE_VERSION: 18 22 | CACHE_KEY: '${{ github.ref }}-${{ github.run_id }}-${{ github.run_attempt }}' 23 | 24 | jobs: 25 | build: 26 | name: Build Package 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - name: Checkout code 31 | uses: actions/checkout@v4 32 | 33 | - uses: ./.github/actions/build 34 | with: 35 | node: ${{ env.NODE_VERSION }} 36 | 37 | - name: Save build artifacts 38 | uses: actions/cache/save@v4 39 | with: 40 | path: . 41 | key: ${{ env.CACHE_KEY }} 42 | 43 | unit: 44 | needs: build # Require build to complete before running tests 45 | 46 | name: Run Unit Tests 47 | runs-on: ubuntu-latest 48 | 49 | steps: 50 | - uses: actions/checkout@v4 51 | 52 | - uses: actions/setup-node@v4 53 | with: 54 | node-version: ${{ env.NODE_VERSION }} 55 | cache: npm 56 | 57 | - uses: actions/cache/restore@v4 58 | with: 59 | path: . 60 | key: ${{ env.CACHE_KEY }} 61 | 62 | - run: npm run ci:test 63 | env: 64 | MOCHA_FILE: junit/test-results.xml 65 | 66 | - uses: codecov/codecov-action@4fe8c5f003fae66aa5ebb77cfd3e7bfbbda0b6b0 # pin@3.1.5 67 | 68 | escheck: 69 | needs: build # Require build to complete before running tests 70 | 71 | name: Check ECMAScript Version 72 | runs-on: ubuntu-latest 73 | 74 | steps: 75 | - uses: actions/checkout@v4 76 | 77 | - uses: actions/setup-node@v4 78 | with: 79 | node-version: ${{ env.NODE_VERSION }} 80 | cache: npm 81 | 82 | - uses: actions/cache/restore@v4 83 | with: 84 | path: . 85 | key: ${{ env.CACHE_KEY }} 86 | 87 | - run: npm run test:es-check:es5 88 | 89 | - run: npm run test:es-check:es2015:module 90 | -------------------------------------------------------------------------------- /src/helpers/rsa-verifier.js: -------------------------------------------------------------------------------- 1 | /* 2 | Based on the work of Tom Wu 3 | http://www-cs-students.stanford.edu/~tjw/jsbn/ 4 | http://www-cs-students.stanford.edu/~tjw/jsbn/LICENSE 5 | */ 6 | 7 | import { BigInteger } from 'jsbn'; 8 | import SHA256 from 'crypto-js/sha256'; 9 | 10 | var DigestInfoHead = { 11 | sha1: '3021300906052b0e03021a05000414', 12 | sha224: '302d300d06096086480165030402040500041c', 13 | sha256: '3031300d060960864801650304020105000420', 14 | sha384: '3041300d060960864801650304020205000430', 15 | sha512: '3051300d060960864801650304020305000440', 16 | md2: '3020300c06082a864886f70d020205000410', 17 | md5: '3020300c06082a864886f70d020505000410', 18 | ripemd160: '3021300906052b2403020105000414' 19 | }; 20 | 21 | var DigestAlgs = { 22 | sha256: SHA256 23 | }; 24 | 25 | function RSAVerifier(modulus, exp) { 26 | this.n = null; 27 | this.e = 0; 28 | 29 | if (modulus != null && exp != null && modulus.length > 0 && exp.length > 0) { 30 | this.n = new BigInteger(modulus, 16); 31 | this.e = parseInt(exp, 16); 32 | } else { 33 | throw new Error('Invalid key data'); 34 | } 35 | } 36 | 37 | function getAlgorithmFromDigest(hDigestInfo) { 38 | for (var algName in DigestInfoHead) { 39 | var head = DigestInfoHead[algName]; 40 | var len = head.length; 41 | 42 | if (hDigestInfo.substring(0, len) === head) { 43 | return { 44 | alg: algName, 45 | hash: hDigestInfo.substring(len) 46 | }; 47 | } 48 | } 49 | return []; 50 | } 51 | 52 | RSAVerifier.prototype.verify = function(msg, encsig) { 53 | encsig = encsig.replace(/[^0-9a-f]|[\s\n]]/gi, ''); 54 | 55 | var sig = new BigInteger(encsig, 16); 56 | if (sig.bitLength() > this.n.bitLength()) { 57 | throw new Error('Signature does not match with the key modulus.'); 58 | } 59 | 60 | var decryptedSig = sig.modPowInt(this.e, this.n); 61 | var digest = decryptedSig.toString(16).replace(/^1f+00/, ''); 62 | 63 | var digestInfo = getAlgorithmFromDigest(digest); 64 | if (digestInfo.length === 0) { 65 | return false; 66 | } 67 | 68 | if (!DigestAlgs.hasOwnProperty(digestInfo.alg)) { 69 | throw new Error('Hashing algorithm is not supported.'); 70 | } 71 | 72 | var msgHash = DigestAlgs[digestInfo.alg](msg).toString(); 73 | return digestInfo.hash === msgHash; 74 | }; 75 | 76 | export default RSAVerifier; 77 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug Report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Report a bug 2 | description: Have you found a bug or issue? Create a bug report for this library 3 | labels: ["bug"] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | **Please do not report security vulnerabilities here**. The [Responsible Disclosure Program](https://auth0.com/responsible-disclosure-policy) details the procedure for disclosing security issues. 10 | 11 | - type: checkboxes 12 | id: checklist 13 | attributes: 14 | label: Checklist 15 | options: 16 | - label: I have looked into the [Readme](https://github.com/auth0/idtoken-verifier#readme) and have not found a suitable solution or answer. 17 | required: true 18 | - label: I have searched the [issues](https://github.com/auth0/idtoken-verifier/issues) and have not found a suitable solution or answer. 19 | required: true 20 | - label: I have searched the [Auth0 Community](https://community.auth0.com) forums and have not found a suitable solution or answer. 21 | required: true 22 | - label: I agree to the terms within the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). 23 | required: true 24 | 25 | - type: textarea 26 | id: description 27 | attributes: 28 | label: Description 29 | description: Provide a clear and concise description of the issue, including what you expected to happen. 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | id: reproduction 35 | attributes: 36 | label: Reproduction 37 | description: Detail the steps taken to reproduce this error, and whether this issue can be reproduced consistently or if it is intermittent. 38 | placeholder: | 39 | 1. Step 1... 40 | 2. Step 2... 41 | 3. ... 42 | validations: 43 | required: true 44 | 45 | - type: textarea 46 | id: additional-context 47 | attributes: 48 | label: Additional context 49 | description: Other libraries that might be involved, or any other relevant information you think would be useful. 50 | validations: 51 | required: false 52 | 53 | - type: input 54 | id: environment-version 55 | attributes: 56 | label: idtoken-verifier version 57 | validations: 58 | required: true 59 | 60 | - type: dropdown 61 | id: environment-browser 62 | attributes: 63 | label: Which browsers have you tested in? 64 | multiple: true 65 | options: 66 | - Chrome 67 | - Edge 68 | - Safari 69 | - Firefox 70 | - Opera 71 | - IE 72 | - Other 73 | validations: 74 | required: true 75 | -------------------------------------------------------------------------------- /docs/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: hsl(104, 100%, 24%); } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: hsl(240, 100%, 50%); } 17 | 18 | /* a comment */ 19 | .com { 20 | color: hsl(0, 0%, 60%); } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: hsl(240, 100%, 32%); } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: hsl(240, 100%, 40%); } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #000000; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #000000; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #000000; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /test/mock/cache-mock.js: -------------------------------------------------------------------------------- 1 | function CacheMock(options) { 2 | this.setAssertion = options.setAssertion || false; 3 | this.getResponse = options.getResponse || false; 4 | } 5 | 6 | CacheMock.prototype.get = function(key) { 7 | if (this.getResponse) { 8 | if (this.getResponse.hasOwnProperty(key)) { 9 | return this.getResponse[key]; 10 | } else { 11 | throw new Error('Invalid get key ' + key); 12 | } 13 | } 14 | 15 | return null; 16 | }; 17 | 18 | CacheMock.prototype.has = function(key) { 19 | if (this.getAssertion) { 20 | if (this.getAssertion.indexOf(key) === -1) { 21 | throw new Error('invalid has call ' + key); 22 | } 23 | } 24 | 25 | if (this.getResponse) { 26 | return this.getResponse.hasOwnProperty(key); 27 | } 28 | 29 | return false; 30 | }; 31 | 32 | CacheMock.prototype.set = function(key, value) { 33 | if (this.setAssertion) { 34 | if ( 35 | !this.setAssertion.hasOwnProperty(key) || 36 | this.setAssertion[key].indexOf(value) === -1 37 | ) { 38 | throw new Error('invalid set call ' + key); 39 | } 40 | } 41 | }; 42 | 43 | function validKey() { 44 | var pk = { 45 | modulus: 46 | 'c333dc7a7b463661d2ecc75d4b96c60f34f5d77d2329de0dc6990a8ecd171760a978daf9c059ed60c4bd4174a9acc8b6c608bbf38124eb897fd399d8b008c328d3c459b8efd61f8daf634fc7f03621d097634aee47892b7d0df9e1f51eceee8fa9032a385f7a6704834e193c497564eabf78aa2b3fc097d9d6b240edce6ad6439c587b15d5243eed8888232372ac8d332f957a5088e4b8d64d5a112db78e0022acdcda84c4534624573367266c010246a6f7a000849de0cb98e727f29d02c512d20c2ab47e814409a3783a8df4567369416e04f8aaf0f2a1d7411e8d66e4e716ecd6bc2a007b9a2ee7cffa1fde83b6edaa51fd1de4c1de5faa2118c91ac5192f', 47 | exp: '010001' 48 | }; 49 | 50 | var cache = new CacheMock({ 51 | getResponse: { 52 | 'https://wptest.auth0.com/QzE4N0ZBM0VDQzE2RUU0NzI1QzY1MzQ4QTk1MzAwMEI4RDgxNzE4Rg': pk 53 | } 54 | }); 55 | 56 | return cache; 57 | } 58 | 59 | function invalidKey() { 60 | var pk = { 61 | modulus: 62 | 'bc60a150631359935f46c5f177e06dcc2e110d838caad221587a25fa5bc0ed765a94cc40616753c0ababd1845e73ddde85af680845e6cbb39bd48d9d27c3909afd86e83988ee35242fd7cb53e8f392499c933488fe6007d91dbb679f873626f94a0b9d58183aac4731bd1e8b5abc79ecdeaf7e8ebcc304fdfc51e566968ae8d5c0fdd5855b87f735fb5f58b2fb06acbdd2c3b8be28cce8b31213aae797957e7f3e1b90d83e98fcf847e91498cfe5777fb1beda3bf41b7126f3b0e6aaad2cd9d18e20f3a2a46ff96ac1770e558fc3efae0d5fc2c71f5561e59aafaae2b1675cbccd43b842b07ad424fd69effed2a255a6f6f4e807dfc6b329e4eff8f24e0d6915', 63 | exp: '010001' 64 | }; 65 | 66 | var cache = new CacheMock({ 67 | getResponse: { 68 | 'https://wptest.auth0.com/QzE4N0ZBM0VDQzE2RUU0NzI1QzY1MzQ4QTk1MzAwMEI4RDgxNzE4Rg': pk 69 | } 70 | }); 71 | 72 | return cache; 73 | } 74 | 75 | export default { 76 | CacheMock: CacheMock, 77 | validKey: validKey, 78 | invalidKey: invalidKey 79 | }; 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "idtoken-verifier", 3 | "version": "2.2.4", 4 | "description": "A lightweight library to decode and verify RS JWT meant for the browser.", 5 | "main": "build/idtoken-verifier.js", 6 | "umd:main": "build/idtoken-verifier.umd.js", 7 | "module": "build/idtoken-verifier.esm.js", 8 | "source": "src/index.js", 9 | "types": "types/index.d.ts", 10 | "files": [ 11 | "build", 12 | "src", 13 | "types" 14 | ], 15 | "scripts": { 16 | "start": "npm run build -- --watch", 17 | "prebuild": "rm -rf build", 18 | "build": "microbundle --target web --external none --generateTypes false", 19 | "postbuild": "node scripts/print-bundle-size", 20 | "test": "cross-env NODE_ENV=test mocha --require @babel/register test/**/*.test.js --exit", 21 | "test:watch": "cross-env NODE_ENV=test mocha --require @babel/register --watch --reporter min test/*.test.js", 22 | "test:coverage": "nyc npm run test", 23 | "test:es-check:es5": "es-check es5 'build/idtoken-verifier.umd.js'", 24 | "test:es-check:es2015:module": "es-check es2015 --module 'build/idtoken-verifier.esm.js'", 25 | "ci:test": "nyc npm run test -- --forbid-only --reporter mocha-junit-reporter", 26 | "jsdoc:generate": "jsdoc --configure .jsdoc.json --verbose", 27 | "prepack": "npm run build", 28 | "upload": "ccu build", 29 | "precommit": "pretty-quick --staged" 30 | }, 31 | "author": "Auth0", 32 | "license": "MIT", 33 | "dependencies": { 34 | "base64-js": "^1.5.1", 35 | "crypto-js": "^4.2.0", 36 | "es6-promise": "^4.2.8", 37 | "jsbn": "^1.1.0", 38 | "unfetch": "^4.2.0", 39 | "url-join": "^4.0.1" 40 | }, 41 | "keywords": [ 42 | "auth0", 43 | "auth", 44 | "authentication", 45 | "jwt", 46 | "verification", 47 | "RS256", 48 | "browser" 49 | ], 50 | "repository": "git@github.com:auth0/idtoken-verifier.git", 51 | "devDependencies": { 52 | "@auth0/component-cdn-uploader": "github:auth0/component-cdn-uploader#v2.2.2", 53 | "@babel/preset-env": "^7.15.0", 54 | "@babel/register": "^7.15.3", 55 | "@sinonjs/referee-sinon": "^11.0.0", 56 | "babel-plugin-istanbul": "^5.1.4", 57 | "babel-plugin-rewire": "^1.2.0", 58 | "cli-table": "^0.3.6", 59 | "cross-env": "^7.0.3", 60 | "es-check": "^7.1.1", 61 | "gzip-size": "^5.1.1", 62 | "husky": "^3.0.0", 63 | "istanbul": "^0.4.5", 64 | "jsdoc": "^3.6.7", 65 | "jsonwebtoken": "^9.0.0", 66 | "microbundle": "^0.13.3", 67 | "minami": "^1.2.3", 68 | "mocha-junit-reporter": "^2.2.1", 69 | "mocha": "^10.1.0", 70 | "node-fetch": "^2.6.7", 71 | "nyc": "^15.1.0", 72 | "pem": "^1.14.4", 73 | "prettier": "^1.18.2", 74 | "pretty-quick": "^1.11.1" 75 | }, 76 | "ccu": { 77 | "name": "idtoken-verifier", 78 | "cdn": "https://cdn.auth0.com", 79 | "mainBundleFile": "idtoken-verifier.umd.js", 80 | "bucket": "assets.us.auth0.com", 81 | "localPath": "build", 82 | "digest": { 83 | "hashes": [ 84 | "sha384" 85 | ], 86 | "extensions": [ 87 | ".js" 88 | ] 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.github/workflows/npm-release.yml: -------------------------------------------------------------------------------- 1 | name: Create npm and GitHub Release 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | node-version: 7 | required: true 8 | type: string 9 | require-build: 10 | default: true 11 | type: string 12 | release-directory: 13 | default: './' 14 | type: string 15 | secrets: 16 | github-token: 17 | required: true 18 | npm-token: 19 | required: true 20 | 21 | ### TODO: Replace instances of './.github/actions/' w/ `auth0/dx-sdk-actions/` and append `@latest` after the common `dx-sdk-actions` repo is made public. 22 | ### TODO: Also remove `get-prerelease`, `get-version`, `release-create`, `tag-create` and `tag-exists` actions from this repo's .github/actions folder once the repo is public. 23 | 24 | jobs: 25 | release: 26 | if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/')) 27 | runs-on: ubuntu-latest 28 | environment: release 29 | 30 | steps: 31 | # Checkout the code 32 | - uses: actions/checkout@v4 33 | with: 34 | fetch-depth: 0 35 | 36 | # Get the version from the branch name 37 | - id: get_version 38 | uses: ./.github/actions/get-version 39 | 40 | # Get the prerelease flag from the branch name 41 | - id: get_prerelease 42 | uses: ./.github/actions/get-prerelease 43 | with: 44 | version: ${{ steps.get_version.outputs.version }} 45 | 46 | # Get the release notes 47 | - id: get_release_notes 48 | uses: ./.github/actions/get-release-notes 49 | with: 50 | token: ${{ secrets.github-token }} 51 | version: ${{ steps.get_version.outputs.version }} 52 | repo_owner: ${{ github.repository_owner }} 53 | repo_name: ${{ github.event.repository.name }} 54 | 55 | # Check if the tag already exists 56 | - id: tag_exists 57 | uses: ./.github/actions/tag-exists 58 | with: 59 | tag: ${{ steps.get_version.outputs.version }} 60 | token: ${{ secrets.github-token }} 61 | 62 | # If the tag already exists, exit with an error 63 | - if: steps.tag_exists.outputs.exists == 'true' 64 | run: exit 1 65 | 66 | # Publish the release to npm 67 | - uses: ./.github/actions/npm-publish 68 | with: 69 | node-version: ${{ inputs.node-version }} 70 | require-build: ${{ inputs.require-build }} 71 | release-directory: ${{ inputs.release-directory }} 72 | version: ${{ steps.get_version.outputs.version }} 73 | npm-token: ${{ secrets.npm-token }} 74 | 75 | # Create a release for the tag 76 | - uses: ./.github/actions/release-create 77 | with: 78 | token: ${{ secrets.github-token }} 79 | name: ${{ steps.get_version.outputs.version }} 80 | body: ${{ steps.get_release_notes.outputs.release-notes }} 81 | tag: ${{ steps.get_version.outputs.version }} 82 | commit: ${{ github.sha }} 83 | prerelease: ${{ steps.get_prerelease.outputs.prerelease }} 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![idtoken-verifier](https://cdn.auth0.com/website/sdks/banners/idtoken-verifier-banner.png) 2 | 3 | A lightweight library to decode and verify RSA ID tokens meant for the browser. 4 | 5 | [![Build Status][circleci-image]][circleci-url] 6 | [![NPM version][npm-image]][npm-url] 7 | [![Coverage][codecov-image]][codecov-url] 8 | [![License][license-image]][license-url] 9 | [![Downloads][downloads-image]][downloads-url] 10 | 11 | :books: [Documentation](#documentation) - :rocket: [Getting Started](#getting-started) - :computer: [API Reference](#api-reference) - :speech_balloon: [Feedback](#feedback) 12 | 13 | ## Documentation 14 | 15 | - [API Reference](https://auth0.github.io/idtoken-verifier) 16 | - [Docs Site](https://auth0.com/docs) - explore our Docs site and learn more about Auth0 17 | 18 | ## Getting Started 19 | 20 | ### Installation 21 | 22 | Using [npm](https://npmjs.org/) in your project directory run the following command: 23 | 24 | ``` 25 | npm install idtoken-verifier 26 | ``` 27 | 28 | ### Verify an ID token 29 | 30 | Import the library, create an instance of `IdTokenVerifier` and call the `verify` method to verify an ID token: 31 | 32 | ```js 33 | import IdTokenVerifier from 'idtoken-verifier'; 34 | 35 | const verifier = new IdTokenVerifier({ 36 | issuer: 'https://my.auth0.com/', 37 | audience: 'gYSNlU4YC4V1YPdqq8zPQcup6rJw1Mbt' 38 | }); 39 | 40 | verifier.verify(id_token, nonce, (error, payload) => { 41 | if (error) { 42 | // handle the error 43 | return; 44 | } 45 | 46 | // do something with `payload` 47 | }); 48 | ``` 49 | 50 | ## API Reference 51 | 52 | - [IdTokenVerifier constructor](https://auth0.github.io/idtoken-verifier/IdTokenVerifier.html) 53 | - [verify](https://auth0.github.io/idtoken-verifier/global.html#verify) 54 | - [decode](https://auth0.github.io/idtoken-verifier/global.html#decode) 55 | - [validateAccessToken](https://auth0.github.io/idtoken-verifier/global.html#validateAccessToken) 56 | 57 | ## Feedback 58 | 59 | ### Contributing 60 | 61 | We appreciate feedback and contribution to this repo! Before you get started, please see the following: 62 | 63 | - [Auth0's general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) 64 | - [Auth0's code of conduct guidelines](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md) 65 | - [The contribution guide](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md) 66 | 67 | ### Raise an issue 68 | 69 | To provide feedback or report a bug, please [raise an issue on our issue tracker](https://github.com/auth0/idtoken-verifier/issues). 70 | 71 | ### Vulnerability Reporting 72 | 73 | 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. 74 | 75 | ## What is Auth0? 76 | 77 |

78 | 79 | 80 | 81 | Auth0 Logo 82 | 83 |

84 |

85 | Auth0 is an easy to implement, adaptable authentication and authorization platform. To learn more checkout Why Auth0? 86 |

87 |

88 | This project is licensed under the Apache 2.0 license. See the LICENSE file for more info. 89 |

90 | 91 | 92 | 93 | 94 | [npm-image]: https://img.shields.io/npm/v/idtoken-verifier.svg?style=flat-square 95 | [npm-url]: https://npmjs.org/package/idtoken-verifier 96 | [circleci-image]: http://img.shields.io/circleci/project/github/auth0/idtoken-verifier.svg?branch=master&style=flat-square 97 | [circleci-url]: https://circleci.com/gh/auth0/idtoken-verifier 98 | [codecov-image]: https://img.shields.io/codecov/c/github/auth0/idtoken-verifier.svg?style=flat-square 99 | [codecov-url]: https://codecov.io/github/auth0/idtoken-verifier?branch=master 100 | [license-image]: http://img.shields.io/npm/l/idtoken-verifier.svg?style=flat-square 101 | [license-url]: #license 102 | [downloads-image]: http://img.shields.io/npm/dm/idtoken-verifier.svg?style=flat-square 103 | [downloads-url]: https://npmjs.org/package/idtoken-verifier 104 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | export default IdTokenVerifier; 2 | export type verifyCallback = (err: Error | null, payload: object | null) => any; 3 | export type DecodedToken = { 4 | /** 5 | * - content of the JWT header. 6 | */ 7 | header: any; 8 | /** 9 | * - token claims. 10 | */ 11 | payload: any; 12 | /** 13 | * - encoded parts of the token. 14 | */ 15 | encoded: any; 16 | }; 17 | export type validateAccessTokenCallback = (err?: Error) => any; 18 | /** 19 | * Creates a new id_token verifier 20 | * @constructor 21 | * @param {Object} parameters 22 | * @param {string} parameters.issuer name of the issuer of the token 23 | * that should match the `iss` claim in the id_token 24 | * @param {string} parameters.audience identifies the recipients that the JWT is intended for 25 | * and should match the `aud` claim 26 | * @param {Object} [parameters.jwksCache] cache for JSON Web Token Keys. By default it has no cache 27 | * @param {string} [parameters.jwksURI] A valid, direct URI to fetch the JSON Web Key Set (JWKS). 28 | * @param {string} [parameters.expectedAlg='RS256'] algorithm in which the id_token was signed 29 | * and will be used to validate 30 | * @param {number} [parameters.leeway=60] number of seconds that the clock can be out of sync 31 | * @param {number} [parameters.maxAge] max age 32 | * while validating expiration of the id_token 33 | */ 34 | declare function IdTokenVerifier(parameters: { 35 | issuer: string; 36 | audience: string; 37 | jwksCache?: any; 38 | jwksURI?: string; 39 | expectedAlg?: string; 40 | leeway?: number; 41 | maxAge?: number; 42 | }): void; 43 | declare class IdTokenVerifier { 44 | /** 45 | * Creates a new id_token verifier 46 | * @constructor 47 | * @param {Object} parameters 48 | * @param {string} parameters.issuer name of the issuer of the token 49 | * that should match the `iss` claim in the id_token 50 | * @param {string} parameters.audience identifies the recipients that the JWT is intended for 51 | * and should match the `aud` claim 52 | * @param {Object} [parameters.jwksCache] cache for JSON Web Token Keys. By default it has no cache 53 | * @param {string} [parameters.jwksURI] A valid, direct URI to fetch the JSON Web Key Set (JWKS). 54 | * @param {string} [parameters.expectedAlg='RS256'] algorithm in which the id_token was signed 55 | * and will be used to validate 56 | * @param {number} [parameters.leeway=60] number of seconds that the clock can be out of sync 57 | * @param {number} [parameters.maxAge] max age 58 | * while validating expiration of the id_token 59 | */ 60 | constructor(parameters: { 61 | issuer: string; 62 | audience: string; 63 | jwksCache?: any; 64 | jwksURI?: string; 65 | expectedAlg?: string; 66 | leeway?: number; 67 | maxAge?: number; 68 | }); 69 | jwksCache: any; 70 | expectedAlg: any; 71 | issuer: any; 72 | audience: any; 73 | leeway: any; 74 | jwksURI: any; 75 | maxAge: any; 76 | __clock: any; 77 | 78 | /** 79 | * @callback verifyCallback 80 | * @param {?Error} err error returned if the verify cannot be performed 81 | * @param {?object} payload payload returned if the token is valid 82 | */ 83 | 84 | /** 85 | * Verifies an id_token 86 | * 87 | * It will validate: 88 | * - signature according to the algorithm configured in the verifier. 89 | * - if nonce is present and matches the one provided 90 | * - if `iss` and `aud` claims matches the configured issuer and audience 91 | * - if token is not expired and valid (if the `nbf` claim is in the past) 92 | * 93 | * @method verify 94 | * @param {string} token id_token to verify 95 | * @param {string} requestedNonce nonce value that should match the one in the id_token claims 96 | * @param {verifyCallback} cb callback used to notify the results of the validation 97 | */ 98 | verify(token: string, requestedNonce: string, cb: verifyCallback): any; 99 | 100 | /** 101 | * Verifies an id_token 102 | * 103 | * It will validate: 104 | * - signature according to the algorithm configured in the verifier. 105 | * - if `iss` and `aud` claims matches the configured issuer and audience 106 | * - if token is not expired and valid (if the `nbf` claim is in the past) 107 | * 108 | * @method verify 109 | * @param {string} token id_token to verify 110 | * @param {verifyCallback} cb callback used to notify the results of the validation 111 | */ 112 | verify(token: string, cb: verifyCallback): any; 113 | 114 | getRsaVerifier(iss: any, kid: any, cb: any): void; 115 | /** 116 | * @typedef DecodedToken 117 | * @type {Object} 118 | * @property {Object} header - content of the JWT header. 119 | * @property {Object} payload - token claims. 120 | * @property {Object} encoded - encoded parts of the token. 121 | */ 122 | /** 123 | * Decodes a well formed JWT without any verification 124 | * 125 | * @method decode 126 | * @param {string} token decodes the token 127 | * @return {DecodedToken} if token is valid according to `exp` and `nbf` 128 | */ 129 | decode(token: string): DecodedToken; 130 | /** 131 | * @callback validateAccessTokenCallback 132 | * @param {Error} [err] error returned if the validation cannot be performed 133 | * or the token is invalid. If there is no error, then the access_token is valid. 134 | */ 135 | /** 136 | * Validates an access_token based on {@link http://openid.net/specs/openid-connect-core-1_0.html#ImplicitTokenValidation}. 137 | * The id_token from where the alg and atHash parameters are taken, 138 | * should be decoded and verified before using thisfunction 139 | * 140 | * @method validateAccessToken 141 | * @param {string} access_token the access_token 142 | * @param {string} alg The algorithm defined in the header of the 143 | * previously verified id_token under the "alg" claim. 144 | * @param {string} atHash The "at_hash" value included in the payload 145 | * of the previously verified id_token. 146 | * @param {validateAccessTokenCallback} cb callback used to notify the results of the validation. 147 | */ 148 | validateAccessToken( 149 | accessToken: any, 150 | alg: string, 151 | atHash: string, 152 | cb: validateAccessTokenCallback 153 | ): any; 154 | } 155 | -------------------------------------------------------------------------------- /test/jwks.test.js: -------------------------------------------------------------------------------- 1 | const { assert, sinon } = require('@sinonjs/referee-sinon'); 2 | const { getJWKS } = require('../src/helpers/jwks'); 3 | 4 | describe('jwks', function() { 5 | afterEach(function() { 6 | global.fetch = undefined; 7 | }); 8 | 9 | describe('getJWKS', function() { 10 | describe('requests correct url', function() { 11 | it('with jwksURI', function(done) { 12 | const fetchStub = sinon.stub().resolves({ ok: true }); 13 | global.fetch = fetchStub; 14 | 15 | getJWKS({ jwksURI: 'https://example.com/jwks.json' }, function( 16 | err, 17 | data 18 | ) { 19 | assert.isTrue(fetchStub.calledWith('https://example.com/jwks.json')); 20 | done(); 21 | }); 22 | }); 23 | 24 | it('without jwksURI', function(done) { 25 | const fetchStub = sinon.stub().resolves({ ok: true }); 26 | global.fetch = fetchStub; 27 | 28 | getJWKS({ iss: 'https://iss.com/' }, function(err, data) { 29 | assert.isTrue( 30 | fetchStub.calledWith('https://iss.com/.well-known/jwks.json') 31 | ); 32 | done(); 33 | }); 34 | }); 35 | }); 36 | 37 | it('returns error in the callback when fetch fails', function(done) { 38 | global.fetch = () => { 39 | return Promise.reject({ error: true }); 40 | }; 41 | 42 | getJWKS( 43 | { 44 | jwksURI: 'https://example.com/jwks.json', 45 | kid: 'some-random-key' 46 | }, 47 | function(err) { 48 | assert.equals(err, { error: true }); 49 | done(); 50 | } 51 | ); 52 | }); 53 | 54 | it('returns error in the callback when jwks response is not ok', function() { 55 | global.fetch = () => { 56 | return Promise.reject({ 57 | ok: false, 58 | statusText: 'the-error' 59 | }); 60 | }; 61 | 62 | return assert.rejects( 63 | getJWKS( 64 | { 65 | jwksURI: 'https://example.com/jwks.json', 66 | kid: 'some-random-key' 67 | }, 68 | null 69 | ), 70 | { ok: false, statusText: 'the-error' } 71 | ); 72 | }); 73 | 74 | it('returns error when the kid is not present in the JWKS', function() { 75 | global.fetch = () => { 76 | return Promise.resolve({ 77 | ok: true, 78 | json: () => 79 | Promise.resolve({ 80 | keys: [ 81 | { 82 | kid: 'NEVBNUNBOTgxRkE5NkQzQzc4OTBEMEFFRDQ5N0Q2Qjk0RkQ1MjFGMQ' 83 | } 84 | ] 85 | }) 86 | }); 87 | }; 88 | 89 | return getJWKS( 90 | { 91 | jwksURI: 'https://example.com/jwks.json', 92 | kid: 'some-random-key' 93 | }, 94 | function(err) { 95 | assert.equals( 96 | err.message, 97 | `Could not find a public key for Key ID (kid) "some-random-key"` 98 | ); 99 | } 100 | ); 101 | }); 102 | 103 | it('returns error when the kid is not present in the JWKS (using promises)', function(done) { 104 | global.fetch = () => { 105 | return Promise.resolve({ 106 | ok: true, 107 | json: () => 108 | Promise.resolve({ 109 | keys: [ 110 | { 111 | kid: 'NEVBNUNBOTgxRkE5NkQzQzc4OTBEMEFFRDQ5N0Q2Qjk0RkQ1MjFGMQ' 112 | } 113 | ] 114 | }) 115 | }); 116 | }; 117 | 118 | getJWKS( 119 | { 120 | jwksURI: 'https://example.com/jwks.json', 121 | kid: 'some-random-key' 122 | }, 123 | null 124 | ).catch(err => { 125 | assert.equals( 126 | err.message, 127 | `Could not find a public key for Key ID (kid) "some-random-key"` 128 | ); 129 | done(); 130 | }); 131 | }); 132 | 133 | it('returns jwks', function() { 134 | global.fetch = () => { 135 | return Promise.resolve({ 136 | ok: true, 137 | json: () => 138 | Promise.resolve({ 139 | // from: https://brucke.auth0.com/.well-known/jwks.json 140 | keys: [ 141 | { 142 | alg: 'RS256', 143 | kty: 'RSA', 144 | use: 'sig', 145 | x5c: [ 146 | 'MIIC6DCCAdCgAwIBAgIJftS/aE0IPdZxMA0GCSqGSIb3DQEBBQUAMBsxGTAXBgNVBAMTEGJydWNrZS5hdXRoMC5jb20wHhcNMTYwNDIwMTIyMzE1WhcNMjkxMjI4MTIyMzE1WjAbMRkwFwYDVQQDExBicnVja2UuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlG4+sywQahA2bzbb53WiMS1MFJVFGJSHLwBuY/J4W84STvRUdcXPV3TE7s4A7/6fSdAXXVb69A5bL7mS/3EkGujgr2CnrRQNNdw055D28VHiZyAKrG2vg6e5+C8iHU0ew6nCqJ1bXK7vYhPs3qYlNdmxbxUyUimQZ038ABre8JlNiwsrnIoct/IKnhMNkUPovrcfNo83PwQwRJDabbYnTEtRIwlzVZxOwHXDySMf+xalRzxrQ+PvY1D5+HPfZHvmm+9ij4src9p5FTagMCkEUg9XrgpdhB+Aa0TyCpK8t1re1PjrFZgnIBZ5wTadTlbukYnD0FL83OU/olgBPMmd8QIDAQABoy8wLTAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBRzW81ap53F2F/90rR4cKJeRiwI8TANBgkqhkiG9w0BAQUFAAOCAQEAecY8JHbUESSXZiPhT9CCiX//VFfLpiBZCG2dWka5E4pPs3AFc7bEospWW7w+r2W0uSL6cDMu7wpb7N7lnWkiGc64Ej89ZXvZYAbpt6glM3z+W9H+fZ767W4/aiFSrD3HAMpGs61TUa7B9Xrn7Zhj4y8L1Z4z5v4xyzl5Zy5KKA19fiPJVtVzt6tVgfpbUDh0ufhno/WPWuRPzNNl+dKRH45JRVSwcYcFR4h2+i+t+3rhUmAuyOmjeN21vWvP8gqX6+LQW/olgkyrvg4rMmN6UZNBVa75g2ptnHo3ItdHh8UPMyn0VStOamtHYFVV+4uqzxV6EU2RHJnxO1YYt8LZfw==' 147 | ], 148 | n: 149 | 'lG4-sywQahA2bzbb53WiMS1MFJVFGJSHLwBuY_J4W84STvRUdcXPV3TE7s4A7_6fSdAXXVb69A5bL7mS_3EkGujgr2CnrRQNNdw055D28VHiZyAKrG2vg6e5-C8iHU0ew6nCqJ1bXK7vYhPs3qYlNdmxbxUyUimQZ038ABre8JlNiwsrnIoct_IKnhMNkUPovrcfNo83PwQwRJDabbYnTEtRIwlzVZxOwHXDySMf-xalRzxrQ-PvY1D5-HPfZHvmm-9ij4src9p5FTagMCkEUg9XrgpdhB-Aa0TyCpK8t1re1PjrFZgnIBZ5wTadTlbukYnD0FL83OU_olgBPMmd8Q', 150 | e: 'AQAB', 151 | kid: 'NEVBNUNBOTgxRkE5NkQzQzc4OTBEMEFFRDQ5N0Q2Qjk0RkQ1MjFGMQ', 152 | x5t: 'NEVBNUNBOTgxRkE5NkQzQzc4OTBEMEFFRDQ5N0Q2Qjk0RkQ1MjFGMQ' 153 | } 154 | ] 155 | }) 156 | }); 157 | }; 158 | 159 | return getJWKS( 160 | { 161 | jwksURI: 'https://example.com/jwks.json', 162 | kid: 'NEVBNUNBOTgxRkE5NkQzQzc4OTBEMEFFRDQ5N0Q2Qjk0RkQ1MjFGMQ' 163 | }, 164 | function(err, data) { 165 | assert.isNull(err); 166 | 167 | assert.equals(data, { 168 | modulus: 169 | '946e3eb32c106a10366f36dbe775a2312d4c1495451894872f006e63f2785bce124ef45475c5cf5774c4eece00effe9f49d0175d56faf40e5b2fb992ff71241ae8e0af60a7ad140d35dc34e790f6f151e267200aac6daf83a7b9f82f221d4d1ec3a9c2a89d5b5caeef6213ecdea62535d9b16f1532522990674dfc001adef0994d8b0b2b9c8a1cb7f20a9e130d9143e8beb71f368f373f04304490da6db6274c4b51230973559c4ec075c3c9231ffb16a5473c6b43e3ef6350f9f873df647be69bef628f8b2b73da791536a0302904520f57ae0a5d841f806b44f20a92bcb75aded4f8eb159827201679c1369d4e56ee9189c3d052fcdce53fa258013cc99df1', 170 | exp: '010001' 171 | }); 172 | } 173 | ); 174 | }); 175 | 176 | it('returns jwks when the key is not first in the keys list', function() { 177 | global.fetch = () => { 178 | return Promise.resolve({ 179 | ok: true, 180 | json: () => 181 | Promise.resolve({ 182 | keys: [ 183 | { 184 | kid: 'some-other-random-key' 185 | }, 186 | { 187 | n: '', 188 | e: '', 189 | kid: 'some-random-key' 190 | } 191 | ] 192 | }) 193 | }); 194 | }; 195 | 196 | return getJWKS( 197 | { 198 | jwksURI: 'https://example.com/jwks.json', 199 | kid: 'some-random-key' 200 | }, 201 | function(err, data) { 202 | assert.isNull(err); 203 | assert.keys(data, ['modulus', 'exp']); 204 | } 205 | ); 206 | }); 207 | 208 | it('returns jwks when the key is not first in the keys list (using promises)', function(done) { 209 | global.fetch = () => { 210 | return Promise.resolve({ 211 | ok: true, 212 | json: () => 213 | Promise.resolve({ 214 | keys: [ 215 | { 216 | kid: 'some-other-random-key' 217 | }, 218 | { 219 | n: '', 220 | e: '', 221 | kid: 'some-random-key' 222 | } 223 | ] 224 | }) 225 | }); 226 | }; 227 | 228 | getJWKS( 229 | { 230 | jwksURI: 'https://example.com/jwks.json', 231 | kid: 'some-random-key' 232 | }, 233 | null 234 | ).then(function(data) { 235 | assert.keys(data, ['modulus', 'exp']); 236 | done(); 237 | }); 238 | }); 239 | }); 240 | }); 241 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Home - Documentation 7 | 8 | 9 | 10 | 13 | 18 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 64 | 65 |
66 |
67 |
68 |

69 | idtoken-verifier 73 |

74 |

75 | A lightweight library to decode and verify RSA ID tokens meant for 76 | the browser. 77 |

78 |

79 | Build Status 84 | NPM version 89 | Coverage 95 | License 100 | Downloads 105 |

106 |

107 | :books: Documentation - :rocket: 108 | Getting Started - :computer: 109 | API Reference - :speech_balloon: 110 | Feedback 111 |

112 |

Documentation

113 |
    114 |
  • 115 | API Reference 118 |
  • 119 |
  • 120 | Docs Site - explore our Docs 121 | site and learn more about Auth0 122 |
  • 123 |
124 |

Getting Started

125 |

Installation

126 |

127 | Using npm in your project directory 128 | run the following command: 129 |

130 |
npm install idtoken-verifier
131 | 
132 |

Verify an ID token

133 |

134 | Import the library, create an instance of 135 | IdTokenVerifier and call the verify method 136 | to verify an ID token: 137 |

138 |
import IdTokenVerifier from 'idtoken-verifier';
141 | 
142 | const verifier = new IdTokenVerifier({
143 |   issuer: 'https://my.auth0.com/',
144 |   audience: 'gYSNlU4YC4V1YPdqq8zPQcup6rJw1Mbt'
145 | });
146 | 
147 | verifier.verify(id_token, nonce, (error, payload) => {
148 |   if (error) {
149 |     // handle the error
150 |     return;
151 |   }
152 | 
153 |   // do something with `payload`
154 | });
155 | 
156 |

API Reference

157 | 183 |

Feedback

184 |

Contributing

185 |

186 | We appreciate feedback and contribution to this repo! Before you get 187 | started, please see the following: 188 |

189 | 209 |

Raise an issue

210 |

211 | To provide feedback or report a bug, please 212 | raise an issue on our issue tracker. 215 |

216 |

Vulnerability Reporting

217 |

218 | Please do not report security vulnerabilities on the public GitHub 219 | issue tracker. The 220 | Responsible Disclosure Program 223 | details the procedure for disclosing security issues. 224 |

225 |

What is Auth0?

226 |

227 | 228 | 233 | 238 | Auth0 Logo 243 | 244 |

245 |

246 | Auth0 is an easy to implement, adaptable authentication and 247 | authorization platform. To learn more checkout 248 | Why Auth0? 249 |

250 |

251 | This project is licensed under the Apache 2.0 license. See the 252 | LICENSE file for more info. 253 |

254 | 255 |
256 |
257 |
258 | 259 |
260 | 261 | 266 | 267 | 270 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /docs/IdTokenVerifier.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | IdTokenVerifier - Documentation 7 | 8 | 9 | 10 | 13 | 18 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 64 | 65 |
66 |

IdTokenVerifier

67 | 68 |
69 |
70 |

71 | IdTokenVerifier 72 |

73 |
74 | 75 |
76 |
77 |
78 |

79 | new IdTokenVerifier(parameters) 83 |

84 | 85 |
86 |

Creates a new id_token verifier

87 |
88 | 89 |
90 |
Source:
91 |
92 | 98 |
99 |
100 | 101 |
Parameters:
102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 121 | 122 | 293 | 294 | 295 |
NameTypeDescription
parameters 119 | Object 120 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 147 | 148 | 149 | 150 | 151 | 152 | 158 | 159 | 160 | 161 | 162 | 163 | 168 | 169 | 170 | 171 | 172 | 173 | 180 | 181 | 182 | 183 | 184 | 185 | 190 | 191 | 192 | 193 | 194 | 195 | 201 | 202 | 203 | 204 | 205 | 206 | 211 | 212 | 213 | 214 | 215 | 216 | 222 | 223 | 224 | 225 | 226 | 227 | 232 | 233 | 234 | 235 | 238 | 239 | 245 | 246 | 247 | 248 | 249 | 250 | 255 | 256 | 257 | 258 | 261 | 262 | 268 | 269 | 270 | 271 | 272 | 273 | 278 | 279 | 280 | 281 | 282 | 283 | 289 | 290 | 291 |
NameTypeAttributesDefaultDescription
issuer 143 | string 146 | 153 |

154 | name of the issuer of the token that should 155 | match the iss claim in the id_token 156 |

157 |
audience 164 | string 167 | 174 |

175 | identifies the recipients that the JWT is 176 | intended for and should match the 177 | aud claim 178 |

179 |
jwksCache 186 | Object 189 | <optional>
196 |

197 | cache for JSON Web Token Keys. By default it has 198 | no cache 199 |

200 |
jwksURI 207 | string 210 | <optional>
217 |

218 | A valid, direct URI to fetch the JSON Web Key 219 | Set (JWKS). 220 |

221 |
expectedAlg 228 | string 231 | <optional>
236 | 'RS256' 237 | 240 |

241 | algorithm in which the id_token was signed and 242 | will be used to validate 243 |

244 |
leeway 251 | number 254 | <optional>
259 | 60 260 | 263 |

264 | number of seconds that the clock can be out of 265 | sync 266 |

267 |
maxAge 274 | number 277 | <optional>
284 |

285 | max age while validating expiration of the 286 | id_token 287 |

288 |
292 |
296 |
297 |
298 |
299 |
300 |
301 | 302 |
303 | 304 | 309 | 310 | 313 | 314 | 315 | 316 | -------------------------------------------------------------------------------- /docs/scripts/prettify/Apache-License-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v2.2.4](https://github.com/auth0/idtoken-verifier/tree/v2.2.4) (2023-10-26) 4 | 5 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v2.2.3...v2.2.4) 6 | 7 | **Security** 8 | 9 | - Bump crypto-js from 4.1.1 to 4.2.0 [\#176](https://github.com/auth0/idtoken-verifier/pull/176) ([dependabot[bot]](https://github.com/apps/dependabot)) 10 | 11 | ## [v2.2.3](https://github.com/auth0/idtoken-verifier/tree/v2.2.3) (2023-02-06) 12 | 13 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v2.2.2...v2.2.3) 14 | 15 | **Changed** 16 | 17 | - [SDK-3693] Readme Redesign [\#159](https://github.com/auth0/idtoken-verifier/pull/159) ([stevehobbsdev](https://github.com/stevehobbsdev)) 18 | 19 | **Security** 20 | 21 | - Bump jsonwebtoken from 8.5.1 to 9.0.0 [\#165](https://github.com/auth0/idtoken-verifier/pull/165) ([dependabot[bot]](https://github.com/apps/dependabot)) 22 | 23 | ## [v2.2.2](https://github.com/auth0/idtoken-verifier/tree/v2.2.2) (2021-10-15) 24 | 25 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v2.2.1...v2.2.2) 26 | 27 | **Fixed** 28 | 29 | - Fix types for overloaded verify method [\#145](https://github.com/auth0/idtoken-verifier/pull/145) ([stevehobbsdev](https://github.com/stevehobbsdev)) 30 | 31 | ## [v2.2.1](https://github.com/auth0/idtoken-verifier/tree/v2.2.1) (2021-08-24) 32 | 33 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v2.2.1...v2.2.1) 34 | 35 | **Security** 36 | 37 | - Depdency updates [\#138](https://github.com/auth0/idtoken-verifier/pull/138) ([stevehobbsdev](https://github.com/stevehobbsdev)) 38 | 39 | ## [v2.2.0](https://github.com/auth0/idtoken-verifier/tree/v2.2.0) (2021-06-18) 40 | 41 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v2.2.0...v2.2.0) 42 | 43 | **Added** 44 | 45 | - Added TypeScript typings [\#122](https://github.com/auth0/idtoken-verifier/pull/122) ([ItalyPaleAle](https://github.com/ItalyPaleAle)) 46 | 47 | ## [v2.1.2](https://github.com/auth0/idtoken-verifier/tree/v2.1.2) (2021-05-26) 48 | 49 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v2.1.2...v2.1.2) 50 | 51 | **Security** 52 | 53 | - Pin crypto-js to 3.3.0 [\#131](https://github.com/auth0/idtoken-verifier/pull/131) ([stevehobbsdev](https://github.com/stevehobbsdev)) 54 | 55 | ## [v2.1.1](https://github.com/auth0/idtoken-verifier/tree/v2.1.1) (2021-05-25) 56 | 57 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v2.1.1...v2.1.1) 58 | 59 | **Fixed** 60 | 61 | - Fixed: exception when key not found in JWKS [\#121](https://github.com/auth0/idtoken-verifier/pull/121) ([ItalyPaleAle](https://github.com/ItalyPaleAle)) 62 | - Remove inconsistencies in verify method's callback invocations [\#120](https://github.com/auth0/idtoken-verifier/pull/120) ([ItalyPaleAle](https://github.com/ItalyPaleAle)) 63 | 64 | **Security** 65 | 66 | - Fix vulnerability with crypto-js 3.3.0 and run audit fix [\#128](https://github.com/auth0/idtoken-verifier/pull/128) ([frederikprijck](https://github.com/frederikprijck)) 67 | - [Security] Bump y18n from 4.0.0 to 4.0.1 [\#119](https://github.com/auth0/idtoken-verifier/pull/119) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 68 | 69 | ## [v2.1.0](https://github.com/auth0/idtoken-verifier/tree/v2.1.0) (2020-09-11) 70 | 71 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v2.1.0...v2.1.0) 72 | 73 | **Changed** 74 | 75 | - Asynchronous JWKS cache [\#107](https://github.com/auth0/idtoken-verifier/pull/107) ([ItalyPaleAle](https://github.com/ItalyPaleAle)) 76 | - Migrate to NPM package lock over Yarn, and update dependencies [\#114](https://github.com/auth0/idtoken-verifier/pull/114) ([stevehobbsdev](https://github.com/stevehobbsdev)) 77 | 78 | **Security** 79 | 80 | - Bump bl from 3.0.0 to 3.0.1 [\#111](https://github.com/auth0/idtoken-verifier/pull/111) ([dependabot[bot]](https://github.com/apps/dependabot)) 81 | - Bump codecov from 3.6.5 to 3.7.1 [\#109](https://github.com/auth0/idtoken-verifier/pull/109) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 82 | - Bump lodash from 4.17.15 to 4.17.19 [\#108](https://github.com/auth0/idtoken-verifier/pull/108) ([dependabot[bot]](https://github.com/apps/dependabot)) 83 | - Bump handlebars from 4.5.3 to 4.7.6 [\#106](https://github.com/auth0/idtoken-verifier/pull/106) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 84 | 85 | ## [v2.0.3](https://github.com/auth0/idtoken-verifier/tree/v2.0.3) (2020-04-23) 86 | 87 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v2.0.2...v2.0.3) 88 | 89 | **Fixed** 90 | 91 | - Fixed bug with keys not first in keybag [\#101](https://github.com/auth0/idtoken-verifier/pull/101) ([ItalyPaleAle](https://github.com/ItalyPaleAle)) 92 | 93 | ## [v2.0.2](https://github.com/auth0/idtoken-verifier/tree/v2.0.2) (2020-02-20) 94 | 95 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v2.0.1...v2.0.2) 96 | 97 | **Security** 98 | 99 | - [Snyk] Security upgrade crypto-js from 3.1.9-1 to 3.2.1 [\#98](https://github.com/auth0/idtoken-verifier/pull/98) ([crew-security](https://github.com/crew-security)) 100 | 101 | ## [v2.0.1](https://github.com/auth0/idtoken-verifier/tree/v2.0.1) (2020-01-10) 102 | 103 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v2.0.0...v2.0.1) 104 | 105 | **Removed** 106 | 107 | - [SDK-1266] Removed iat future value check [\#95](https://github.com/auth0/idtoken-verifier/pull/95) ([stevehobbsdev](https://github.com/stevehobbsdev)) 108 | 109 | ## [v2.0.0](https://github.com/auth0/idtoken-verifier/tree/v2.0.0) (2019-12-06) 110 | 111 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v1.5.1...v2.0.0) 112 | 113 | This new major version introduces more validation checks on ID tokens for [OIDC conformance](https://openid.net/specs/openid-connect-core-1_0-final.html#IDTokenValidation) and as such **could introduce a breaking change** if you are already validating tokens that are not OIDC conformant. 114 | 115 | In addition, methods that were marked as deprecated in [v1.5.0](https://github.com/auth0/idtoken-verifier/releases/v1.5.0) have now been removed. From here, always use the `verify` method to validate ID tokens. 116 | 117 | **Changed** 118 | 119 | - Added build step into the versioning process (to be done before release) [\#93](https://github.com/auth0/idtoken-verifier/pull/93) ([stevehobbsdev](https://github.com/stevehobbsdev)) 120 | - Updated dependencies [\#92](https://github.com/auth0/idtoken-verifier/pull/92) ([stevehobbsdev](https://github.com/stevehobbsdev)) 121 | 122 | **Removed** 123 | 124 | - Removed deprecated methods + tests [\#90](https://github.com/auth0/idtoken-verifier/pull/90) ([stevehobbsdev](https://github.com/stevehobbsdev)) 125 | 126 | **Security** 127 | 128 | - [SDK-974] Improved OIDC compliance [\#89](https://github.com/auth0/idtoken-verifier/pull/89) ([stevehobbsdev](https://github.com/stevehobbsdev)) 129 | - Bump tough-cookie from 2.3.2 to 2.3.4 [\#88](https://github.com/auth0/idtoken-verifier/pull/88) ([dependabot[bot]](https://github.com/apps/dependabot)) 130 | - Bump stringstream from 0.0.5 to 0.0.6 [\#87](https://github.com/auth0/idtoken-verifier/pull/87) ([dependabot[bot]](https://github.com/apps/dependabot)) 131 | - Bump extend from 3.0.1 to 3.0.2 [\#86](https://github.com/auth0/idtoken-verifier/pull/86) ([dependabot[bot]](https://github.com/apps/dependabot)) 132 | - Bump sshpk from 1.13.0 to 1.16.1 [\#85](https://github.com/auth0/idtoken-verifier/pull/85) ([dependabot[bot]](https://github.com/apps/dependabot)) 133 | - Bump lodash.merge from 4.6.1 to 4.6.2 [\#84](https://github.com/auth0/idtoken-verifier/pull/84) ([dependabot[bot]](https://github.com/apps/dependabot)) 134 | 135 | ## [v1.5.1](https://github.com/auth0/idtoken-verifier/tree/v1.5.1) (2019-12-06) 136 | 137 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v1.5.0...v1.5.1) 138 | 139 | Re-release to correct build directory error 140 | 141 | ## [v1.5.0](https://github.com/auth0/idtoken-verifier/tree/v1.5.0) (2019-12-05) 142 | 143 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v1.4.1...v1.5.0) 144 | 145 | **Added** 146 | 147 | - [SDK-1166] Replaced promise-polyfill with es6-promise, applied globally [\#78](https://github.com/auth0/idtoken-verifier/pull/78) ([stevehobbsdev](https://github.com/stevehobbsdev)) 148 | 149 | ## [v1.4.1](https://github.com/auth0/idtoken-verifier/tree/v1.4.1) (2019-07-09) 150 | 151 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v1.4.0...v1.4.1) 152 | 153 | **Fixed** 154 | 155 | - Use unfetch without requiring window at load time [\#42](https://github.com/auth0/idtoken-verifier/pull/42) ([luisrudge](https://github.com/luisrudge)) 156 | 157 | ## [v1.4.0](https://github.com/auth0/idtoken-verifier/tree/v1.4.0) (2019-06-18) 158 | 159 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v1.3.0...v1.4.0) 160 | 161 | **Fixed** 162 | 163 | - Validate claims after verifying the signature of the token [\#39](https://github.com/auth0/idtoken-verifier/pull/39) ([luisrudge](https://github.com/luisrudge)) 164 | 165 | ## [v1.3.0](https://github.com/auth0/idtoken-verifier/tree/v1.3.0) (2019-06-05) 166 | 167 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v1.2.0...v1.3.0) 168 | 169 | **Changed** 170 | 171 | - Increase leeway limit to 300s [\#31](https://github.com/auth0/idtoken-verifier/pull/31) ([luisrudge](https://github.com/luisrudge)) 172 | - Replace superagent with unfetch [\#27](https://github.com/auth0/idtoken-verifier/pull/27) ([luisrudge](https://github.com/luisrudge)) 173 | 174 | ## [v1.2.0](https://github.com/auth0/idtoken-verifier/tree/v1.2.0) (2018-03-21) 175 | 176 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v1.1.2...v1.2.0) 177 | 178 | **Added** 179 | 180 | - Add option to set the endpoint to fetch the jwks.json file [\#19](https://github.com/auth0/idtoken-verifier/pull/19) ([luisrudge](https://github.com/luisrudge)) 181 | - Adding access_token validation method `validateAccessToken` [\#17](https://github.com/auth0/idtoken-verifier/pull/17) ([luisrudge](https://github.com/luisrudge)) 182 | 183 | ## [v1.1.2](https://github.com/auth0/idtoken-verifier/tree/v1.1.2) (2018-03-01) 184 | 185 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v1.1.1...v1.1.2) 186 | 187 | **Fixed** 188 | 189 | - Fixing issue with IdTokenVerifier.getRsaVerifier [\#14](https://github.com/auth0/idtoken-verifier/pull/14) ([dfung](https://github.com/dfung)) 190 | 191 | - Use base64-js methods instead of browser globals atob and btoa [\#15](https://github.com/auth0/idtoken-verifier/pull/15) ([maxbeatty](https://github.com/maxbeatty)) 192 | 193 | ## [v1.1.1](https://github.com/auth0/idtoken-verifier/tree/v1.1.1) (2018-01-15) 194 | 195 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v1.1.0...v1.1.1) 196 | 197 | **Changed** 198 | 199 | - Upgrade superagent version [\#10](https://github.com/auth0/idtoken-verifier/pull/10) ([luisrudge](https://github.com/luisrudge)) 200 | 201 | ## [v1.1.0](https://github.com/auth0/idtoken-verifier/tree/v1.1.0) (2017-06-15) 202 | 203 | [Full Changelog](https://github.com/auth0/idtoken-verifier/compare/v1.0.2...v1.1.0) 204 | 205 | **Changed** 206 | 207 | - Replace iat check with nbf check. [\#7](https://github.com/auth0/idtoken-verifier/pull/7) ([nicosabena](https://github.com/nicosabena)) 208 | 209 | ## [v1.0.2](https://github.com/auth0/auth0.js/tree/v1.0.2) (2017-05-08) 210 | 211 | [Full Changelog](https://github.com/auth0/auth0.js/compare/v1.0.1...v1.0.2) 212 | 213 | **Fixed** 214 | 215 | - FIX decode base64 string with special characters. [\#6](https://github.com/auth0/idtoken-verifier/pull/6) ([dctoon](https://github.com/dctoon)) 216 | 217 | ## [v1.0.1](https://github.com/auth0/auth0.js/tree/v1.0.1) (2017-05-08) 218 | 219 | [Full Changelog](https://github.com/auth0/auth0.js/compare/v1.0.0...v1.0.1) 220 | 221 | **Fixed** 222 | 223 | - Handle JSON.parse errors during decode [\#3](https://github.com/auth0/idtoken-verifier/pull/3) ([rolodato](https://github.com/rolodato)) 224 | 225 | ## [v1.0.0](https://github.com/auth0/idtoken-verifier/tree/v1.0.0) (2016-12-30) 226 | 227 | [Full Changelog](https://github.com/auth0/idtoken-verifier/tree/v1.0.0) 228 | 229 | A lightweight library to decode and verify RS JWT meant for the browser. 230 | 231 | ### Usage 232 | 233 | ```js 234 | var IdTokenVerifier = require('idtoken-verifier'); 235 | 236 | var verifier = new IdTokenVerifier({ 237 | issuer: 'https://my.auth0.com/', 238 | audience: 'gYSNlU4YC4V1YPdqq8zPQcup6rJw1Mbt' 239 | }); 240 | 241 | verifier.verify(id_token, nonce, function(error, payload) { 242 | ... 243 | }); 244 | 245 | var decoded = verifier.decode(id_token); 246 | ``` 247 | -------------------------------------------------------------------------------- /docs/scripts/prettify/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p code { 197 | font-size: 0.85em; 198 | } 199 | 200 | .readme table { 201 | margin-bottom: 1em; 202 | border-collapse: collapse; 203 | border-spacing: 0; 204 | } 205 | 206 | .readme table tr { 207 | background-color: #fff; 208 | border-top: 1px solid #ccc; 209 | } 210 | 211 | .readme table th, 212 | .readme table td { 213 | padding: 6px 13px; 214 | border: 1px solid #ddd; 215 | } 216 | 217 | .readme table tr:nth-child(2n) { 218 | background-color: #f8f8f8; 219 | } 220 | 221 | /** Nav **/ 222 | nav { 223 | float: left; 224 | display: block; 225 | width: 250px; 226 | background: #fff; 227 | overflow: auto; 228 | position: fixed; 229 | height: 100%; 230 | padding: 10px; 231 | border-right: 1px solid #eee; 232 | /* box-shadow: 0 0 3px rgba(0,0,0,0.1); */ 233 | } 234 | 235 | nav li { 236 | list-style: none; 237 | padding: 0; 238 | margin: 0; 239 | } 240 | 241 | .nav-heading { 242 | margin-top: 10px; 243 | font-weight: bold; 244 | } 245 | 246 | .nav-heading a { 247 | color: #888; 248 | font-size: 14px; 249 | display: inline-block; 250 | } 251 | 252 | .nav-item-type { 253 | /* margin-left: 5px; */ 254 | width: 18px; 255 | height: 18px; 256 | display: inline-block; 257 | text-align: center; 258 | border-radius: 0.2em; 259 | margin-right: 5px; 260 | font-weight: bold; 261 | line-height: 20px; 262 | font-size: 13px; 263 | } 264 | 265 | .type-function { 266 | background: #B3E5FC; 267 | color: #0288D1; 268 | } 269 | 270 | .type-class { 271 | background: #D1C4E9; 272 | color: #4527A0; 273 | } 274 | 275 | .type-member { 276 | background: #C8E6C9; 277 | color: #388E3C; 278 | } 279 | 280 | .type-module { 281 | background: #E1BEE7; 282 | color: #7B1FA2; 283 | } 284 | 285 | 286 | /** Footer **/ 287 | footer { 288 | color: hsl(0, 0%, 28%); 289 | margin-left: 250px; 290 | display: block; 291 | padding: 30px; 292 | font-style: italic; 293 | font-size: 90%; 294 | border-top: 1px solid #eee; 295 | } 296 | 297 | .ancestors { 298 | color: #999 299 | } 300 | 301 | .ancestors a { 302 | color: #999 !important; 303 | text-decoration: none; 304 | } 305 | 306 | .clear { 307 | clear: both 308 | } 309 | 310 | .important { 311 | font-weight: bold; 312 | color: #950B02; 313 | } 314 | 315 | .yes-def { 316 | text-indent: -1000px 317 | } 318 | 319 | .type-signature { 320 | color: #aaa 321 | } 322 | 323 | .name, .signature { 324 | font-family: Consolas, Monaco, 'Andale Mono', monospace 325 | } 326 | 327 | .details { 328 | margin-top: 14px; 329 | border-left: 2px solid #DDD; 330 | line-height: 30px; 331 | } 332 | 333 | .details dt { 334 | width: 120px; 335 | float: left; 336 | padding-left: 10px; 337 | } 338 | 339 | .details dd { 340 | margin-left: 70px 341 | } 342 | 343 | .details ul { 344 | margin: 0 345 | } 346 | 347 | .details ul { 348 | list-style-type: none 349 | } 350 | 351 | .details li { 352 | margin-left: 30px 353 | } 354 | 355 | .details pre.prettyprint { 356 | margin: 0 357 | } 358 | 359 | .details .object-value { 360 | padding-top: 0 361 | } 362 | 363 | .description { 364 | margin-bottom: 1em; 365 | margin-top: 1em; 366 | } 367 | 368 | .code-caption { 369 | font-style: italic; 370 | font-size: 107%; 371 | margin: 0; 372 | } 373 | 374 | .prettyprint { 375 | font-size: 13px; 376 | border: 1px solid #ddd; 377 | border-radius: 3px; 378 | box-shadow: 0 1px 3px hsla(0, 0%, 0%, 0.05); 379 | overflow: auto; 380 | } 381 | 382 | .prettyprint.source { 383 | width: inherit 384 | } 385 | 386 | .prettyprint code { 387 | font-size: 12px; 388 | line-height: 18px; 389 | display: block; 390 | background-color: #fff; 391 | color: #4D4E53; 392 | } 393 | 394 | .prettyprint code:empty:before { 395 | content: ''; 396 | } 397 | 398 | .prettyprint > code { 399 | padding: 15px 400 | } 401 | 402 | .prettyprint .linenums code { 403 | padding: 0 15px 404 | } 405 | 406 | .prettyprint .linenums li:first-of-type code { 407 | padding-top: 15px 408 | } 409 | 410 | .prettyprint code span.line { 411 | display: inline-block 412 | } 413 | 414 | .prettyprint.linenums { 415 | padding-left: 70px; 416 | -webkit-user-select: none; 417 | -moz-user-select: none; 418 | -ms-user-select: none; 419 | user-select: none; 420 | } 421 | 422 | .prettyprint.linenums ol { 423 | padding-left: 0 424 | } 425 | 426 | .prettyprint.linenums li { 427 | border-left: 3px #ddd solid 428 | } 429 | 430 | .prettyprint.linenums li.selected, .prettyprint.linenums li.selected * { 431 | background-color: lightyellow 432 | } 433 | 434 | .prettyprint.linenums li * { 435 | -webkit-user-select: text; 436 | -moz-user-select: text; 437 | -ms-user-select: text; 438 | user-select: text; 439 | } 440 | 441 | .params, .props { 442 | border-spacing: 0; 443 | border: 1px solid #ddd; 444 | border-collapse: collapse; 445 | border-radius: 3px; 446 | box-shadow: 0 1px 3px rgba(0,0,0,0.1); 447 | width: 100%; 448 | font-size: 14px; 449 | /* margin-left: 15px; */ 450 | } 451 | 452 | .params .name, .props .name, .name code { 453 | color: #4D4E53; 454 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 455 | font-size: 100%; 456 | } 457 | 458 | .params td, .params th, .props td, .props th { 459 | margin: 0px; 460 | text-align: left; 461 | vertical-align: top; 462 | padding: 10px; 463 | display: table-cell; 464 | } 465 | 466 | .params td { 467 | border-top: 1px solid #eee 468 | } 469 | 470 | .params thead tr, .props thead tr { 471 | background-color: #fff; 472 | font-weight: bold; 473 | } 474 | 475 | .params .params thead tr, .props .props thead tr { 476 | background-color: #fff; 477 | font-weight: bold; 478 | } 479 | 480 | .params td.description > p:first-child, .props td.description > p:first-child { 481 | margin-top: 0; 482 | padding-top: 0; 483 | } 484 | 485 | .params td.description > p:last-child, .props td.description > p:last-child { 486 | margin-bottom: 0; 487 | padding-bottom: 0; 488 | } 489 | 490 | dl.param-type { 491 | /* border-bottom: 1px solid hsl(0, 0%, 87%); */ 492 | margin: 0; 493 | padding: 0; 494 | font-size: 16px; 495 | } 496 | 497 | .param-type dt, .param-type dd { 498 | display: inline-block 499 | } 500 | 501 | .param-type dd { 502 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 503 | display: inline-block; 504 | padding: 0; 505 | margin: 0; 506 | font-size: 14px; 507 | } 508 | 509 | .disabled { 510 | color: #454545 511 | } 512 | 513 | /* navicon button */ 514 | .navicon-button { 515 | display: none; 516 | position: relative; 517 | padding: 2.0625rem 1.5rem; 518 | transition: 0.25s; 519 | cursor: pointer; 520 | user-select: none; 521 | opacity: .8; 522 | } 523 | .navicon-button .navicon:before, .navicon-button .navicon:after { 524 | transition: 0.25s; 525 | } 526 | .navicon-button:hover { 527 | transition: 0.5s; 528 | opacity: 1; 529 | } 530 | .navicon-button:hover .navicon:before, .navicon-button:hover .navicon:after { 531 | transition: 0.25s; 532 | } 533 | .navicon-button:hover .navicon:before { 534 | top: .825rem; 535 | } 536 | .navicon-button:hover .navicon:after { 537 | top: -.825rem; 538 | } 539 | 540 | /* navicon */ 541 | .navicon { 542 | position: relative; 543 | width: 2.5em; 544 | height: .3125rem; 545 | background: #000; 546 | transition: 0.3s; 547 | border-radius: 2.5rem; 548 | } 549 | .navicon:before, .navicon:after { 550 | display: block; 551 | content: ""; 552 | height: .3125rem; 553 | width: 2.5rem; 554 | background: #000; 555 | position: absolute; 556 | z-index: -1; 557 | transition: 0.3s 0.25s; 558 | border-radius: 1rem; 559 | } 560 | .navicon:before { 561 | top: .625rem; 562 | } 563 | .navicon:after { 564 | top: -.625rem; 565 | } 566 | 567 | /* open */ 568 | .nav-trigger:checked + label:not(.steps) .navicon:before, 569 | .nav-trigger:checked + label:not(.steps) .navicon:after { 570 | top: 0 !important; 571 | } 572 | 573 | .nav-trigger:checked + label .navicon:before, 574 | .nav-trigger:checked + label .navicon:after { 575 | transition: 0.5s; 576 | } 577 | 578 | /* Minus */ 579 | .nav-trigger:checked + label { 580 | transform: scale(0.75); 581 | } 582 | 583 | /* × and + */ 584 | .nav-trigger:checked + label.plus .navicon, 585 | .nav-trigger:checked + label.x .navicon { 586 | background: transparent; 587 | } 588 | 589 | .nav-trigger:checked + label.plus .navicon:before, 590 | .nav-trigger:checked + label.x .navicon:before { 591 | transform: rotate(-45deg); 592 | background: #FFF; 593 | } 594 | 595 | .nav-trigger:checked + label.plus .navicon:after, 596 | .nav-trigger:checked + label.x .navicon:after { 597 | transform: rotate(45deg); 598 | background: #FFF; 599 | } 600 | 601 | .nav-trigger:checked + label.plus { 602 | transform: scale(0.75) rotate(45deg); 603 | } 604 | 605 | .nav-trigger:checked ~ nav { 606 | left: 0 !important; 607 | } 608 | 609 | .nav-trigger:checked ~ .overlay { 610 | display: block; 611 | } 612 | 613 | .nav-trigger { 614 | position: fixed; 615 | top: 0; 616 | clip: rect(0, 0, 0, 0); 617 | } 618 | 619 | .overlay { 620 | display: none; 621 | position: fixed; 622 | top: 0; 623 | bottom: 0; 624 | left: 0; 625 | right: 0; 626 | width: 100%; 627 | height: 100%; 628 | background: hsla(0, 0%, 0%, 0.5); 629 | z-index: 1; 630 | } 631 | 632 | .section-method { 633 | margin-bottom: 30px; 634 | padding-bottom: 30px; 635 | border-bottom: 1px solid #eee; 636 | } 637 | 638 | @media only screen and (min-width: 320px) and (max-width: 680px) { 639 | body { 640 | overflow-x: hidden; 641 | } 642 | 643 | nav { 644 | background: #FFF; 645 | width: 250px; 646 | height: 100%; 647 | position: fixed; 648 | top: 0; 649 | right: 0; 650 | bottom: 0; 651 | left: -250px; 652 | z-index: 3; 653 | padding: 0 10px; 654 | transition: left 0.2s; 655 | } 656 | 657 | .navicon-button { 658 | display: inline-block; 659 | position: fixed; 660 | top: 1.5em; 661 | right: 0; 662 | z-index: 2; 663 | } 664 | 665 | #main { 666 | width: 100%; 667 | min-width: 360px; 668 | } 669 | 670 | #main h1.page-title { 671 | margin: 1em 0; 672 | } 673 | 674 | #main section { 675 | padding: 0; 676 | } 677 | 678 | footer { 679 | margin-left: 0; 680 | } 681 | } 682 | 683 | @media only print { 684 | nav { 685 | display: none; 686 | } 687 | 688 | #main { 689 | float: none; 690 | width: 100%; 691 | } 692 | } 693 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import p from 'es6-promise'; 2 | p.polyfill(); 3 | 4 | import sha256 from 'crypto-js/sha256'; 5 | import cryptoBase64 from 'crypto-js/enc-base64'; 6 | import cryptoHex from 'crypto-js/enc-hex'; 7 | 8 | import RSAVerifier from './helpers/rsa-verifier'; 9 | import * as base64 from './helpers/base64'; 10 | import * as jwks from './helpers/jwks'; 11 | import * as error from './helpers/error'; 12 | import DummyCache from './helpers/dummy-cache'; 13 | 14 | var supportedAlg = 'RS256'; 15 | var isNumber = n => typeof n === 'number'; 16 | var defaultClock = () => new Date(); 17 | var DEFAULT_LEEWAY = 60; 18 | 19 | /** 20 | * Creates a new id_token verifier 21 | * @constructor 22 | * @param {Object} parameters 23 | * @param {string} parameters.issuer name of the issuer of the token 24 | * that should match the `iss` claim in the id_token 25 | * @param {string} parameters.audience identifies the recipients that the JWT is intended for 26 | * and should match the `aud` claim 27 | * @param {Object} [parameters.jwksCache] cache for JSON Web Token Keys. By default it has no cache 28 | * @param {string} [parameters.jwksURI] A valid, direct URI to fetch the JSON Web Key Set (JWKS). 29 | * @param {string} [parameters.expectedAlg='RS256'] algorithm in which the id_token was signed 30 | * and will be used to validate 31 | * @param {number} [parameters.leeway=60] number of seconds that the clock can be out of sync 32 | * @param {number} [parameters.maxAge] max age 33 | * while validating expiration of the id_token 34 | */ 35 | function IdTokenVerifier(parameters) { 36 | var options = parameters || {}; 37 | 38 | this.jwksCache = options.jwksCache || new DummyCache(); 39 | this.expectedAlg = options.expectedAlg || 'RS256'; 40 | this.issuer = options.issuer; 41 | this.audience = options.audience; 42 | this.leeway = options.leeway === 0 ? 0 : options.leeway || DEFAULT_LEEWAY; 43 | this.jwksURI = options.jwksURI; 44 | this.maxAge = options.maxAge; 45 | 46 | this.__clock = 47 | typeof options.__clock === 'function' ? options.__clock : defaultClock; 48 | 49 | if (this.leeway < 0 || this.leeway > 300) { 50 | throw new error.ConfigurationError( 51 | 'The leeway should be positive and lower than five minutes.' 52 | ); 53 | } 54 | 55 | if (supportedAlg !== this.expectedAlg) { 56 | throw new error.ConfigurationError( 57 | 'Signature algorithm of "' + 58 | this.expectedAlg + 59 | '" is not supported. Expected the ID token to be signed with "' + 60 | supportedAlg + 61 | '".' 62 | ); 63 | } 64 | } 65 | 66 | /** 67 | * @callback verifyCallback 68 | * @param {?Error} err error returned if the verify cannot be performed 69 | * @param {?object} payload payload returned if the token is valid 70 | */ 71 | 72 | /** 73 | * Verifies an id_token 74 | * 75 | * It will validate: 76 | * - signature according to the algorithm configured in the verifier. 77 | * - if nonce is present and matches the one provided 78 | * - if `iss` and `aud` claims matches the configured issuer and audience 79 | * - if token is not expired and valid (if the `nbf` claim is in the past) 80 | * 81 | * @method verify 82 | * @param {string} token id_token to verify 83 | * @param {string} [requestedNonce] nonce value that should match the one in the id_token claims 84 | * @param {verifyCallback} cb callback used to notify the results of the validation 85 | */ 86 | IdTokenVerifier.prototype.verify = function(token, requestedNonce, cb) { 87 | if (!cb && requestedNonce && typeof requestedNonce == 'function') { 88 | cb = requestedNonce; 89 | requestedNonce = undefined; 90 | } 91 | if (!token) { 92 | return cb( 93 | new error.TokenValidationError('ID token is required but missing'), 94 | null 95 | ); 96 | } 97 | 98 | var jwt = this.decode(token); 99 | 100 | if (jwt instanceof Error) { 101 | return cb( 102 | new error.TokenValidationError('ID token could not be decoded'), 103 | null 104 | ); 105 | } 106 | 107 | /* eslint-disable vars-on-top */ 108 | var headerAndPayload = jwt.encoded.header + '.' + jwt.encoded.payload; 109 | var signature = base64.decodeToHEX(jwt.encoded.signature); 110 | 111 | var alg = jwt.header.alg; 112 | var kid = jwt.header.kid; 113 | 114 | var aud = jwt.payload.aud; 115 | var sub = jwt.payload.sub; 116 | var iss = jwt.payload.iss; 117 | var exp = jwt.payload.exp; 118 | var nbf = jwt.payload.nbf; 119 | var iat = jwt.payload.iat; 120 | var azp = jwt.payload.azp; 121 | var auth_time = jwt.payload.auth_time; 122 | var nonce = jwt.payload.nonce; 123 | var now = this.__clock(); 124 | 125 | /* eslint-enable vars-on-top */ 126 | var _this = this; 127 | 128 | if (_this.expectedAlg !== alg) { 129 | return cb( 130 | new error.TokenValidationError( 131 | 'Signature algorithm of "' + 132 | alg + 133 | '" is not supported. Expected the ID token to be signed with "' + 134 | supportedAlg + 135 | '".' 136 | ), 137 | null 138 | ); 139 | } 140 | 141 | this.getRsaVerifier(iss, kid, function(err, rsaVerifier) { 142 | if (err) { 143 | return cb(err, null); 144 | } 145 | 146 | if (!rsaVerifier.verify(headerAndPayload, signature)) { 147 | return cb( 148 | new error.TokenValidationError('Invalid ID token signature.'), 149 | null 150 | ); 151 | } 152 | 153 | if (!iss || typeof iss !== 'string') { 154 | return cb( 155 | new error.TokenValidationError( 156 | 'Issuer (iss) claim must be a string present in the ID token' 157 | ), 158 | null 159 | ); 160 | } 161 | 162 | if (_this.issuer !== iss) { 163 | return cb( 164 | new error.TokenValidationError( 165 | 'Issuer (iss) claim mismatch in the ID token, expected "' + 166 | _this.issuer + 167 | '", found "' + 168 | iss + 169 | '"' 170 | ), 171 | null 172 | ); 173 | } 174 | 175 | if (!sub || typeof sub !== 'string') { 176 | return cb( 177 | new error.TokenValidationError( 178 | 'Subject (sub) claim must be a string present in the ID token' 179 | ), 180 | null 181 | ); 182 | } 183 | 184 | if (!aud || (typeof aud !== 'string' && !Array.isArray(aud))) { 185 | return cb( 186 | new error.TokenValidationError( 187 | 'Audience (aud) claim must be a string or array of strings present in the ID token' 188 | ), 189 | null 190 | ); 191 | } 192 | 193 | if (Array.isArray(aud) && !aud.includes(_this.audience)) { 194 | return cb( 195 | new error.TokenValidationError( 196 | 'Audience (aud) claim mismatch in the ID token; expected "' + 197 | _this.audience + 198 | '" but was not one of "' + 199 | aud.join(', ') + 200 | '"' 201 | ), 202 | null 203 | ); 204 | } else if (typeof aud === 'string' && _this.audience !== aud) { 205 | return cb( 206 | new error.TokenValidationError( 207 | 'Audience (aud) claim mismatch in the ID token; expected "' + 208 | _this.audience + 209 | '" but found "' + 210 | aud + 211 | '"' 212 | ), 213 | null 214 | ); 215 | } 216 | 217 | if (requestedNonce) { 218 | if (!nonce || typeof nonce !== 'string') { 219 | return cb( 220 | new error.TokenValidationError( 221 | 'Nonce (nonce) claim must be a string present in the ID token' 222 | ), 223 | null 224 | ); 225 | } 226 | 227 | if (nonce !== requestedNonce) { 228 | return cb( 229 | new error.TokenValidationError( 230 | 'Nonce (nonce) claim value mismatch in the ID token; expected "' + 231 | requestedNonce + 232 | '", found "' + 233 | nonce + 234 | '"' 235 | ), 236 | null 237 | ); 238 | } 239 | } 240 | 241 | if (Array.isArray(aud) && aud.length > 1) { 242 | if (!azp || typeof azp !== 'string') { 243 | return cb( 244 | new error.TokenValidationError( 245 | 'Authorized Party (azp) claim must be a string present in the ID token when Audience (aud) claim has multiple values' 246 | ), 247 | null 248 | ); 249 | } 250 | 251 | if (azp !== _this.audience) { 252 | return cb( 253 | new error.TokenValidationError( 254 | 'Authorized Party (azp) claim mismatch in the ID token; expected "' + 255 | _this.audience + 256 | '", found "' + 257 | azp + 258 | '"' 259 | ), 260 | null 261 | ); 262 | } 263 | } 264 | 265 | if (!exp || !isNumber(exp)) { 266 | return cb( 267 | new error.TokenValidationError( 268 | 'Expiration Time (exp) claim must be a number present in the ID token' 269 | ), 270 | null 271 | ); 272 | } 273 | 274 | if (!iat || !isNumber(iat)) { 275 | return cb( 276 | new error.TokenValidationError( 277 | 'Issued At (iat) claim must be a number present in the ID token' 278 | ), 279 | null 280 | ); 281 | } 282 | 283 | var expTime = exp + _this.leeway; 284 | var expTimeDate = new Date(0); 285 | expTimeDate.setUTCSeconds(expTime); 286 | 287 | if (now > expTimeDate) { 288 | return cb( 289 | new error.TokenValidationError( 290 | 'Expiration Time (exp) claim error in the ID token; current time "' + 291 | now + 292 | '" is after expiration time "' + 293 | expTimeDate + 294 | '"' 295 | ), 296 | null 297 | ); 298 | } 299 | 300 | if (nbf && isNumber(nbf)) { 301 | var nbfTime = nbf - _this.leeway; 302 | var nbfTimeDate = new Date(0); 303 | nbfTimeDate.setUTCSeconds(nbfTime); 304 | 305 | if (now < nbfTimeDate) { 306 | return cb( 307 | new error.TokenValidationError( 308 | 'Not Before Time (nbf) claim error in the ID token; current time "' + 309 | now + 310 | '" is before the not before time "' + 311 | nbfTimeDate + 312 | '"' 313 | ), 314 | null 315 | ); 316 | } 317 | } 318 | 319 | if (_this.maxAge) { 320 | if (!auth_time || !isNumber(auth_time)) { 321 | return cb( 322 | new error.TokenValidationError( 323 | 'Authentication Time (auth_time) claim must be a number present in the ID token when Max Age (max_age) is specified' 324 | ), 325 | null 326 | ); 327 | } 328 | 329 | var authValidUntil = auth_time + _this.maxAge + _this.leeway; 330 | var authTimeDate = new Date(0); 331 | 332 | authTimeDate.setUTCSeconds(authValidUntil); 333 | 334 | if (now > authTimeDate) { 335 | return cb( 336 | new error.TokenValidationError( 337 | `Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time "${now}" is after last auth time at "${authTimeDate}"` 338 | ), 339 | null 340 | ); 341 | } 342 | } 343 | 344 | return cb(null, jwt.payload); 345 | }); 346 | }; 347 | 348 | IdTokenVerifier.prototype.getRsaVerifier = function(iss, kid, cb) { 349 | var _this = this; 350 | var cachekey = iss + kid; 351 | 352 | Promise.resolve(this.jwksCache.has(cachekey)) 353 | .then(function(hasKey) { 354 | if (!hasKey) { 355 | return jwks.getJWKS({ 356 | jwksURI: _this.jwksURI, 357 | iss: iss, 358 | kid: kid 359 | }); 360 | } else { 361 | return _this.jwksCache.get(cachekey); 362 | } 363 | }) 364 | .then(function(keyInfo) { 365 | if (!keyInfo || !keyInfo.modulus || !keyInfo.exp) { 366 | throw new Error('Empty keyInfo in response'); 367 | } 368 | return Promise.resolve(_this.jwksCache.set(cachekey, keyInfo)).then( 369 | function() { 370 | cb && cb(null, new RSAVerifier(keyInfo.modulus, keyInfo.exp)); 371 | } 372 | ); 373 | }) 374 | .catch(function(err) { 375 | cb && cb(err); 376 | }); 377 | }; 378 | 379 | /** 380 | * @typedef DecodedToken 381 | * @type {Object} 382 | * @property {Object} header - content of the JWT header. 383 | * @property {Object} payload - token claims. 384 | * @property {Object} encoded - encoded parts of the token. 385 | */ 386 | 387 | /** 388 | * Decodes a well formed JWT without any verification 389 | * 390 | * @method decode 391 | * @param {string} token decodes the token 392 | * @return {DecodedToken} if token is valid according to `exp` and `nbf` 393 | */ 394 | IdTokenVerifier.prototype.decode = function(token) { 395 | var parts = token.split('.'); 396 | var header; 397 | var payload; 398 | 399 | if (parts.length !== 3) { 400 | return new error.TokenValidationError('Cannot decode a malformed JWT'); 401 | } 402 | 403 | try { 404 | header = JSON.parse(base64.decodeToString(parts[0])); 405 | payload = JSON.parse(base64.decodeToString(parts[1])); 406 | } catch (e) { 407 | return new error.TokenValidationError( 408 | 'Token header or payload is not valid JSON' 409 | ); 410 | } 411 | 412 | return { 413 | header: header, 414 | payload: payload, 415 | encoded: { 416 | header: parts[0], 417 | payload: parts[1], 418 | signature: parts[2] 419 | } 420 | }; 421 | }; 422 | 423 | /** 424 | * @callback validateAccessTokenCallback 425 | * @param {Error} [err] error returned if the validation cannot be performed 426 | * or the token is invalid. If there is no error, then the access_token is valid. 427 | */ 428 | 429 | /** 430 | * Validates an access_token based on {@link http://openid.net/specs/openid-connect-core-1_0.html#ImplicitTokenValidation}. 431 | * The id_token from where the alg and atHash parameters are taken, 432 | * should be decoded and verified before using thisfunction 433 | * 434 | * @method validateAccessToken 435 | * @param {string} access_token the access_token 436 | * @param {string} alg The algorithm defined in the header of the 437 | * previously verified id_token under the "alg" claim. 438 | * @param {string} atHash The "at_hash" value included in the payload 439 | * of the previously verified id_token. 440 | * @param {validateAccessTokenCallback} cb callback used to notify the results of the validation. 441 | */ 442 | IdTokenVerifier.prototype.validateAccessToken = function( 443 | accessToken, 444 | alg, 445 | atHash, 446 | cb 447 | ) { 448 | if (this.expectedAlg !== alg) { 449 | return cb( 450 | new error.TokenValidationError( 451 | 'Signature algorithm of "' + 452 | alg + 453 | '" is not supported. Expected "' + 454 | this.expectedAlg + 455 | '"' 456 | ) 457 | ); 458 | } 459 | var sha256AccessToken = sha256(accessToken); 460 | var hashToHex = cryptoHex.stringify(sha256AccessToken); 461 | var hashToHexFirstHalf = hashToHex.substring(0, hashToHex.length / 2); 462 | var hashFirstHalfWordArray = cryptoHex.parse(hashToHexFirstHalf); 463 | var hashFirstHalfBase64 = cryptoBase64.stringify(hashFirstHalfWordArray); 464 | var hashFirstHalfBase64SafeUrl = base64.base64ToBase64Url( 465 | hashFirstHalfBase64 466 | ); 467 | if (hashFirstHalfBase64SafeUrl !== atHash) { 468 | return cb(new error.TokenValidationError('Invalid access_token')); 469 | } 470 | return cb(null); 471 | }; 472 | 473 | export default IdTokenVerifier; 474 | -------------------------------------------------------------------------------- /docs/index.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | index.js - Documentation 7 | 8 | 9 | 10 | 13 | 18 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 64 | 65 |
66 |

index.js

67 | 68 |
69 |
70 |
import p from 'es6-promise';
 73 | p.polyfill();
 74 | 
 75 | import sha256 from 'crypto-js/sha256';
 76 | import cryptoBase64 from 'crypto-js/enc-base64';
 77 | import cryptoHex from 'crypto-js/enc-hex';
 78 | 
 79 | import RSAVerifier from './helpers/rsa-verifier';
 80 | import * as base64 from './helpers/base64';
 81 | import * as jwks from './helpers/jwks';
 82 | import * as error from './helpers/error';
 83 | import DummyCache from './helpers/dummy-cache';
 84 | 
 85 | var supportedAlg = 'RS256';
 86 | var isNumber = n => typeof n === 'number';
 87 | var defaultClock = () => new Date();
 88 | var DEFAULT_LEEWAY = 60;
 89 | 
 90 | /**
 91 |  * Creates a new id_token verifier
 92 |  * @constructor
 93 |  * @param {Object} parameters
 94 |  * @param {string} parameters.issuer name of the issuer of the token
 95 |  * that should match the `iss` claim in the id_token
 96 |  * @param {string} parameters.audience identifies the recipients that the JWT is intended for
 97 |  * and should match the `aud` claim
 98 |  * @param {Object} [parameters.jwksCache] cache for JSON Web Token Keys. By default it has no cache
 99 |  * @param {string} [parameters.jwksURI] A valid, direct URI to fetch the JSON Web Key Set (JWKS).
100 |  * @param {string} [parameters.expectedAlg='RS256'] algorithm in which the id_token was signed
101 |  * and will be used to validate
102 |  * @param {number} [parameters.leeway=60] number of seconds that the clock can be out of sync
103 |  * @param {number} [parameters.maxAge] max age
104 |  * while validating expiration of the id_token
105 |  */
106 | function IdTokenVerifier(parameters) {
107 |   var options = parameters || {};
108 | 
109 |   this.jwksCache = options.jwksCache || new DummyCache();
110 |   this.expectedAlg = options.expectedAlg || 'RS256';
111 |   this.issuer = options.issuer;
112 |   this.audience = options.audience;
113 |   this.leeway = options.leeway === 0 ? 0 : options.leeway || DEFAULT_LEEWAY;
114 |   this.jwksURI = options.jwksURI;
115 |   this.maxAge = options.maxAge;
116 | 
117 |   this.__clock =
118 |     typeof options.__clock === 'function' ? options.__clock : defaultClock;
119 | 
120 |   if (this.leeway < 0 || this.leeway > 300) {
121 |     throw new error.ConfigurationError(
122 |       'The leeway should be positive and lower than five minutes.'
123 |     );
124 |   }
125 | 
126 |   if (supportedAlg !== this.expectedAlg) {
127 |     throw new error.ConfigurationError(
128 |       'Signature algorithm of "' +
129 |         this.expectedAlg +
130 |         '" is not supported. Expected the ID token to be signed with "' +
131 |         supportedAlg +
132 |         '".'
133 |     );
134 |   }
135 | }
136 | 
137 | /**
138 |  * @callback verifyCallback
139 |  * @param {?Error} err error returned if the verify cannot be performed
140 |  * @param {?object} payload payload returned if the token is valid
141 |  */
142 | 
143 | /**
144 |  * Verifies an id_token
145 |  *
146 |  * It will validate:
147 |  * - signature according to the algorithm configured in the verifier.
148 |  * - if nonce is present and matches the one provided
149 |  * - if `iss` and `aud` claims matches the configured issuer and audience
150 |  * - if token is not expired and valid (if the `nbf` claim is in the past)
151 |  *
152 |  * @method verify
153 |  * @param {string} token id_token to verify
154 |  * @param {string} [requestedNonce] nonce value that should match the one in the id_token claims
155 |  * @param {verifyCallback} cb callback used to notify the results of the validation
156 |  */
157 | IdTokenVerifier.prototype.verify = function(token, requestedNonce, cb) {
158 |   if (!cb && requestedNonce && typeof requestedNonce == 'function') {
159 |     cb = requestedNonce;
160 |     requestedNonce = undefined;
161 |   }
162 |   if (!token) {
163 |     return cb(
164 |       new error.TokenValidationError('ID token is required but missing'),
165 |       null
166 |     );
167 |   }
168 | 
169 |   var jwt = this.decode(token);
170 | 
171 |   if (jwt instanceof Error) {
172 |     return cb(
173 |       new error.TokenValidationError('ID token could not be decoded'),
174 |       null
175 |     );
176 |   }
177 | 
178 |   /* eslint-disable vars-on-top */
179 |   var headerAndPayload = jwt.encoded.header + '.' + jwt.encoded.payload;
180 |   var signature = base64.decodeToHEX(jwt.encoded.signature);
181 | 
182 |   var alg = jwt.header.alg;
183 |   var kid = jwt.header.kid;
184 | 
185 |   var aud = jwt.payload.aud;
186 |   var sub = jwt.payload.sub;
187 |   var iss = jwt.payload.iss;
188 |   var exp = jwt.payload.exp;
189 |   var nbf = jwt.payload.nbf;
190 |   var iat = jwt.payload.iat;
191 |   var azp = jwt.payload.azp;
192 |   var auth_time = jwt.payload.auth_time;
193 |   var nonce = jwt.payload.nonce;
194 |   var now = this.__clock();
195 | 
196 |   /* eslint-enable vars-on-top */
197 |   var _this = this;
198 | 
199 |   if (_this.expectedAlg !== alg) {
200 |     return cb(
201 |       new error.TokenValidationError(
202 |         'Signature algorithm of "' +
203 |           alg +
204 |           '" is not supported. Expected the ID token to be signed with "' +
205 |           supportedAlg +
206 |           '".'
207 |       ),
208 |       null
209 |     );
210 |   }
211 | 
212 |   this.getRsaVerifier(iss, kid, function(err, rsaVerifier) {
213 |     if (err) {
214 |       return cb(err, null);
215 |     }
216 | 
217 |     if (!rsaVerifier.verify(headerAndPayload, signature)) {
218 |       return cb(
219 |         new error.TokenValidationError('Invalid ID token signature.'),
220 |         null
221 |       );
222 |     }
223 | 
224 |     if (!iss || typeof iss !== 'string') {
225 |       return cb(
226 |         new error.TokenValidationError(
227 |           'Issuer (iss) claim must be a string present in the ID token'
228 |         ),
229 |         null
230 |       );
231 |     }
232 | 
233 |     if (_this.issuer !== iss) {
234 |       return cb(
235 |         new error.TokenValidationError(
236 |           'Issuer (iss) claim mismatch in the ID token, expected "' +
237 |             _this.issuer +
238 |             '", found "' +
239 |             iss +
240 |             '"'
241 |         ),
242 |         null
243 |       );
244 |     }
245 | 
246 |     if (!sub || typeof sub !== 'string') {
247 |       return cb(
248 |         new error.TokenValidationError(
249 |           'Subject (sub) claim must be a string present in the ID token'
250 |         ),
251 |         null
252 |       );
253 |     }
254 | 
255 |     if (!aud || (typeof aud !== 'string' && !Array.isArray(aud))) {
256 |       return cb(
257 |         new error.TokenValidationError(
258 |           'Audience (aud) claim must be a string or array of strings present in the ID token'
259 |         ),
260 |         null
261 |       );
262 |     }
263 | 
264 |     if (Array.isArray(aud) && !aud.includes(_this.audience)) {
265 |       return cb(
266 |         new error.TokenValidationError(
267 |           'Audience (aud) claim mismatch in the ID token; expected "' +
268 |             _this.audience +
269 |             '" but was not one of "' +
270 |             aud.join(', ') +
271 |             '"'
272 |         ),
273 |         null
274 |       );
275 |     } else if (typeof aud === 'string' && _this.audience !== aud) {
276 |       return cb(
277 |         new error.TokenValidationError(
278 |           'Audience (aud) claim mismatch in the ID token; expected "' +
279 |             _this.audience +
280 |             '" but found "' +
281 |             aud +
282 |             '"'
283 |         ),
284 |         null
285 |       );
286 |     }
287 | 
288 |     if (requestedNonce) {
289 |       if (!nonce || typeof nonce !== 'string') {
290 |         return cb(
291 |           new error.TokenValidationError(
292 |             'Nonce (nonce) claim must be a string present in the ID token'
293 |           ),
294 |           null
295 |         );
296 |       }
297 | 
298 |       if (nonce !== requestedNonce) {
299 |         return cb(
300 |           new error.TokenValidationError(
301 |             'Nonce (nonce) claim value mismatch in the ID token; expected "' +
302 |               requestedNonce +
303 |               '", found "' +
304 |               nonce +
305 |               '"'
306 |           ),
307 |           null
308 |         );
309 |       }
310 |     }
311 | 
312 |     if (Array.isArray(aud) && aud.length > 1) {
313 |       if (!azp || typeof azp !== 'string') {
314 |         return cb(
315 |           new error.TokenValidationError(
316 |             'Authorized Party (azp) claim must be a string present in the ID token when Audience (aud) claim has multiple values'
317 |           ),
318 |           null
319 |         );
320 |       }
321 | 
322 |       if (azp !== _this.audience) {
323 |         return cb(
324 |           new error.TokenValidationError(
325 |             'Authorized Party (azp) claim mismatch in the ID token; expected "' +
326 |               _this.audience +
327 |               '", found "' +
328 |               azp +
329 |               '"'
330 |           ),
331 |           null
332 |         );
333 |       }
334 |     }
335 | 
336 |     if (!exp || !isNumber(exp)) {
337 |       return cb(
338 |         new error.TokenValidationError(
339 |           'Expiration Time (exp) claim must be a number present in the ID token'
340 |         ),
341 |         null
342 |       );
343 |     }
344 | 
345 |     if (!iat || !isNumber(iat)) {
346 |       return cb(
347 |         new error.TokenValidationError(
348 |           'Issued At (iat) claim must be a number present in the ID token'
349 |         ),
350 |         null
351 |       );
352 |     }
353 | 
354 |     var expTime = exp + _this.leeway;
355 |     var expTimeDate = new Date(0);
356 |     expTimeDate.setUTCSeconds(expTime);
357 | 
358 |     if (now > expTimeDate) {
359 |       return cb(
360 |         new error.TokenValidationError(
361 |           'Expiration Time (exp) claim error in the ID token; current time "' +
362 |             now +
363 |             '" is after expiration time "' +
364 |             expTimeDate +
365 |             '"'
366 |         ),
367 |         null
368 |       );
369 |     }
370 | 
371 |     if (nbf && isNumber(nbf)) {
372 |       var nbfTime = nbf - _this.leeway;
373 |       var nbfTimeDate = new Date(0);
374 |       nbfTimeDate.setUTCSeconds(nbfTime);
375 | 
376 |       if (now < nbfTimeDate) {
377 |         return cb(
378 |           new error.TokenValidationError(
379 |             'Not Before Time (nbf) claim error in the ID token; current time "' +
380 |               now +
381 |               '" is before the not before time "' +
382 |               nbfTimeDate +
383 |               '"'
384 |           ),
385 |           null
386 |         );
387 |       }
388 |     }
389 | 
390 |     if (_this.maxAge) {
391 |       if (!auth_time || !isNumber(auth_time)) {
392 |         return cb(
393 |           new error.TokenValidationError(
394 |             'Authentication Time (auth_time) claim must be a number present in the ID token when Max Age (max_age) is specified'
395 |           ),
396 |           null
397 |         );
398 |       }
399 | 
400 |       var authValidUntil = auth_time + _this.maxAge + _this.leeway;
401 |       var authTimeDate = new Date(0);
402 | 
403 |       authTimeDate.setUTCSeconds(authValidUntil);
404 | 
405 |       if (now > authTimeDate) {
406 |         return cb(
407 |           new error.TokenValidationError(
408 |             `Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time "${now}" is after last auth time at "${authTimeDate}"`
409 |           ),
410 |           null
411 |         );
412 |       }
413 |     }
414 | 
415 |     return cb(null, jwt.payload);
416 |   });
417 | };
418 | 
419 | IdTokenVerifier.prototype.getRsaVerifier = function(iss, kid, cb) {
420 |   var _this = this;
421 |   var cachekey = iss + kid;
422 | 
423 |   Promise.resolve(this.jwksCache.has(cachekey))
424 |     .then(function(hasKey) {
425 |       if (!hasKey) {
426 |         return jwks.getJWKS({
427 |           jwksURI: _this.jwksURI,
428 |           iss: iss,
429 |           kid: kid
430 |         });
431 |       } else {
432 |         return _this.jwksCache.get(cachekey);
433 |       }
434 |     })
435 |     .then(function(keyInfo) {
436 |       if (!keyInfo || !keyInfo.modulus || !keyInfo.exp) {
437 |         throw new Error('Empty keyInfo in response');
438 |       }
439 |       return Promise.resolve(_this.jwksCache.set(cachekey, keyInfo)).then(
440 |         function() {
441 |           cb && cb(null, new RSAVerifier(keyInfo.modulus, keyInfo.exp));
442 |         }
443 |       );
444 |     })
445 |     .catch(function(err) {
446 |       cb && cb(err);
447 |     });
448 | };
449 | 
450 | /**
451 |  * @typedef DecodedToken
452 |  * @type {Object}
453 |  * @property {Object} header - content of the JWT header.
454 |  * @property {Object} payload - token claims.
455 |  * @property {Object} encoded - encoded parts of the token.
456 |  */
457 | 
458 | /**
459 |  * Decodes a well formed JWT without any verification
460 |  *
461 |  * @method decode
462 |  * @param {string} token decodes the token
463 |  * @return {DecodedToken} if token is valid according to `exp` and `nbf`
464 |  */
465 | IdTokenVerifier.prototype.decode = function(token) {
466 |   var parts = token.split('.');
467 |   var header;
468 |   var payload;
469 | 
470 |   if (parts.length !== 3) {
471 |     return new error.TokenValidationError('Cannot decode a malformed JWT');
472 |   }
473 | 
474 |   try {
475 |     header = JSON.parse(base64.decodeToString(parts[0]));
476 |     payload = JSON.parse(base64.decodeToString(parts[1]));
477 |   } catch (e) {
478 |     return new error.TokenValidationError(
479 |       'Token header or payload is not valid JSON'
480 |     );
481 |   }
482 | 
483 |   return {
484 |     header: header,
485 |     payload: payload,
486 |     encoded: {
487 |       header: parts[0],
488 |       payload: parts[1],
489 |       signature: parts[2]
490 |     }
491 |   };
492 | };
493 | 
494 | /**
495 |  * @callback validateAccessTokenCallback
496 |  * @param {Error} [err] error returned if the validation cannot be performed
497 |  * or the token is invalid. If there is no error, then the access_token is valid.
498 |  */
499 | 
500 | /**
501 |  * Validates an access_token based on {@link http://openid.net/specs/openid-connect-core-1_0.html#ImplicitTokenValidation}.
502 |  * The id_token from where the alg and atHash parameters are taken,
503 |  * should be decoded and verified before using thisfunction
504 |  *
505 |  * @method validateAccessToken
506 |  * @param {string} access_token the access_token
507 |  * @param {string} alg The algorithm defined in the header of the
508 |  * previously verified id_token under the "alg" claim.
509 |  * @param {string} atHash The "at_hash" value included in the payload
510 |  * of the previously verified id_token.
511 |  * @param {validateAccessTokenCallback} cb callback used to notify the results of the validation.
512 |  */
513 | IdTokenVerifier.prototype.validateAccessToken = function(
514 |   accessToken,
515 |   alg,
516 |   atHash,
517 |   cb
518 | ) {
519 |   if (this.expectedAlg !== alg) {
520 |     return cb(
521 |       new error.TokenValidationError(
522 |         'Signature algorithm of "' +
523 |           alg +
524 |           '" is not supported. Expected "' +
525 |           this.expectedAlg +
526 |           '"'
527 |       )
528 |     );
529 |   }
530 |   var sha256AccessToken = sha256(accessToken);
531 |   var hashToHex = cryptoHex.stringify(sha256AccessToken);
532 |   var hashToHexFirstHalf = hashToHex.substring(0, hashToHex.length / 2);
533 |   var hashFirstHalfWordArray = cryptoHex.parse(hashToHexFirstHalf);
534 |   var hashFirstHalfBase64 = cryptoBase64.stringify(hashFirstHalfWordArray);
535 |   var hashFirstHalfBase64SafeUrl = base64.base64ToBase64Url(
536 |     hashFirstHalfBase64
537 |   );
538 |   if (hashFirstHalfBase64SafeUrl !== atHash) {
539 |     return cb(new error.TokenValidationError('Invalid access_token'));
540 |   }
541 |   return cb(null);
542 | };
543 | 
544 | export default IdTokenVerifier;
545 | 
546 |
547 |
548 |
549 | 550 |
551 | 552 |
553 | Generated by JSDoc 3.6.11 on 554 | Thu Oct 26 2023 15:40:27 GMT+0100 (Irish Standard Time) using the Minami 555 | theme. 556 |
557 | 558 | 561 | 562 | 563 | 564 | -------------------------------------------------------------------------------- /docs/global.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Global - Documentation 7 | 8 | 9 | 10 | 13 | 18 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 64 | 65 |
66 |

Global

67 | 68 |
69 |
70 |

71 |
72 | 73 |
74 |
75 |
76 |
77 | 78 |

Methods

79 | 80 |
81 |

82 | decode(token) 85 | → {DecodedToken} 88 |

89 | 90 |
91 |

Decodes a well formed JWT without any verification

92 |
93 | 94 |
95 |
Source:
96 |
97 | 103 |
104 |
105 | 106 |
Parameters:
107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 126 | 127 | 130 | 131 | 132 |
NameTypeDescription
token 124 | string 125 | 128 |

decodes the token

129 |
133 | 134 |
135 |
Returns:
136 | 137 |
138 |
139 | Type: 140 |
141 |
142 | DecodedToken 147 |
148 |
149 | 150 |
151 |

152 | if token is valid according to exp and 153 | nbf 154 |

155 |
156 |
157 |
158 | 159 |
160 |

161 | validateAccessToken(access_token, alg, atHash, cb) 165 |

166 | 167 |
168 |

169 | Validates an access_token based on 170 | http://openid.net/specs/openid-connect-core-1_0.html#ImplicitTokenValidation. The id_token from where the alg and atHash parameters are 174 | taken, should be decoded and verified before using thisfunction 175 |

176 |
177 | 178 |
179 |
Source:
180 |
181 | 187 |
188 |
189 | 190 |
Parameters:
191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 210 | 211 | 214 | 215 | 216 | 217 | 218 | 219 | 222 | 223 | 229 | 230 | 231 | 232 | 233 | 234 | 237 | 238 | 244 | 245 | 246 | 247 | 248 | 249 | 258 | 259 | 264 | 265 | 266 |
NameTypeDescription
access_token 208 | string 209 | 212 |

the access_token

213 |
alg 220 | string 221 | 224 |

225 | The algorithm defined in the header of the previously 226 | verified id_token under the "alg" claim. 227 |

228 |
atHash 235 | string 236 | 239 |

240 | The "at_hash" value included in the payload of 241 | the previously verified id_token. 242 |

243 |
cb 250 | validateAccessTokenCallback 257 | 260 |

261 | callback used to notify the results of the validation. 262 |

263 |
267 |
268 | 269 |
270 |

271 | verify(token, requestedNonceopt, cb) 276 |

277 | 278 |
279 |

Verifies an id_token

280 |

It will validate:

281 |
    282 |
  • 283 | signature according to the algorithm configured in the 284 | verifier. 285 |
  • 286 |
  • if nonce is present and matches the one provided
  • 287 |
  • 288 | if iss and aud claims matches the 289 | configured issuer and audience 290 |
  • 291 |
  • 292 | if token is not expired and valid (if the 293 | nbf claim is in the past) 294 |
  • 295 |
296 |
297 | 298 |
299 |
Source:
300 |
301 | 307 |
308 |
309 | 310 |
Parameters:
311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 332 | 333 | 334 | 335 | 338 | 339 | 340 | 341 | 342 | 343 | 346 | 347 | 348 | 349 | 355 | 356 | 357 | 358 | 359 | 360 | 369 | 370 | 371 | 372 | 375 | 376 | 377 |
NameTypeAttributesDescription
token 330 | string 331 | 336 |

id_token to verify

337 |
requestedNonce 344 | string 345 | <optional>
350 |

351 | nonce value that should match the one in the id_token 352 | claims 353 |

354 |
cb 361 | verifyCallback 368 | 373 |

callback used to notify the results of the validation

374 |
378 |
379 | 380 |

Type Definitions

381 | 382 |
383 |

DecodedToken

384 | 385 |
Properties:
386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 405 | 406 | 409 | 410 | 411 | 412 | 413 | 414 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 427 | 428 | 431 | 432 | 433 |
NameTypeDescription
header 403 | Object 404 | 407 |

content of the JWT header.

408 |
payload 415 | Object 416 |

token claims.

encoded 425 | Object 426 | 429 |

encoded parts of the token.

430 |
434 | 435 |
436 |
Source:
437 |
438 | 444 |
445 |
446 | 447 |
Type:
448 |
    449 |
  • 450 | Object 451 |
  • 452 |
453 |
454 | 455 |
456 |

457 | validateAccessTokenCallback(erropt) 461 |

462 | 463 |
464 |
Source:
465 |
466 | 472 |
473 |
474 | 475 |
Parameters:
476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 497 | 498 | 499 | 500 | 507 | 508 | 509 |
NameTypeAttributesDescription
err 495 | Error 496 | <optional>
501 |

502 | error returned if the validation cannot be performed or 503 | the token is invalid. If there is no error, then the 504 | access_token is valid. 505 |

506 |
510 |
511 | 512 |
513 |

514 | verifyCallback(errnullable, 517 | payloadnullable) 519 |

520 | 521 |
522 |
Source:
523 |
524 | 530 |
531 |
532 | 533 |
Parameters:
534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 555 | 556 | 557 | 558 | 561 | 562 | 563 | 564 | 565 | 566 | 569 | 570 | 571 | 572 | 575 | 576 | 577 |
NameTypeAttributesDescription
err 553 | Error 554 | <nullable>
559 |

error returned if the verify cannot be performed

560 |
payload 567 | object 568 | <nullable>
573 |

payload returned if the token is valid

574 |
578 |
579 |
580 |
581 |
582 | 583 |
584 | 585 |
586 | Generated by JSDoc 3.6.11 on 587 | Thu Oct 26 2023 15:40:27 GMT+0100 (Irish Standard Time) using the Minami 588 | theme. 589 |
590 | 591 | 594 | 595 | 596 | 597 | --------------------------------------------------------------------------------