├── assets ├── .gitkeep ├── gifs │ └── .gitkeep └── screenshots │ └── .gitkeep ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── artifacts-zip.yml ├── devtools.html ├── icons ├── icon16.png ├── icon32.png ├── icon48.png └── icon128.png ├── part.edge.json ├── devtools.js ├── .gitignore ├── styles ├── popup.scss ├── options.scss └── jwt-panel.scss ├── .gitattributes ├── part.firefox.json ├── popup.html ├── scripts ├── generateChromeExtension.sh ├── generateEdgeExtension.sh └── generateFirefoxExtension.sh ├── jwt-panel.html ├── EDGE.md ├── CHROME.md ├── FIREFOX.md ├── README.md ├── manifest.json ├── options.html ├── LICENSE ├── options.js ├── package.json ├── jwt-panel.js └── encoder.js /assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/gifs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/screenshots/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [piraces] 2 | ko_fi: piraces 3 | liberapay: piraces 4 | -------------------------------------------------------------------------------- /devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piraces/jwt-inspector-malicious-poc/main/icons/icon16.png -------------------------------------------------------------------------------- /icons/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piraces/jwt-inspector-malicious-poc/main/icons/icon32.png -------------------------------------------------------------------------------- /icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piraces/jwt-inspector-malicious-poc/main/icons/icon48.png -------------------------------------------------------------------------------- /part.edge.json: -------------------------------------------------------------------------------- 1 | { 2 | "update_URL": "https://edge.microsoft.com/extensionwebstorebase/v1/crx" 3 | } -------------------------------------------------------------------------------- /icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piraces/jwt-inspector-malicious-poc/main/icons/icon128.png -------------------------------------------------------------------------------- /devtools.js: -------------------------------------------------------------------------------- 1 | chrome.devtools.panels.create( 2 | "JWT", "", "jwt-panel.html", function(panel) {} 3 | ); 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache 2 | styles/*.css 3 | styles/*.min.css 4 | styles/*.min.css.map 5 | styles/*.css.map 6 | node_modules 7 | .DS_Store 8 | *.zip 9 | .vs -------------------------------------------------------------------------------- /styles/popup.scss: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: monospace; 3 | } 4 | 5 | body { 6 | width: 20rem; 7 | } 8 | 9 | p { 10 | font-size: medium; 11 | } 12 | 13 | a { 14 | font-size: medium; 15 | } 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=lf 3 | 4 | # Denote all files that are truly binary and should not be modified. 5 | *.png binary 6 | *.jpg binary 7 | *.gif binary 8 | -------------------------------------------------------------------------------- /part.firefox.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "browser_specific_settings": { 4 | "gecko": { 5 | "id": "raul.piraces@gmail.com", 6 | "strict_min_version": "57.0" 7 | } 8 | }, 9 | "browser_action": { 10 | "default_popup": "popup.html" 11 | } 12 | } -------------------------------------------------------------------------------- /styles/options.scss: -------------------------------------------------------------------------------- 1 | #status { 2 | height: 1.5em; 3 | } 4 | 5 | @media (prefers-color-scheme: dark) { 6 | * { 7 | color: silver; 8 | background-color: #202124; 9 | } 10 | 11 | button { 12 | background-color: silver; 13 | color: #202124; 14 | } 15 | 16 | input { 17 | border-style: solid; 18 | border-color: #333333; 19 | } 20 | } -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
    8 |
  1. Open developer tools and select the JWT tab
  2. 9 |
  3. Use a site which sends JWT bearer tokens in the Authorization HTTP header
  4. 10 |
  5. See the token contents in the developer tools panel
  6. 11 |
12 | More information 13 | 14 | -------------------------------------------------------------------------------- /scripts/generateChromeExtension.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | npm install 4 | npm run process-styles 5 | npm run js-minify 6 | npm run clean 7 | npm install --production 8 | rm -rf assets && rm -rf .github && rm -rf .git && rm .gitignore package-lock.json package.json part.edge.json part.firefox.json styles/options.css styles/options.css.map styles/options.scss styles/popup.css styles/popup.css.map styles/popup.scss styles/jwt-panel.css styles/jwt-panel.css.map styles/jwt-panel.scss 9 | zip -r jwt-dev-inspector-chrome.zip . -------------------------------------------------------------------------------- /jwt-panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 11 |
12 |
13 | Header 14 | 15 |
16 |
17 | Claims 18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /scripts/generateEdgeExtension.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | npm install 4 | npm run process-styles 5 | npm run js-minify 6 | npm run clean 7 | npm install --production 8 | echo $(jq -s 'reduce .[] as $item ({}; . * $item)' manifest.json part.edge.json) > manifest.json 9 | rm -rf assets && rm -rf .github && rm -rf .git && rm .gitignore package-lock.json package.json part.edge.json part.firefox.json CHANGELOG.md styles/options.css styles/options.css.map styles/options.scss styles/popup.css styles/popup.css.map styles/popup.scss styles/jwt-panel.css styles/jwt-panel.css.map styles/jwt-panel.scss 10 | zip -r jwt-dev-inspector-edge.zip . -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEAT] - " 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /scripts/generateFirefoxExtension.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | npm install 4 | npm run process-styles 5 | npm run js-minify 6 | npm run clean 7 | npm install --production 8 | echo $(jq 'del(.background)' manifest.json) > manifest.json 9 | echo $(jq 'del(.action)' manifest.json) > manifest.json 10 | echo $(jq -s 'reduce .[] as $item ({}; . * $item)' manifest.json part.firefox.json) > manifest.json 11 | rm -rf assets && rm -rf .github && rm -rf .git && rm .gitignore package-lock.json package.json part.edge.json part.firefox.json styles/options.css styles/options.css.map styles/options.scss styles/popup.css styles/popup.css.map styles/popup.scss styles/jwt-panel.css styles/jwt-panel.css.map styles/jwt-panel.scss 12 | zip -r jwt-dev-inspector-firefox.xpi . -------------------------------------------------------------------------------- /EDGE.md: -------------------------------------------------------------------------------- 1 | # Generating the ZIP file for the Edge extension 2 | 3 | This extension has a script in the `./scripts` folder named `generateEdgeExtension.sh` which is executed in the publish pipeline in order to generate the final ZIP to publish to the Edge extensions store. 4 | 5 | This script executes different `npm` script in order to build the extension for production mode, delete all unneeded files and generate the final `manifest.json`. 6 | 7 | 8 | The following tools are used with the following purposes: 9 | - `npm`: generate the final extension files and clean unneeded files. 10 | - `jq`: replacements inside the `manifest.json` file to make it cross-browser supported. 11 | - `find`: removal of unneeded files. 12 | - `zip`: generate the final bundled extension in `.zip` format. -------------------------------------------------------------------------------- /CHROME.md: -------------------------------------------------------------------------------- 1 | # Generating the ZIP file for the Chrome extension 2 | 3 | This extension has a script in the `./scripts` folder named `generateChromeExtension.sh` which is executed in the publish pipeline in order to generate the final ZIP to publish to the Chrome extensions store. 4 | 5 | This script executes different `npm` script in order to build the extension for production mode, delete all unneeded files and generate the final `manifest.json`. 6 | 7 | 8 | The following tools are used with the following purposes: 9 | - `npm`: generate the final extension files and clean unneeded files. 10 | - `jq`: replacements inside the `manifest.json` file to make it cross-browser supported. 11 | - `find`: removal of unneeded files. 12 | - `zip`: generate the final bundled extension in `.zip` format. -------------------------------------------------------------------------------- /FIREFOX.md: -------------------------------------------------------------------------------- 1 | # Generating the XPI file for the Firefox extension 2 | 3 | This extension has a script in the `./scripts` folder named `generateFirefoxExtension.sh` which is executed in the publish pipeline in order to generate the final XPI to publish to the Firefox Add-ons store. 4 | 5 | This script executes different `npm` script in order to build the extension for production mode, delete all unneeded files and generate the final `manifest.json`. 6 | 7 | 8 | The following tools are used with the following purposes: 9 | - `npm`: generate the final extension files and clean unneeded files. 10 | - `jq`: replacements inside the `manifest.json` file to make it cross-browser supported. 11 | - `find`: removal of unneeded files. 12 | - `zip`: generate the final bundled extension in `.xpi` format. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] - " 5 | labels: bug 6 | assignees: piraces 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JWT Inspector 2 | 3 | JWT Inspector is a Chrome extension which makes it easy to inspect the content of 4 | any JWT bearer token sent by a webapp. 5 | 6 | The extension adds a new **JWT** tab in Chrome's Developer Tools. 7 | When the tab is open, the extension inspects all server requests and picks out 8 | the token from any request which has an `Authorization` header containing a JWT 9 | bearer token. 10 | 11 | # Development 12 | 13 | The main implementation file is `jwt-panel.js`. 14 | 15 | You can install the extension as "unpacked" straight from the source directory to 16 | directly test any changes you make. (Enable "Developer mode" in 17 | [chrome://extensions/](chrome://extensions/) 18 | to make this option available.) 19 | 20 | Running `build.sh` packages the extension for upload to the chrome web store. 21 | Don't forget to change the version number in `manifest.json` before creating the package. 22 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JWT Dev Inspector", 3 | "version": "1.6.2", 4 | "description": "Display JWT bearer tokens in a new tab in Chrome's Developer Tools", 5 | "author": "Raúl Piracés", 6 | "homepage_url": "https://piraces.github.io/jwt-inspector/", 7 | "manifest_version": 3, 8 | "devtools_page": "devtools.html", 9 | "options_ui": { 10 | "page": "options.html", 11 | "open_in_tab": false 12 | }, 13 | "permissions": ["storage"], 14 | "incognito": "split", 15 | "action": { 16 | "default_icon": { 17 | "16": "/icons/icon16.png", 18 | "32": "/icons/icon32.png", 19 | "48": "/icons/icon48.png", 20 | "128": "/icons/icon128.png" 21 | }, 22 | "default_popup": "popup.html" 23 | }, 24 | "icons": { 25 | "16": "/icons/icon16.png", 26 | "32": "/icons/icon32.png", 27 | "48": "/icons/icon48.png", 28 | "128": "/icons/icon128.png" 29 | } 30 | } -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JWT Inspector Options 6 | 7 | 8 | 9 | 10 | 15 | 18 | 19 | 20 | 25 | 29 | 30 | 31 | 36 | 39 | 40 |
11 | 14 | 16 | 17 |
21 | 24 | 26 |
27 | (More than one? Use , between them) 28 |
32 | 35 | 37 | 38 |
41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Tore Green 4 | 5 | Modifications to the theme and codebase Copyright (c) 2023 Raúl Piracés @piraces 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /styles/jwt-panel.scss: -------------------------------------------------------------------------------- 1 | td:first-child { 2 | color: green; 3 | font-weight: bold; 4 | text-align: right; 5 | vertical-align: text-top; 6 | padding-right: 0.5em; 7 | min-width: 70px; 8 | max-width: 50%; 9 | overflow-wrap: anywhere; 10 | } 11 | 12 | td:first-child::after { 13 | content: ":"; 14 | } 15 | 16 | .ts { 17 | font-style: italic; 18 | color: firebrick; 19 | } 20 | 21 | .ts::before { 22 | content: " ("; 23 | } 24 | 25 | .ts::after { 26 | content: ")"; 27 | } 28 | 29 | fieldset { 30 | border: 2px solid #000; 31 | -moz-border-radius: 8px; 32 | -webkit-border-radius: 8px; 33 | border-radius: 8px; 34 | } 35 | 36 | legend { 37 | color: #000; 38 | font-size: 1.5em; 39 | } 40 | 41 | @media (prefers-color-scheme: dark) { 42 | * { 43 | color: silver; 44 | background-color: #202124; 45 | } 46 | 47 | button { 48 | background-color: silver; 49 | color: #202124; 50 | } 51 | 52 | td:first-child { 53 | color: darkseagreen; 54 | } 55 | 56 | .ts { 57 | color: lightcoral; 58 | } 59 | 60 | fieldset { 61 | border: thick solid #494c50; 62 | } 63 | 64 | legend { 65 | color: #9aa0a5; 66 | } 67 | } -------------------------------------------------------------------------------- /options.js: -------------------------------------------------------------------------------- 1 | // Saves options to chrome.storage 2 | function save_options() { 3 | var header_name = document.getElementById('header_name').value; 4 | var header_prefix = document.getElementById('header_prefix').value; 5 | var copy_prefix = document.getElementById('copy_prefix').checked; 6 | chrome.storage.local.set({ 7 | header_name: header_name, 8 | header_prefix: header_prefix, 9 | copy_prefix: copy_prefix 10 | }, function() { 11 | // Update status to let user know options were saved. 12 | var status = document.getElementById('status'); 13 | status.textContent = 'Options saved.'; 14 | setTimeout(function() { 15 | status.textContent = ''; 16 | }, 750); 17 | }); 18 | } 19 | 20 | function restore_options() { 21 | chrome.storage.local.get({ 22 | header_name: "Authorization", 23 | header_prefix: "Bearer", 24 | copy_prefix: false 25 | }, function(items) { 26 | document.getElementById('header_name').value = items.header_name; 27 | document.getElementById('header_prefix').value = items.header_prefix; 28 | document.getElementById('copy_prefix').checked = items.copy_prefix; 29 | }); 30 | } 31 | 32 | // Resets options to default values 33 | function reset_options() { 34 | document.getElementById('header_name').value = "Authorization"; 35 | document.getElementById('header_prefix').value = "Bearer"; 36 | document.getElementById('copy_prefix').checked = false; 37 | save_options(); 38 | } 39 | 40 | document.addEventListener('DOMContentLoaded', restore_options); 41 | document.getElementById('save').addEventListener('click', save_options); 42 | document.getElementById('reset').addEventListener('click', reset_options); 43 | -------------------------------------------------------------------------------- /.github/workflows/artifacts-zip.yml: -------------------------------------------------------------------------------- 1 | name: Generate artifacts to publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | chrome: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Use Node.js 18.x 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: 18 17 | - run: | 18 | chmod +x ./scripts/generateChromeExtension.sh 19 | ./scripts/generateChromeExtension.sh 20 | - uses: actions/upload-artifact@v3 21 | with: 22 | name: jwt-dev-inspector-chrome 23 | path: jwt-dev-inspector-chrome.zip 24 | 25 | firefox: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v3 29 | - name: Use Node.js 18.x 30 | uses: actions/setup-node@v3 31 | with: 32 | node-version: 18 33 | - run: | 34 | chmod +x ./scripts/generateFirefoxExtension.sh 35 | ./scripts/generateFirefoxExtension.sh 36 | - uses: actions/upload-artifact@v3 37 | with: 38 | name: jwt-dev-inspector-firefox 39 | path: jwt-dev-inspector-firefox.xpi 40 | 41 | edge: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v3 45 | - name: Use Node.js 18.x 46 | uses: actions/setup-node@v3 47 | with: 48 | node-version: 18 49 | - run: | 50 | chmod +x ./scripts/generateEdgeExtension.sh 51 | ./scripts/generateEdgeExtension.sh 52 | - uses: actions/upload-artifact@v3 53 | with: 54 | name: jwt-dev-inspector-edge 55 | path: jwt-dev-inspector-edge.zip -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jwt-dev-inspector", 3 | "version": "1.6.2", 4 | "description": "Display JWT bearer tokens in a new tab in Chrome's Developer Tools", 5 | "main": "main.js", 6 | "scripts": { 7 | "process-styles": "npm run compile-styles && npm run css-minify", 8 | "compile-styles": "node-sass --output-style expanded --source-map true --source-map-contents true --precision 6 styles/popup.scss styles/popup.css && node-sass --output-style expanded --source-map true --source-map-contents true --precision 6 styles/options.scss styles/options.css && node-sass --output-style expanded --source-map true --source-map-contents true --precision 6 styles/jwt-panel.scss styles/jwt-panel.css", 9 | "css-minify": "cleancss --format breaksWith=lf --output styles/popup.min.css styles/popup.css && cleancss --format breaksWith=lf --output styles/options.min.css styles/options.css && cleancss --format breaksWith=lf --output styles/jwt-panel.min.css styles/jwt-panel.css", 10 | "js-minify": "uglifyjs devtools.js -o devtools.js && uglifyjs encoder.js -o encoder.js && uglifyjs jwt-panel.js -o jwt-panel.js && uglifyjs options.js -o options.js", 11 | "clean": "rimraf node_modules" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/piraces/jwt-inspector.git" 16 | }, 17 | "keywords": [ 18 | "chrome-extension", 19 | "extension", 20 | "JWT", 21 | "authentication" 22 | ], 23 | "author": "Raúl Piracés", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/piraces/jwt-inspector/issues" 27 | }, 28 | "homepage": "https://github.com/piraces/jwt-inspector#readme", 29 | "dependencies": { 30 | }, 31 | "devDependencies": { 32 | "clean-css-cli": "^5.6.2", 33 | "node-sass": "^8.0.0", 34 | "rimraf": "^4.1.2", 35 | "uglify-js": "^3.17.4" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /jwt-panel.js: -------------------------------------------------------------------------------- 1 | var options = { 2 | header_name: "authorization", 3 | header_prefix: ["Bearer "], 4 | copy_prefix: false 5 | }; 6 | 7 | function setOptions(o) { 8 | options.header_name = o.header_name.toLowerCase().trim(); 9 | options.header_prefix = typeof o.header_prefix == "string" ? o.header_prefix.split(',') : o.header_prefix; 10 | options.copy_prefix = o.copy_prefix; 11 | for(var i = 0 ; i0 && !options.header_prefix[i].endsWith(' ')) { 13 | options.header_prefix[i] += ' '; 14 | } 15 | } 16 | var caption = document.getElementById("caption"); 17 | var p = options.header_prefix.length>1 ? '{'+options.header_prefix.join()+'}' : options.header_prefix[0]; 18 | caption.innerHTML = 'Waiting for request with '+Encoder.htmlEncode(o.header_name)+ 19 | ': '+Encoder.htmlEncode(p)+' [token]'+ 20 | '
(Go to Extentions > JWT Inspector > Options to customize)'; 21 | } 22 | 23 | function bearer_token(h) { 24 | if(h && h.name && h.name.toLowerCase() == options.header_name && h.value) { 25 | var p = options.header_prefix.find( s => h.value.startsWith(s) ); 26 | if(p) { 27 | return { prefix:p , tok:h.value.substring(p.length) }; 28 | } 29 | } 30 | return null; 31 | } 32 | 33 | function isObject(obj) { 34 | var type = typeof obj; 35 | return type === 'function' || type === 'object' && !!obj; 36 | } 37 | 38 | const ts_claims = ["exp","iat","nbf"]; 39 | 40 | function renderClaims(claims) { 41 | var table = document.createElement("table"); 42 | for(var c in claims) { 43 | var row = document.createElement("tr"); 44 | var td1 = document.createElement("td"); 45 | td1.appendChild(document.createTextNode(Encoder.htmlEncode(String(c)))); 46 | row.appendChild(td1); 47 | var td2 = document.createElement("td"); 48 | if(isObject(claims[c])) { 49 | td2.appendChild(renderClaims(claims[c])); 50 | } else { 51 | td2.appendChild(document.createTextNode(Encoder.htmlEncode(String(claims[c])))); 52 | if(ts_claims.includes(c)) { 53 | var ts = document.createElement("span"); 54 | ts.className = "ts"; 55 | var d = new Date(claims[c]*1000); 56 | ts.appendChild(document.createTextNode(d.toLocaleString())); 57 | td2.appendChild(ts); 58 | } 59 | } 60 | row.appendChild(td2); 61 | table.appendChild(row); 62 | } 63 | return table; 64 | } 65 | 66 | function render(header, claims, url, time) { 67 | 68 | var divHeader = document.getElementById("header"); 69 | var dlHeader = renderClaims(header); 70 | divHeader.innerHTML = ""; 71 | divHeader.appendChild(dlHeader); 72 | 73 | var div = document.getElementById("claims"); 74 | var dl = renderClaims(claims); 75 | div.innerHTML = ""; 76 | div.appendChild(dl); 77 | 78 | var caption = document.getElementById("caption"); 79 | caption.innerHTML = "Bearer token extracted from request to "+Encoder.htmlEncode(String(url)); 80 | var ts = document.createElement("span"); 81 | ts.className = "ts"; 82 | ts.appendChild(document.createTextNode(Encoder.htmlEncode(String(time)))); 83 | caption.appendChild(ts); 84 | } 85 | 86 | function updateCopyButton(p,tok) { 87 | var b = document.getElementById("copy_token"); 88 | b.dataset.token = options.copy_prefix ? p+tok : tok; 89 | b.disabled = false; 90 | } 91 | 92 | // Taken from: https://stackoverflow.com/a/18455088/1823175 93 | function copyTextToClipboard(text) { 94 | //Create a textbox field where we can insert text to. 95 | var copyFrom = document.createElement("textarea"); 96 | 97 | //Set the text content to be the text you wished to copy. 98 | copyFrom.textContent = text; 99 | 100 | //Append the textbox field into the body as a child. 101 | //"execCommand()" only works when there exists selected text, and the text is inside 102 | //document.body (meaning the text is part of a valid rendered HTML element). 103 | document.body.appendChild(copyFrom); 104 | 105 | //Select all the text! 106 | copyFrom.select(); 107 | 108 | //Execute command 109 | document.execCommand('copy'); 110 | 111 | //(Optional) De-select the text using blur(). 112 | copyFrom.blur(); 113 | 114 | //Remove the textbox field from the document.body, so no other JavaScript nor 115 | //other elements can get access to this. 116 | document.body.removeChild(copyFrom); 117 | } 118 | 119 | function copyToken() { 120 | var t = this.dataset.token; 121 | copyTextToClipboard(t); 122 | } 123 | 124 | function onRequestFinished(request) { 125 | var h = bearer_token(request.request.headers.find(bearer_token)); 126 | if(!h) return; 127 | try { 128 | var parts = h.tok.split('.'); 129 | var header = JSON.parse(atob(parts[0])); 130 | var claims = JSON.parse(atob(parts[1])); 131 | render(header, claims, request.request.url, request.startedDateTime); 132 | updateCopyButton(h.prefix,h.tok); 133 | } catch (error) { 134 | // Not a token we can extract and decode 135 | } 136 | } 137 | 138 | chrome.devtools.network.onRequestFinished.addListener(onRequestFinished); 139 | window.onload = function() { 140 | document.getElementById("copy_token").onclick = copyToken; 141 | chrome.storage.local.get(options, setOptions); 142 | }; 143 | chrome.storage.onChanged.addListener( function(changes, namespace) { 144 | chrome.storage.local.get(options, setOptions); 145 | }); 146 | -------------------------------------------------------------------------------- /encoder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A Javascript object to encode and/or decode html characters using HTML or Numeric entities that handles double or partial encoding 3 | * Author: R Reid 4 | * source: http://www.strictly-software.com/htmlencode 5 | * Licences: GPL, The MIT License (MIT) 6 | * Copyright: (c) 2011 Robert Reid - Strictly-Software.com 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | * 12 | * Revision: 13 | * 2011-07-14, Jacques-Yves Bleau: 14 | * - fixed conversion error with capitalized accentuated characters 15 | * + converted arr1 and arr2 to object property to remove redundancy 16 | * 17 | * Revision: 18 | * 2011-11-10, Ce-Yi Hio: 19 | * - fixed conversion error with a number of capitalized entity characters 20 | * 21 | * Revision: 22 | * 2011-11-10, Rob Reid: 23 | * - changed array format 24 | * 25 | * Revision: 26 | * 2012-09-23, Alex Oss: 27 | * - replaced string concatonation in numEncode with string builder, push and join for peformance with ammendments by Rob Reid 28 | */ 29 | 30 | Encoder = { 31 | 32 | // When encoding do we convert characters into html or numerical entities 33 | EncodeType : "entity", // entity OR numerical 34 | 35 | isEmpty : function(val){ 36 | if(val){ 37 | return ((val===null) || val.length==0 || /^\s+$/.test(val)); 38 | }else{ 39 | return true; 40 | } 41 | }, 42 | 43 | // arrays for conversion from HTML Entities to Numerical values 44 | arr1: [' ','¡','¢','£','¤','¥','¦','§','¨','©','ª','«','¬','­','®','¯','°','±','²','³','´','µ','¶','·','¸','¹','º','»','¼','½','¾','¿','À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','×','Ø','Ù','Ú','Û','Ü','Ý','Þ','ß','à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ð','ñ','ò','ó','ô','õ','ö','÷','ø','ù','ú','û','ü','ý','þ','ÿ','"','&','<','>','Œ','œ','Š','š','Ÿ','ˆ','˜',' ',' ',' ','‌','‍','‎','‏','–','—','‘','’','‚','“','”','„','†','‡','‰','‹','›','€','ƒ','Α','Β','Γ','Δ','Ε','Ζ','Η','Θ','Ι','Κ','Λ','Μ','Ν','Ξ','Ο','Π','Ρ','Σ','Τ','Υ','Φ','Χ','Ψ','Ω','α','β','γ','δ','ε','ζ','η','θ','ι','κ','λ','μ','ν','ξ','ο','π','ρ','ς','σ','τ','υ','φ','χ','ψ','ω','ϑ','ϒ','ϖ','•','…','′','″','‾','⁄','℘','ℑ','ℜ','™','ℵ','←','↑','→','↓','↔','↵','⇐','⇑','⇒','⇓','⇔','∀','∂','∃','∅','∇','∈','∉','∋','∏','∑','−','∗','√','∝','∞','∠','∧','∨','∩','∪','∫','∴','∼','≅','≈','≠','≡','≤','≥','⊂','⊃','⊄','⊆','⊇','⊕','⊗','⊥','⋅','⌈','⌉','⌊','⌋','⟨','⟩','◊','♠','♣','♥','♦'], 45 | arr2: [' ','¡','¢','£','¤','¥','¦','§','¨','©','ª','«','¬','­','®','¯','°','±','²','³','´','µ','¶','·','¸','¹','º','»','¼','½','¾','¿','À','Á','Â','Ã','Ä','Å','Æ','Ç','È','É','Ê','Ë','Ì','Í','Î','Ï','Ð','Ñ','Ò','Ó','Ô','Õ','Ö','×','Ø','Ù','Ú','Û','Ü','Ý','Þ','ß','à','á','â','ã','ä','å','æ','ç','è','é','ê','ë','ì','í','î','ï','ð','ñ','ò','ó','ô','õ','ö','÷','ø','ù','ú','û','ü','ý','þ','ÿ','"','&','<','>','Œ','œ','Š','š','Ÿ','ˆ','˜',' ',' ',' ','‌','‍','‎','‏','–','—','‘','’','‚','“','”','„','†','‡','‰','‹','›','€','ƒ','Α','Β','Γ','Δ','Ε','Ζ','Η','Θ','Ι','Κ','Λ','Μ','Ν','Ξ','Ο','Π','Ρ','Σ','Τ','Υ','Φ','Χ','Ψ','Ω','α','β','γ','δ','ε','ζ','η','θ','ι','κ','λ','μ','ν','ξ','ο','π','ρ','ς','σ','τ','υ','φ','χ','ψ','ω','ϑ','ϒ','ϖ','•','…','′','″','‾','⁄','℘','ℑ','ℜ','™','ℵ','←','↑','→','↓','↔','↵','⇐','⇑','⇒','⇓','⇔','∀','∂','∃','∅','∇','∈','∉','∋','∏','∑','−','∗','√','∝','∞','∠','∧','∨','∩','∪','∫','∴','∼','≅','≈','≠','≡','≤','≥','⊂','⊃','⊄','⊆','⊇','⊕','⊗','⊥','⋅','⌈','⌉','⌊','⌋','〈','〉','◊','♠','♣','♥','♦'], 46 | 47 | // Convert HTML entities into numerical entities 48 | HTML2Numerical : function(s){ 49 | return this.swapArrayVals(s,this.arr1,this.arr2); 50 | }, 51 | 52 | // Convert Numerical entities into HTML entities 53 | NumericalToHTML : function(s){ 54 | return this.swapArrayVals(s,this.arr2,this.arr1); 55 | }, 56 | 57 | 58 | // Numerically encodes all unicode characters 59 | numEncode : function(s){ 60 | if(this.isEmpty(s)) return ""; 61 | 62 | var a = [], 63 | l = s.length; 64 | 65 | for (var i=0;i "~"){ 68 | a.push("&#"); 69 | a.push(c.charCodeAt()); //numeric value of code point 70 | a.push(";"); 71 | }else{ 72 | a.push(c); 73 | } 74 | } 75 | 76 | return a.join(""); 77 | }, 78 | 79 | // HTML Decode numerical and HTML entities back to original values 80 | htmlDecode : function(s){ 81 | 82 | var c,m,d = s; 83 | 84 | if(this.isEmpty(d)) return ""; 85 | 86 | // convert HTML entites back to numerical entites first 87 | d = this.HTML2Numerical(d); 88 | 89 | // look for numerical entities " 90 | arr=d.match(/&#[0-9]{1,5};/g); 91 | 92 | // if no matches found in string then skip 93 | if(arr!=null){ 94 | for(var x=0;x= -32768 && c <= 65535){ 99 | // decode every single match within string 100 | d = d.replace(m, String.fromCharCode(c)); 101 | }else{ 102 | d = d.replace(m, ""); //invalid so replace with nada 103 | } 104 | } 105 | } 106 | 107 | return d; 108 | }, 109 | 110 | // encode an input string into either numerical or HTML entities 111 | htmlEncode : function(s,dbl){ 112 | 113 | if(this.isEmpty(s)) return ""; 114 | 115 | // do we allow double encoding? E.g will & be turned into &amp; 116 | dbl = dbl || false; //default to prevent double encoding 117 | 118 | // if allowing double encoding we do ampersands first 119 | if(dbl){ 120 | if(this.EncodeType=="numerical"){ 121 | s = s.replace(/&/g, "&"); 122 | }else{ 123 | s = s.replace(/&/g, "&"); 124 | } 125 | } 126 | 127 | // convert the xss chars to numerical entities ' " < > 128 | s = this.XSSEncode(s,false); 129 | 130 | if(this.EncodeType=="numerical" || !dbl){ 131 | // Now call function that will convert any HTML entities to numerical codes 132 | s = this.HTML2Numerical(s); 133 | } 134 | 135 | // Now encode all chars above 127 e.g unicode 136 | s = this.numEncode(s); 137 | 138 | // now we know anything that needs to be encoded has been converted to numerical entities we 139 | // can encode any ampersands & that are not part of encoded entities 140 | // to handle the fact that I need to do a negative check and handle multiple ampersands &&& 141 | // I am going to use a placeholder 142 | 143 | // if we don't want double encoded entities we ignore the & in existing entities 144 | if(!dbl){ 145 | s = s.replace(/&#/g,"##AMPHASH##"); 146 | 147 | if(this.EncodeType=="numerical"){ 148 | s = s.replace(/&/g, "&"); 149 | }else{ 150 | s = s.replace(/&/g, "&"); 151 | } 152 | 153 | s = s.replace(/##AMPHASH##/g,"&#"); 154 | } 155 | 156 | // replace any malformed entities 157 | s = s.replace(/&#\d*([^\d;]|$)/g, "$1"); 158 | 159 | if(!dbl){ 160 | // safety check to correct any double encoded & 161 | s = this.correctEncoding(s); 162 | } 163 | 164 | // now do we need to convert our numerical encoded string into entities 165 | if(this.EncodeType=="entity"){ 166 | s = this.NumericalToHTML(s); 167 | } 168 | 169 | return s; 170 | }, 171 | 172 | // Encodes the basic 4 characters used to malform HTML in XSS hacks 173 | XSSEncode : function(s,en){ 174 | if(!this.isEmpty(s)){ 175 | en = en || true; 176 | // do we convert to numerical or html entity? 177 | if(en){ 178 | s = s.replace(/\'/g,"'"); //no HTML equivalent as &apos is not cross browser supported 179 | s = s.replace(/\"/g,"""); 180 | s = s.replace(//g,">"); 182 | }else{ 183 | s = s.replace(/\'/g,"'"); //no HTML equivalent as &apos is not cross browser supported 184 | s = s.replace(/\"/g,"""); 185 | s = s.replace(//g,">"); 187 | } 188 | return s; 189 | }else{ 190 | return ""; 191 | } 192 | }, 193 | 194 | // returns true if a string contains html or numerical encoded entities 195 | hasEncoded : function(s){ 196 | if(/&#[0-9]{1,5};/g.test(s)){ 197 | return true; 198 | }else if(/&[A-Z]{2,6};/gi.test(s)){ 199 | return true; 200 | }else{ 201 | return false; 202 | } 203 | }, 204 | 205 | // will remove any unicode characters 206 | stripUnicode : function(s){ 207 | return s.replace(/[^\x20-\x7E]/g,""); 208 | 209 | }, 210 | 211 | // corrects any double encoded & entities e.g &amp; 212 | correctEncoding : function(s){ 213 | return s.replace(/(&)(amp;)+/,"$1"); 214 | }, 215 | 216 | 217 | // Function to loop through an array swaping each item with the value from another array e.g swap HTML entities with Numericals 218 | swapArrayVals : function(s,arr1,arr2){ 219 | if(this.isEmpty(s)) return ""; 220 | var re; 221 | if(arr1 && arr2){ 222 | //ShowDebug("in swapArrayVals arr1.length = " + arr1.length + " arr2.length = " + arr2.length) 223 | // array lengths must match 224 | if(arr1.length == arr2.length){ 225 | for(var x=0,i=arr1.length;x