{"id":749,"date":"2026-04-12T16:26:47","date_gmt":"2026-04-12T16:26:47","guid":{"rendered":"https:\/\/primetoolhub.com\/?page_id=749"},"modified":"2026-08-06T13:48:20","modified_gmt":"2026-08-06T13:48:20","slug":"jwt-decoder-inspector","status":"publish","type":"page","link":"https:\/\/schoolict.net\/tools\/jwt-decoder-inspector\/","title":{"rendered":"JWT Decoder &amp; Inspector (JSON Web Token)"},"content":{"rendered":"\n<div class=\"pth-hero-section\">\n    <div class=\"pth-hero-content\">\n        <h2>All-in-One JWT Studio | Decode, Sign &#038; Verify Tokens Offline<\/h2>\n        <p> Meta Description: A free offline JWT studio to decode, sign, verify and security-audit JSON Web Tokens in your browser. HS256\/384\/512 support, no token ever uploaded.<\/p>\n\n        <div id=\"pth-toc-placeholder\"><\/div>\n    <\/div>\n\n <div class=\"pth-hero-image\">\n\t<img data-no-lazy=\"1\"\n         src=\"https:\/\/schoolict.net\/tools\/wp-content\/uploads\/2026\/05\/JWT-Decoder-Inspector-800x447.jpeg\"\n         width=\"800\"\n         height=\"447\"\n         alt=\"JWT Decoder &#038; Inspector (JSON Web Token)\"\n         fetchpriority=\"high\"\n         loading=\"eager\"\n         decoding=\"async\"\n         style=\"width:100%; height:auto; display:block;\">\n  <\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>Table of Contents<\/h2><nav><ul><li><a href=\"#\ud83d\udd34-reading-a-token-when-an-api-keeps-returning-401\">\ud83d\udd34\u00a0Reading a token when an API keeps returning 401<\/a><\/li><li><a href=\"#\ud83d\udfe1-signing-your-own-test-tokens-without-a-backend\">\ud83d\udfe1\u00a0Signing your own test tokens without a backend<\/a><\/li><li><a href=\"#\ud83d\udfe2-catching-an-expired-or-tampered-token-before-it-wastes-an-hour\">\ud83d\udfe2\u00a0Catching an expired or tampered token before it wastes an hour<\/a><ul><li><a href=\"#\ud83d\udfe2-a-short-checklist-for-a-token-that-should-work-but-doesnt\">\ud83d\udfe2\u00a0A short checklist for a token that &#8220;should work but doesn&#8217;t&#8221;<\/a><\/li><\/ul><\/li><li><a href=\"#\ud83d\udd34-what-this-studio-will-not-do-and-where-to-go-instead\">\ud83d\udd34\u00a0What this studio will not do, and where to go instead<\/a><\/li><\/ul><\/nav><\/div>\n\n\n<!-- LSCACHE_DISABLE -->\r\n<div id=\"pth-jwt-studio-wrapper\">\r\n    <style>\r\n        #pth-jwt-studio-wrapper { box-sizing: border-box; font-family: 'Inter', system-ui, -apple-system, sans-serif; color: #1e293b; background-color: #ffffff; border-radius: 12px; overflow: hidden; border: 1px solid #e2e8f0; margin: 20px 0; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05); }\r\n        #pth-jwt-studio-wrapper * { box-sizing: border-box; min-width: 0; }\r\n\r\n        \/* Header \/ meta bar *\/\r\n        #pth-jwt-studio-wrapper .pth-topbar { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; padding: 20px 24px 0; }\r\n        #pth-jwt-studio-wrapper .pth-title { margin: 0; font-size: 1.35rem; font-weight: 800; color: #0f172a; }\r\n        #pth-jwt-studio-wrapper .pth-meta-bar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; padding: 8px 24px 0; }\r\n        #pth-jwt-studio-wrapper .pth-version-badge { background: #eff6ff; color: #1d4ed8; font-size: 0.72rem; font-weight: 800; padding: 4px 10px; border-radius: 20px; letter-spacing: 0.4px; }\r\n        #pth-jwt-studio-wrapper .pth-status-offline { background: #ecfdf5; color: #047857; font-size: 0.72rem; font-weight: 800; padding: 4px 10px; border-radius: 20px; }\r\n\r\n        #pth-jwt-studio-wrapper .pth-toolbar { display: flex; gap: 8px; flex-wrap: wrap; }\r\n        #pth-jwt-studio-wrapper .pth-btn { border: none; padding: 8px 14px; font-size: 0.85rem; border-radius: 7px; cursor: pointer; font-weight: 700; font-family: inherit; display: inline-flex; align-items: center; gap: 6px; transition: background 0.2s; }\r\n        #pth-jwt-studio-wrapper .pth-sample-btn { background: #eff6ff; color: #1d4ed8; }\r\n        #pth-jwt-studio-wrapper .pth-sample-btn:hover { background: #dbeafe; }\r\n        #pth-jwt-studio-wrapper .pth-fw-btn { background: #e2e8f0; color: #0f172a; }\r\n        #pth-jwt-studio-wrapper .pth-fw-btn:hover { background: #cbd5e1; }\r\n        #pth-jwt-studio-wrapper .pth-clear-btn { background: #fef2f2; color: #991b1b; }\r\n        #pth-jwt-studio-wrapper .pth-clear-btn:hover { background: #fee2e2; }\r\n\r\n        \/* Tabs *\/\r\n        #pth-jwt-studio-wrapper .pth-tabbar { display: flex; gap: 4px; flex-wrap: wrap; padding: 16px 24px 0; border-bottom: 2px solid #e2e8f0; }\r\n        #pth-jwt-studio-wrapper .pth-tab-btn { background: transparent; border: none; border-bottom: 3px solid transparent; margin-bottom: -2px; padding: 10px 16px; font-size: 0.9rem; font-weight: 700; color: #334155; cursor: pointer; font-family: inherit; border-radius: 6px 6px 0 0; display: inline-flex; align-items: center; gap: 6px; }\r\n        #pth-jwt-studio-wrapper .pth-tab-btn:hover { background: #f1f5f9; color: #0f172a; }\r\n        #pth-jwt-studio-wrapper .pth-tab-btn.active { color: #1d4ed8; border-bottom-color: #2563eb; background: #eff6ff; }\r\n\r\n        \/* Layout *\/\r\n        #pth-jwt-studio-wrapper .pth-grid { display: grid; grid-template-columns: 1.8fr 1fr; gap: 0; }\r\n        #pth-jwt-studio-wrapper .pth-main { padding: 24px; border-right: 1px solid #e2e8f0; }\r\n        #pth-jwt-studio-wrapper .pth-side { padding: 24px; background: #f8fafc; }\r\n        #pth-jwt-studio-wrapper .pth-grid.full-width { grid-template-columns: 1fr; }\r\n        #pth-jwt-studio-wrapper .pth-grid.full-width .pth-side { display: none; }\r\n        #pth-jwt-studio-wrapper .pth-grid.full-width .pth-main { border-right: none; }\r\n\r\n        \/* Panels *\/\r\n        #pth-jwt-studio-wrapper .pth-panel { display: none; }\r\n        #pth-jwt-studio-wrapper .pth-panel.active { display: block; }\r\n        #pth-jwt-studio-wrapper .pth-panel-intro { font-size: 0.9rem; color: #334155; line-height: 1.6; margin: 0 0 18px; font-weight: 500; }\r\n\r\n        \/* Inputs *\/\r\n        #pth-jwt-studio-wrapper .pth-field { margin-bottom: 18px; }\r\n        #pth-jwt-studio-wrapper .pth-field label { display: block; font-size: 0.9rem; font-weight: 700; margin-bottom: 8px; color: #334155; }\r\n        #pth-jwt-studio-wrapper .pth-hint { font-size: 0.8rem; color: #334155; font-style: italic; margin: 0 0 6px; }\r\n        #pth-jwt-studio-wrapper textarea.pth-ta { width: 100%; min-height: 120px; padding: 14px; font-size: 0.9rem; font-family: 'Courier New', monospace; line-height: 1.6; color: #1e293b; background: #f8fafc; border: 2px solid #cbd5e1; border-radius: 8px; resize: vertical; word-break: break-all; }\r\n        #pth-jwt-studio-wrapper textarea.pth-ta.pth-json { min-height: 96px; word-break: normal; }\r\n        #pth-jwt-studio-wrapper textarea.pth-ta:focus, #pth-jwt-studio-wrapper .pth-inp:focus { outline: none; border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37,99,235,0.12); }\r\n        #pth-jwt-studio-wrapper .pth-inp { width: 100%; padding: 10px 12px; border: 2px solid #cbd5e1; border-radius: 8px; font-family: 'Courier New', monospace; font-size: 0.9rem; color: #1e293b; background: #f8fafc; }\r\n        #pth-jwt-studio-wrapper select.pth-inp { font-family: inherit; font-weight: 600; }\r\n        #pth-jwt-studio-wrapper .pth-row { display: flex; gap: 12px; flex-wrap: wrap; align-items: flex-end; margin-bottom: 18px; }\r\n        #pth-jwt-studio-wrapper .pth-row .pth-field { margin-bottom: 0; }\r\n        #pth-jwt-studio-wrapper .pth-col-grow { flex: 1 1 220px; }\r\n        #pth-jwt-studio-wrapper .pth-col-alg { flex: 0 0 150px; }\r\n\r\n        #pth-jwt-studio-wrapper .pth-action-btn { background: #2563eb; color: #ffffff; border: none; padding: 11px 20px; font-size: 0.9rem; font-weight: 700; border-radius: 8px; cursor: pointer; font-family: inherit; }\r\n        #pth-jwt-studio-wrapper .pth-action-btn:hover { background: #1d4ed8; }\r\n\r\n        \/* Output boxes *\/\r\n        #pth-jwt-studio-wrapper .pth-out-box { border-radius: 8px; overflow: hidden; border: 1px solid #e2e8f0; margin-bottom: 14px; }\r\n        #pth-jwt-studio-wrapper .pth-out-head { padding: 9px 14px; font-size: 0.78rem; font-weight: 800; letter-spacing: 0.4px; text-transform: uppercase; display: flex; justify-content: space-between; align-items: center; gap: 8px; }\r\n        #pth-jwt-studio-wrapper .pth-copy-btn { background: rgba(255,255,255,0.7); border: 1px solid rgba(0,0,0,0.08); color: #0f172a; font-size: 0.72rem; font-weight: 700; padding: 3px 9px; border-radius: 5px; cursor: pointer; font-family: inherit; text-transform: none; letter-spacing: 0; }\r\n        #pth-jwt-studio-wrapper .pth-copy-btn:hover { background: #ffffff; }\r\n        #pth-jwt-studio-wrapper .pth-out-body { margin: 0; padding: 14px; font-family: 'Courier New', monospace; font-size: 0.88rem; white-space: pre-wrap; word-wrap: break-word; overflow-x: auto; background: #ffffff; min-height: 44px; color: #1e293b; }\r\n        #pth-jwt-studio-wrapper .pth-box-red .pth-out-head { background: #fef2f2; color: #991b1b; border-bottom: 1px solid #fecaca; }\r\n        #pth-jwt-studio-wrapper .pth-box-purple .pth-out-head { background: #f5f3ff; color: #5b21b6; border-bottom: 1px solid #ddd6fe; }\r\n        #pth-jwt-studio-wrapper .pth-box-blue .pth-out-head { background: #eff6ff; color: #1d4ed8; border-bottom: 1px solid #bfdbfe; }\r\n        #pth-jwt-studio-wrapper .pth-box-green .pth-out-head { background: #ecfdf5; color: #047857; border-bottom: 1px solid #a7f3d0; }\r\n\r\n        \/* Verify badge *\/\r\n        #pth-jwt-studio-wrapper .pth-badge { padding: 9px 16px; border-radius: 8px; font-weight: 800; font-size: 0.85rem; display: inline-flex; align-items: center; gap: 6px; }\r\n        #pth-jwt-studio-wrapper .pth-badge.default { background: #e2e8f0; color: #334155; }\r\n        #pth-jwt-studio-wrapper .pth-badge.ok { background: #059669; color: #ffffff; }\r\n        #pth-jwt-studio-wrapper .pth-badge.bad { background: #dc2626; color: #ffffff; }\r\n\r\n        \/* Claims \/ health rows *\/\r\n        #pth-jwt-studio-wrapper .pth-claim-row { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; font-size: 0.86rem; padding: 10px 0; border-bottom: 1px dashed #cbd5e1; }\r\n        #pth-jwt-studio-wrapper .pth-claim-row:last-child { border-bottom: none; }\r\n        #pth-jwt-studio-wrapper .pth-claim-label { font-weight: 700; color: #334155; }\r\n        #pth-jwt-studio-wrapper .pth-claim-val { font-family: monospace; color: #0f172a; font-weight: 600; }\r\n        #pth-jwt-studio-wrapper .pth-pill { padding: 2px 8px; border-radius: 5px; font-size: 0.72rem; font-weight: 800; margin-left: 6px; }\r\n        #pth-jwt-studio-wrapper .pth-pill-ok { background: #dcfce7; color: #166534; }\r\n        #pth-jwt-studio-wrapper .pth-pill-bad { background: #fee2e2; color: #991b1b; }\r\n        #pth-jwt-studio-wrapper .pth-pill-warn { background: #fef9c3; color: #92400e; }\r\n\r\n        \/* Alert cards for security findings *\/\r\n        #pth-jwt-studio-wrapper .pth-finding { padding: 12px 14px; border-radius: 8px; font-size: 0.86rem; line-height: 1.55; margin-bottom: 10px; font-weight: 500; }\r\n        #pth-jwt-studio-wrapper .pth-finding strong { font-weight: 800; }\r\n        #pth-jwt-studio-wrapper .pth-f-crit { background: #fef2f2; color: #991b1b; border: 1px solid #fecaca; }\r\n        #pth-jwt-studio-wrapper .pth-f-warn { background: #fefce8; color: #92400e; border: 1px solid #fde68a; }\r\n        #pth-jwt-studio-wrapper .pth-f-ok { background: #ecfdf5; color: #047857; border: 1px solid #a7f3d0; }\r\n\r\n        #pth-jwt-studio-wrapper .pth-error { color: #991b1b; font-weight: 700; }\r\n\r\n        \/* Epoch helper *\/\r\n        #pth-jwt-studio-wrapper .pth-epoch { background: #ffffff; border: 1px solid #e2e8f0; padding: 14px; border-radius: 8px; margin-top: 8px; }\r\n        #pth-jwt-studio-wrapper .pth-epoch-head { font-size: 0.92rem; font-weight: 800; margin-bottom: 10px; color: #0f172a; }\r\n        #pth-jwt-studio-wrapper .pth-epoch-flex { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }\r\n        #pth-jwt-studio-wrapper .pth-epoch-out { font-size: 0.86rem; font-weight: 700; color: #1d4ed8; background: #eff6ff; padding: 8px 12px; border-radius: 6px; flex: 1 1 180px; }\r\n\r\n        \/* Side panel *\/\r\n        #pth-jwt-studio-wrapper .pth-side-title { font-size: 0.98rem; font-weight: 800; margin: 0 0 10px; color: #0f172a; }\r\n        #pth-jwt-studio-wrapper .pth-side-text { font-size: 0.85rem; color: #334155; line-height: 1.65; margin: 0 0 16px; font-weight: 500; }\r\n        #pth-jwt-studio-wrapper .pth-legend { list-style: none; padding: 0; margin: 0 0 20px; }\r\n        #pth-jwt-studio-wrapper .pth-legend li { display: flex; align-items: center; gap: 8px; font-size: 0.85rem; font-weight: 600; margin-bottom: 9px; color: #334155; }\r\n        #pth-jwt-studio-wrapper .pth-dot { width: 12px; height: 12px; border-radius: 50%; flex: 0 0 auto; }\r\n        #pth-jwt-studio-wrapper .pth-note { background: #fffbeb; border: 1px solid #fde68a; padding: 12px; border-radius: 8px; font-size: 0.84rem; color: #92400e; line-height: 1.55; margin-bottom: 16px; font-weight: 500; }\r\n        #pth-jwt-studio-wrapper .pth-note strong { font-weight: 800; }\r\n        #pth-jwt-studio-wrapper .pth-related a { color: #1d4ed8; text-decoration: none; font-weight: 700; }\r\n        #pth-jwt-studio-wrapper .pth-related a:hover { text-decoration: underline; }\r\n        #pth-jwt-studio-wrapper .pth-related li { margin-bottom: 7px; font-size: 0.85rem; }\r\n\r\n        \/* Toast *\/\r\n        #pth-jwt-studio-wrapper .pth-toast-wrap { position: fixed; bottom: 20px; right: 20px; z-index: 999999; display: flex; flex-direction: column; gap: 8px; }\r\n        #pth-jwt-studio-wrapper .pth-toast { padding: 12px 18px; border-radius: 8px; color: #ffffff; font-size: 0.88rem; font-weight: 700; box-shadow: 0 8px 24px rgba(0,0,0,0.18); font-family: 'Inter', sans-serif; }\r\n        #pth-jwt-studio-wrapper .pth-toast.success { background: #059669; }\r\n        #pth-jwt-studio-wrapper .pth-toast.error { background: #dc2626; }\r\n        #pth-jwt-studio-wrapper .pth-toast.info { background: #2563eb; }\r\n\r\n        @media (max-width: 900px) {\r\n            #pth-jwt-studio-wrapper .pth-grid { grid-template-columns: 1fr; }\r\n            #pth-jwt-studio-wrapper .pth-main { border-right: none; border-bottom: 1px solid #e2e8f0; }\r\n        }\r\n    <\/style>\r\n\r\n    <div class=\"pth-topbar\">\r\n        <h2 class=\"pth-title\">All-in-One JWT Studio Pro<\/h2>\r\n        <div class=\"pth-toolbar\">\r\n            <button type=\"button\" class=\"pth-btn pth-sample-btn\" id=\"pth-sample-btn\">\ud83d\udca1 Load Sample<\/button>\r\n            <button type=\"button\" class=\"pth-btn pth-fw-btn\" id=\"pth-fw-btn\" aria-label=\"Toggle full width view\">\u26f6 Full Width<\/button>\r\n            <button type=\"button\" class=\"pth-btn pth-clear-btn\" id=\"pth-clear-btn\">\ud83d\uddd1\ufe0f Clear<\/button>\r\n        <\/div>\r\n    <\/div>\r\n    <div class=\"pth-meta-bar\">\r\n        <span class=\"pth-version-badge\">JWT STUDIO v6.0<\/span>\r\n        <span class=\"pth-status-offline\">\ud83d\udd12 100% Offline<\/span>\r\n    <\/div>\r\n\r\n    <div class=\"pth-tabbar\" role=\"tablist\">\r\n        <button type=\"button\" class=\"pth-tab-btn active\" data-tab=\"decode\" role=\"tab\">\ud83d\udd13 Decode &amp; Inspect<\/button>\r\n        <button type=\"button\" class=\"pth-tab-btn\" data-tab=\"encode\" role=\"tab\">\ud83d\udd10 Encode &amp; Sign<\/button>\r\n        <button type=\"button\" class=\"pth-tab-btn\" data-tab=\"verify\" role=\"tab\">\ud83d\udee1\ufe0f Verify Signature<\/button>\r\n        <button type=\"button\" class=\"pth-tab-btn\" data-tab=\"health\" role=\"tab\">\ud83e\ude7a Token Health<\/button>\r\n    <\/div>\r\n\r\n    <div class=\"pth-grid\" id=\"pth-grid\">\r\n        <div class=\"pth-main\">\r\n\r\n            <!-- TAB 1: DECODE -->\r\n            <div class=\"pth-panel active\" id=\"pth-panel-decode\" role=\"tabpanel\">\r\n                <p class=\"pth-panel-intro\">Paste any JSON Web Token to instantly split and decode its three parts. Everything runs in your browser &mdash; nothing is uploaded.<\/p>\r\n                <div class=\"pth-field\">\r\n                    <label for=\"pth-decode-input\">Encoded JWT<\/label>\r\n                    <p class=\"pth-hint\">\u2139\ufe0f The text below is just an example placeholder (not real data) &mdash; paste your own token, or click \ud83d\udca1 Load Sample above to fill in a working example.<\/p>\r\n                    <textarea id=\"pth-decode-input\" class=\"pth-ta\" aria-label=\"Encoded JWT to decode\" placeholder=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\"><\/textarea>\r\n                <\/div>\r\n\r\n                <div class=\"pth-out-box pth-box-red\">\r\n                    <div class=\"pth-out-head\"><span>Header &mdash; algorithm &amp; type<\/span><button type=\"button\" class=\"pth-copy-btn\" data-copy=\"pth-dec-header\">Copy<\/button><\/div>\r\n                    <pre class=\"pth-out-body\" id=\"pth-dec-header\">Awaiting input...<\/pre>\r\n                <\/div>\r\n                <div class=\"pth-out-box pth-box-purple\">\r\n                    <div class=\"pth-out-head\"><span>Payload &mdash; data &amp; claims<\/span><button type=\"button\" class=\"pth-copy-btn\" data-copy=\"pth-dec-payload\">Copy<\/button><\/div>\r\n                    <pre class=\"pth-out-body\" id=\"pth-dec-payload\">Awaiting input...<\/pre>\r\n                <\/div>\r\n                <div class=\"pth-out-box pth-box-blue\">\r\n                    <div class=\"pth-out-head\"><span>Signature<\/span><button type=\"button\" class=\"pth-copy-btn\" data-copy=\"pth-dec-sig\">Copy<\/button><\/div>\r\n                    <pre class=\"pth-out-body\" id=\"pth-dec-sig\">Awaiting input...<\/pre>\r\n                <\/div>\r\n\r\n                <div class=\"pth-epoch\">\r\n                    <div class=\"pth-epoch-head\">\u23f1\ufe0f Quick Epoch Helper<\/div>\r\n                    <div class=\"pth-epoch-flex\">\r\n                        <input type=\"number\" id=\"pth-epoch-input\" class=\"pth-inp\" style=\"flex:0 0 160px;\" aria-label=\"Unix epoch timestamp in seconds\" placeholder=\"e.g. 1735689600\">\r\n                        <div class=\"pth-epoch-out\" id=\"pth-epoch-output\" aria-live=\"polite\">Local time: &mdash;<\/div>\r\n                    <\/div>\r\n                    <p class=\"pth-side-text\" style=\"margin:10px 0 0;\">Need full timezone tables and date&rarr;epoch conversion? Use the dedicated <a class=\"pth-related-inline\" href=\"https:\/\/schoolict.net\/tools\/unix-epoch-time-converter\/\" style=\"color:#1d4ed8;font-weight:700;\">Unix Epoch Time Converter<\/a>.<\/p>\r\n                <\/div>\r\n            <\/div>\r\n\r\n            <!-- TAB 2: ENCODE -->\r\n            <div class=\"pth-panel\" id=\"pth-panel-encode\" role=\"tabpanel\">\r\n                <p class=\"pth-panel-intro\">Build and sign a brand-new token. Edit the header and payload as JSON, choose an HMAC algorithm, add your secret, and generate a valid signed JWT locally.<\/p>\r\n                <div class=\"pth-field\">\r\n                    <label for=\"pth-enc-header\">Header (JSON)<\/label>\r\n                    <textarea id=\"pth-enc-header\" class=\"pth-ta pth-json\" aria-label=\"JWT header JSON\">{\r\n  \"alg\": \"HS256\",\r\n  \"typ\": \"JWT\"\r\n}<\/textarea>\r\n                <\/div>\r\n                <div class=\"pth-field\">\r\n                    <label for=\"pth-enc-payload\">Payload (JSON)<\/label>\r\n                    <textarea id=\"pth-enc-payload\" class=\"pth-ta pth-json\" aria-label=\"JWT payload JSON\">{\r\n  \"sub\": \"user_9921\",\r\n  \"name\": \"Ada Lovelace\",\r\n  \"role\": \"admin\",\r\n  \"iat\": 1735689600\r\n}<\/textarea>\r\n                <\/div>\r\n                <div class=\"pth-row\">\r\n                    <div class=\"pth-field pth-col-alg\">\r\n                        <label for=\"pth-enc-alg\">Algorithm<\/label>\r\n                        <select id=\"pth-enc-alg\" class=\"pth-inp\" aria-label=\"Signing algorithm\">\r\n                            <option value=\"HS256\">HS256<\/option>\r\n                            <option value=\"HS384\">HS384<\/option>\r\n                            <option value=\"HS512\">HS512<\/option>\r\n                        <\/select>\r\n                    <\/div>\r\n                    <div class=\"pth-field pth-col-grow\">\r\n                        <label for=\"pth-enc-secret\">Secret Key<\/label>\r\n                        <input type=\"text\" id=\"pth-enc-secret\" class=\"pth-inp\" aria-label=\"HMAC secret key for signing\" value=\"your-256-bit-secret\">\r\n                    <\/div>\r\n                <\/div>\r\n                <button type=\"button\" class=\"pth-action-btn\" id=\"pth-enc-btn\">\ud83d\udd0f Generate Signed JWT<\/button>\r\n\r\n                <div class=\"pth-out-box pth-box-green\" style=\"margin-top:18px;\">\r\n                    <div class=\"pth-out-head\"><span>Signed Token<\/span><button type=\"button\" class=\"pth-copy-btn\" data-copy=\"pth-enc-out\">Copy<\/button><\/div>\r\n                    <pre class=\"pth-out-body\" id=\"pth-enc-out\">Awaiting generation...<\/pre>\r\n                <\/div>\r\n            <\/div>\r\n\r\n            <!-- TAB 3: VERIFY -->\r\n            <div class=\"pth-panel\" id=\"pth-panel-verify\" role=\"tabpanel\">\r\n                <p class=\"pth-panel-intro\">Check whether a token's signature is authentic. The algorithm is read from the token header &mdash; HS256, HS384 and HS512 are supported, fully offline.<\/p>\r\n                <div class=\"pth-field\">\r\n                    <label for=\"pth-ver-input\">Encoded JWT<\/label>\r\n                    <textarea id=\"pth-ver-input\" class=\"pth-ta\" aria-label=\"Encoded JWT to verify\" placeholder=\"Paste the token to verify...\"><\/textarea>\r\n                <\/div>\r\n                <div class=\"pth-field\">\r\n                    <label for=\"pth-ver-secret\">Secret Key<\/label>\r\n                    <input type=\"text\" id=\"pth-ver-secret\" class=\"pth-inp\" aria-label=\"HMAC secret key for verification\" placeholder=\"Enter the shared secret...\">\r\n                <\/div>\r\n                <button type=\"button\" class=\"pth-action-btn\" id=\"pth-ver-btn\">\ud83d\udee1\ufe0f Verify Signature<\/button>\r\n\r\n                <div class=\"pth-out-box pth-box-blue\" style=\"margin-top:18px;\">\r\n                    <div class=\"pth-out-head\"><span>Result<\/span><\/div>\r\n                    <div class=\"pth-out-body\" id=\"pth-ver-body\" role=\"status\" aria-live=\"polite\">\r\n                        <span class=\"pth-badge default\" id=\"pth-ver-badge\">Awaiting verification<\/span>\r\n                        <p id=\"pth-ver-detail\" style=\"margin:10px 0 0; font-family:'Inter',sans-serif; font-size:0.85rem; color:#334155;\"><\/p>\r\n                    <\/div>\r\n                <\/div>\r\n            <\/div>\r\n\r\n            <!-- TAB 4: HEALTH -->\r\n            <div class=\"pth-panel\" id=\"pth-panel-health\" role=\"tabpanel\">\r\n                <p class=\"pth-panel-intro\">A full claim-by-claim and security audit of your token: expiry status, timing claims, and common JWT attack surfaces such as the <strong>alg:none<\/strong> bypass.<\/p>\r\n                <div class=\"pth-field\">\r\n                    <label for=\"pth-health-input\">Encoded JWT<\/label>\r\n                    <textarea id=\"pth-health-input\" class=\"pth-ta\" aria-label=\"Encoded JWT to audit\" placeholder=\"Paste the token to audit...\"><\/textarea>\r\n                <\/div>\r\n                <button type=\"button\" class=\"pth-action-btn\" id=\"pth-health-btn\">\ud83e\ude7a Run Health Check<\/button>\r\n\r\n                <div id=\"pth-health-results\" style=\"margin-top:18px;\">\r\n                    <div class=\"pth-out-body\" id=\"pth-health-empty\" style=\"border:1px solid #e2e8f0; border-radius:8px; color:#334155; font-family:'Inter',sans-serif;\">Run a check to see the claims breakdown and security findings.<\/div>\r\n                <\/div>\r\n            <\/div>\r\n\r\n        <\/div>\r\n\r\n        <div class=\"pth-side\">\r\n            <h3 class=\"pth-side-title\">Anatomy of a JWT<\/h3>\r\n            <ul class=\"pth-legend\">\r\n                <li><span class=\"pth-dot\" style=\"background:#991b1b;\"><\/span> Header (red) &mdash; algorithm<\/li>\r\n                <li><span class=\"pth-dot\" style=\"background:#5b21b6;\"><\/span> Payload (purple) &mdash; claims<\/li>\r\n                <li><span class=\"pth-dot\" style=\"background:#1d4ed8;\"><\/span> Signature (blue) &mdash; trust<\/li>\r\n            <\/ul>\r\n\r\n            <div class=\"pth-note\">\r\n                <strong>\ud83d\udee1\ufe0f Local &amp; private.<\/strong> Decoding, signing and verification all run in your browser through the native Web Crypto API. Your tokens and secret keys never leave this device.\r\n            <\/div>\r\n\r\n            <h3 class=\"pth-side-title\">Registered Claims<\/h3>\r\n            <p class=\"pth-side-text\">\r\n                <strong>exp<\/strong> &mdash; expiration time<br>\r\n                <strong>nbf<\/strong> &mdash; not valid before<br>\r\n                <strong>iat<\/strong> &mdash; issued at<br>\r\n                <strong>iss<\/strong> &mdash; issuer<br>\r\n                <strong>sub<\/strong> &mdash; subject<br>\r\n                <strong>aud<\/strong> &mdash; audience<br>\r\n                <strong>jti<\/strong> &mdash; token ID\r\n            <\/p>\r\n\r\n            <h3 class=\"pth-side-title\">Related Tools<\/h3>\r\n            <ul class=\"pth-related\" style=\"list-style:none; padding:0; margin:0;\">\r\n                <li><a href=\"https:\/\/schoolict.net\/tools\/free-secure-hash-generator\/\">SHA Hash Generator<\/a><\/li>\r\n                <li><a href=\"https:\/\/schoolict.net\/tools\/free-base64-encoder-decoder-utf-8-supported\/\">Base64 Encoder \/ Decoder<\/a><\/li>\r\n                <li><a href=\"https:\/\/schoolict.net\/tools\/unix-epoch-time-converter\/\">Unix Epoch Converter<\/a><\/li>\r\n                <li><a href=\"https:\/\/schoolict.net\/tools\/free-strong-password-generator\/\">Strong Secret Generator<\/a><\/li>\r\n            <\/ul>\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <div class=\"pth-toast-wrap\" id=\"pth-toast-wrap\" aria-live=\"assertive\"><\/div>\r\n\r\n    <script data-no-optimize=\"1\" data-no-minify=\"1\" data-cfasync=\"false\">\r\n    (function () {\r\n        'use strict';\r\n\r\n        if (window.pthJwtStudioApp) { return; }\r\n\r\n        window.pthJwtStudioApp = {\r\n            initialized: false,\r\n\r\n            init: function () {\r\n                var self = this;\r\n                self.wrapper = document.getElementById('pth-jwt-studio-wrapper');\r\n                if (!self.wrapper) { return; }\r\n\r\n                self.grid = document.getElementById('pth-grid');\r\n                self.activeTab = 'decode';\r\n                self.attachEvents();\r\n            },\r\n\r\n            \/* ---------- Events ---------- *\/\r\n            attachEvents: function () {\r\n                var self = this;\r\n                var w = self.wrapper;\r\n\r\n                \/\/ Tab switching\r\n                var tabs = w.querySelectorAll('.pth-tab-btn');\r\n                for (var i = 0; i < tabs.length; i++) {\r\n                    tabs[i].addEventListener('click', function () {\r\n                        self.switchTab(this.getAttribute('data-tab'));\r\n                    });\r\n                }\r\n\r\n                \/\/ Global toolbar\r\n                document.getElementById('pth-sample-btn').addEventListener('click', function () { self.loadSample(); });\r\n                document.getElementById('pth-clear-btn').addEventListener('click', function () { self.clearActive(); });\r\n                document.getElementById('pth-fw-btn').addEventListener('click', function () { self.toggleWidth(); });\r\n\r\n                \/\/ Copy buttons (event delegation)\r\n                w.addEventListener('click', function (e) {\r\n                    var t = e.target;\r\n                    if (t && t.classList && t.classList.contains('pth-copy-btn')) {\r\n                        self.copyFrom(t.getAttribute('data-copy'));\r\n                    }\r\n                });\r\n\r\n                \/\/ Decode tab\r\n                document.getElementById('pth-decode-input').addEventListener('input', function () { self.runDecode(); });\r\n\r\n                \/\/ Epoch helper\r\n                document.getElementById('pth-epoch-input').addEventListener('input', function () {\r\n                    var v = parseInt(this.value, 10);\r\n                    var out = document.getElementById('pth-epoch-output');\r\n                    if (!isNaN(v) && v > 0) {\r\n                        out.textContent = 'Local time: ' + new Date(v * 1000).toLocaleString();\r\n                    } else {\r\n                        out.textContent = 'Local time: \\u2014';\r\n                    }\r\n                });\r\n\r\n                \/\/ Encode tab\r\n                document.getElementById('pth-enc-btn').addEventListener('click', function () { self.runEncode(); });\r\n\r\n                \/\/ Verify tab\r\n                document.getElementById('pth-ver-btn').addEventListener('click', function () { self.runVerify(); });\r\n\r\n                \/\/ Health tab\r\n                document.getElementById('pth-health-btn').addEventListener('click', function () { self.runHealth(); });\r\n            },\r\n\r\n            switchTab: function (name) {\r\n                var self = this;\r\n                var w = self.wrapper;\r\n                self.activeTab = name;\r\n\r\n                var tabs = w.querySelectorAll('.pth-tab-btn');\r\n                for (var i = 0; i < tabs.length; i++) {\r\n                    if (tabs[i].getAttribute('data-tab') === name) {\r\n                        tabs[i].classList.add('active');\r\n                    } else {\r\n                        tabs[i].classList.remove('active');\r\n                    }\r\n                }\r\n                var panels = w.querySelectorAll('.pth-panel');\r\n                for (var j = 0; j < panels.length; j++) {\r\n                    if (panels[j].id === 'pth-panel-' + name) {\r\n                        panels[j].classList.add('active');\r\n                    } else {\r\n                        panels[j].classList.remove('active');\r\n                    }\r\n                }\r\n            },\r\n\r\n            toggleWidth: function () {\r\n                var self = this;\r\n                self.grid.classList.toggle('full-width');\r\n                var btn = document.getElementById('pth-fw-btn');\r\n                btn.textContent = self.grid.classList.contains('full-width') ? '\\u25E7 Split View' : '\\u26F6 Full Width';\r\n            },\r\n\r\n            clearActive: function () {\r\n                var self = this;\r\n                var t = self.activeTab;\r\n                if (t === 'decode') {\r\n                    document.getElementById('pth-decode-input').value = '';\r\n                    self.runDecode();\r\n                    document.getElementById('pth-epoch-input').value = '';\r\n                    document.getElementById('pth-epoch-output').textContent = 'Local time: \\u2014';\r\n                } else if (t === 'encode') {\r\n                    document.getElementById('pth-enc-out').textContent = 'Awaiting generation...';\r\n                } else if (t === 'verify') {\r\n                    document.getElementById('pth-ver-input').value = '';\r\n                    document.getElementById('pth-ver-secret').value = '';\r\n                    self.setVerifyBadge('default', 'Awaiting verification', '');\r\n                } else if (t === 'health') {\r\n                    document.getElementById('pth-health-input').value = '';\r\n                    self.resetHealth();\r\n                }\r\n                self.showToast('Cleared', 'info');\r\n            },\r\n\r\n            loadSample: function () {\r\n                var self = this;\r\n                var sampleToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzk5MjEiLCJuYW1lIjoiQWRhIExvdmVsYWNlIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjE4OTM0NTYwMDB9.QF2iZ7bkQ8pFq3o1r7l5w0Zt3l1s3sQ6xQ8m2p9k4A';\r\n                var sampleSecret = 'your-256-bit-secret';\r\n                var t = self.activeTab;\r\n\r\n                if (t === 'decode') {\r\n                    document.getElementById('pth-decode-input').value = sampleToken;\r\n                    self.runDecode();\r\n                } else if (t === 'encode') {\r\n                    document.getElementById('pth-enc-header').value = '{\\n  \"alg\": \"HS256\",\\n  \"typ\": \"JWT\"\\n}';\r\n                    document.getElementById('pth-enc-payload').value = '{\\n  \"sub\": \"user_9921\",\\n  \"name\": \"Ada Lovelace\",\\n  \"role\": \"admin\",\\n  \"iat\": 1735689600\\n}';\r\n                    document.getElementById('pth-enc-secret').value = sampleSecret;\r\n                    self.runEncode();\r\n                } else if (t === 'verify') {\r\n                    \/\/ Build a genuinely valid token so the sample actually verifies.\r\n                    self.buildSampleAndVerify(sampleSecret);\r\n                } else if (t === 'health') {\r\n                    document.getElementById('pth-health-input').value = sampleToken;\r\n                    self.runHealth();\r\n                }\r\n                self.showToast('Sample loaded', 'success');\r\n            },\r\n\r\n            \/* ---------- Base64Url helpers ---------- *\/\r\n            b64UrlDecode: function (str) {\r\n                var base64 = str.replace(\/-\/g, '+').replace(\/_\/g, '\/');\r\n                while (base64.length % 4) { base64 += '='; }\r\n                var decoded = window.atob(base64);\r\n                try {\r\n                    var pct = decoded.split('').map(function (c) {\r\n                        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);\r\n                    }).join('');\r\n                    return decodeURIComponent(pct);\r\n                } catch (e) {\r\n                    return decoded;\r\n                }\r\n            },\r\n\r\n            strToB64Url: function (str) {\r\n                var bytes = new TextEncoder().encode(str);\r\n                var bin = '';\r\n                for (var i = 0; i < bytes.length; i++) { bin += String.fromCharCode(bytes[i]); }\r\n                return window.btoa(bin).replace(\/\\+\/g, '-').replace(\/\\\/\/g, '_').replace(\/=+$\/, '');\r\n            },\r\n\r\n            bufToB64Url: function (buffer) {\r\n                var bytes = new Uint8Array(buffer);\r\n                var bin = '';\r\n                for (var i = 0; i < bytes.byteLength; i++) { bin += String.fromCharCode(bytes[i]); }\r\n                return window.btoa(bin).replace(\/\\+\/g, '-').replace(\/\\\/\/g, '_').replace(\/=+$\/, '');\r\n            },\r\n\r\n            hashForAlg: function (alg) {\r\n                if (alg === 'HS384') { return 'SHA-384'; }\r\n                if (alg === 'HS512') { return 'SHA-512'; }\r\n                return 'SHA-256';\r\n            },\r\n\r\n            hmacSign: function (secret, dataStr, hashName) {\r\n                var enc = new TextEncoder();\r\n                return crypto.subtle.importKey(\r\n                    'raw', enc.encode(secret), { name: 'HMAC', hash: hashName }, false, ['sign']\r\n                ).then(function (key) {\r\n                    return crypto.subtle.sign('HMAC', key, enc.encode(dataStr));\r\n                });\r\n            },\r\n\r\n            fmtDate: function (ts) {\r\n                if (!ts || isNaN(ts)) { return 'Invalid date'; }\r\n                return new Date(ts * 1000).toLocaleString();\r\n            },\r\n\r\n            escapeHtml: function (s) {\r\n                return String(s)\r\n                    .replace(\/&\/g, '&amp;').replace(\/<\/g, '&lt;')\r\n                    .replace(\/>\/g, '&gt;').replace(\/\"\/g, '&quot;');\r\n            },\r\n\r\n            \/* ---------- TAB 1: Decode ---------- *\/\r\n            runDecode: function () {\r\n                var self = this;\r\n                var token = document.getElementById('pth-decode-input').value.trim();\r\n                var hEl = document.getElementById('pth-dec-header');\r\n                var pEl = document.getElementById('pth-dec-payload');\r\n                var sEl = document.getElementById('pth-dec-sig');\r\n\r\n                if (!token) {\r\n                    hEl.textContent = 'Awaiting input...';\r\n                    pEl.textContent = 'Awaiting input...';\r\n                    sEl.textContent = 'Awaiting input...';\r\n                    return;\r\n                }\r\n                var parts = token.split('.');\r\n                if (parts.length !== 3) {\r\n                    hEl.innerHTML = '<span class=\"pth-error\">\\u26A0\\uFE0F Invalid format \\u2014 a JWT must have 3 parts separated by dots.<\/span>';\r\n                    pEl.textContent = '';\r\n                    sEl.textContent = '';\r\n                    return;\r\n                }\r\n                try {\r\n                    var jsonHeader = JSON.parse(self.b64UrlDecode(parts[0]));\r\n                    var jsonPayload = JSON.parse(self.b64UrlDecode(parts[1]));\r\n                    hEl.textContent = JSON.stringify(jsonHeader, null, 2);\r\n                    pEl.textContent = JSON.stringify(jsonPayload, null, 2);\r\n                    sEl.textContent = parts[2] || '(empty)';\r\n                } catch (e) {\r\n                    hEl.innerHTML = '<span class=\"pth-error\">\\u26A0\\uFE0F Could not decode \\u2014 the token may be corrupted.<\/span>';\r\n                    pEl.textContent = e.message;\r\n                    sEl.textContent = '';\r\n                }\r\n            },\r\n\r\n            \/* ---------- TAB 2: Encode ---------- *\/\r\n            runEncode: function () {\r\n                var self = this;\r\n                var out = document.getElementById('pth-enc-out');\r\n                var headerRaw = document.getElementById('pth-enc-header').value;\r\n                var payloadRaw = document.getElementById('pth-enc-payload').value;\r\n                var alg = document.getElementById('pth-enc-alg').value;\r\n                var secret = document.getElementById('pth-enc-secret').value;\r\n\r\n                var headerObj, payloadObj;\r\n                try {\r\n                    headerObj = JSON.parse(headerRaw);\r\n                } catch (e) {\r\n                    out.innerHTML = '<span class=\"pth-error\">\\u26A0\\uFE0F Header is not valid JSON.<\/span>';\r\n                    self.showToast('Header JSON error', 'error');\r\n                    return;\r\n                }\r\n                try {\r\n                    payloadObj = JSON.parse(payloadRaw);\r\n                } catch (e2) {\r\n                    out.innerHTML = '<span class=\"pth-error\">\\u26A0\\uFE0F Payload is not valid JSON.<\/span>';\r\n                    self.showToast('Payload JSON error', 'error');\r\n                    return;\r\n                }\r\n                if (!secret) {\r\n                    out.innerHTML = '<span class=\"pth-error\">\\u26A0\\uFE0F Enter a secret key to sign the token.<\/span>';\r\n                    self.showToast('Secret key required', 'error');\r\n                    return;\r\n                }\r\n\r\n                \/\/ Keep header alg in sync with the chosen algorithm.\r\n                headerObj.alg = alg;\r\n                if (!headerObj.typ) { headerObj.typ = 'JWT'; }\r\n\r\n                var encHeader = self.strToB64Url(JSON.stringify(headerObj));\r\n                var encPayload = self.strToB64Url(JSON.stringify(payloadObj));\r\n                var signingInput = encHeader + '.' + encPayload;\r\n\r\n                out.textContent = 'Signing...';\r\n                self.hmacSign(secret, signingInput, self.hashForAlg(alg)).then(function (sigBuf) {\r\n                    var sig = self.bufToB64Url(sigBuf);\r\n                    out.textContent = signingInput + '.' + sig;\r\n                    self.showToast('Token generated', 'success');\r\n                }).catch(function () {\r\n                    out.innerHTML = '<span class=\"pth-error\">\\u26A0\\uFE0F Signing failed in this browser.<\/span>';\r\n                    self.showToast('Signing failed', 'error');\r\n                });\r\n            },\r\n\r\n            \/* ---------- TAB 3: Verify ---------- *\/\r\n            runVerify: function () {\r\n                var self = this;\r\n                var token = document.getElementById('pth-ver-input').value.trim();\r\n                var secret = document.getElementById('pth-ver-secret').value;\r\n\r\n                if (!token) {\r\n                    self.setVerifyBadge('default', 'Awaiting verification', 'Paste a token above.');\r\n                    return;\r\n                }\r\n                var parts = token.split('.');\r\n                if (parts.length !== 3) {\r\n                    self.setVerifyBadge('bad', '\\u274C Invalid', 'This is not a well-formed JWT (needs 3 parts).');\r\n                    return;\r\n                }\r\n                var header;\r\n                try {\r\n                    header = JSON.parse(self.b64UrlDecode(parts[0]));\r\n                } catch (e) {\r\n                    self.setVerifyBadge('bad', '\\u274C Invalid', 'The header could not be decoded.');\r\n                    return;\r\n                }\r\n                var alg = header.alg;\r\n                if (alg === 'none') {\r\n                    self.setVerifyBadge('bad', '\\u26A0\\uFE0F alg:none', 'This token declares no signature (alg:none). It cannot be trusted.');\r\n                    return;\r\n                }\r\n                if (alg !== 'HS256' && alg !== 'HS384' && alg !== 'HS512') {\r\n                    self.setVerifyBadge('default', 'Unsupported: ' + self.escapeHtml(String(alg)), 'This tab verifies HMAC tokens (HS256 \/ HS384 \/ HS512).');\r\n                    return;\r\n                }\r\n                if (!secret) {\r\n                    self.setVerifyBadge('default', 'Awaiting key', 'Enter the shared secret to verify.');\r\n                    return;\r\n                }\r\n\r\n                var signingInput = parts[0] + '.' + parts[1];\r\n                self.hmacSign(secret, signingInput, self.hashForAlg(alg)).then(function (sigBuf) {\r\n                    var expected = self.bufToB64Url(sigBuf);\r\n                    if (expected === parts[2]) {\r\n                        self.setVerifyBadge('ok', '\\u2705 Verified', 'Signature is valid for ' + alg + '. The token is authentic and untampered.');\r\n                    } else {\r\n                        self.setVerifyBadge('bad', '\\u274C Invalid', 'Signature does not match. Wrong secret, or the token was altered.');\r\n                    }\r\n                }).catch(function () {\r\n                    self.setVerifyBadge('bad', 'Error', 'Verification could not run in this browser.');\r\n                });\r\n            },\r\n\r\n            setVerifyBadge: function (cls, text, detail) {\r\n                var badge = document.getElementById('pth-ver-badge');\r\n                var det = document.getElementById('pth-ver-detail');\r\n                badge.className = 'pth-badge ' + cls;\r\n                badge.textContent = text;\r\n                det.textContent = detail || '';\r\n            },\r\n\r\n            buildSampleAndVerify: function (secret) {\r\n                var self = this;\r\n                var headerObj = { alg: 'HS256', typ: 'JWT' };\r\n                var payloadObj = { sub: 'user_9921', name: 'Ada Lovelace', role: 'admin', iat: 1735689600 };\r\n                var encHeader = self.strToB64Url(JSON.stringify(headerObj));\r\n                var encPayload = self.strToB64Url(JSON.stringify(payloadObj));\r\n                var signingInput = encHeader + '.' + encPayload;\r\n                self.hmacSign(secret, signingInput, 'SHA-256').then(function (sigBuf) {\r\n                    var token = signingInput + '.' + self.bufToB64Url(sigBuf);\r\n                    document.getElementById('pth-ver-input').value = token;\r\n                    document.getElementById('pth-ver-secret').value = secret;\r\n                    self.runVerify();\r\n                });\r\n            },\r\n\r\n            \/* ---------- TAB 4: Health ---------- *\/\r\n            resetHealth: function () {\r\n                var box = document.getElementById('pth-health-results');\r\n                box.innerHTML = '<div class=\"pth-out-body\" id=\"pth-health-empty\" style=\"border:1px solid #e2e8f0; border-radius:8px; color:#334155; font-family:\\'Inter\\',sans-serif;\">Run a check to see the claims breakdown and security findings.<\/div>';\r\n            },\r\n\r\n            runHealth: function () {\r\n                var self = this;\r\n                var box = document.getElementById('pth-health-results');\r\n                var token = document.getElementById('pth-health-input').value.trim();\r\n\r\n                if (!token) {\r\n                    self.resetHealth();\r\n                    self.showToast('Paste a token first', 'info');\r\n                    return;\r\n                }\r\n                var parts = token.split('.');\r\n                if (parts.length !== 3) {\r\n                    box.innerHTML = '<div class=\"pth-finding pth-f-crit\"><strong>\\u26A0\\uFE0F Malformed token.<\/strong> A JWT needs exactly 3 dot-separated parts.<\/div>';\r\n                    return;\r\n                }\r\n                var header, payload;\r\n                try {\r\n                    header = JSON.parse(self.b64UrlDecode(parts[0]));\r\n                    payload = JSON.parse(self.b64UrlDecode(parts[1]));\r\n                } catch (e) {\r\n                    box.innerHTML = '<div class=\"pth-finding pth-f-crit\"><strong>\\u26A0\\uFE0F Decode error.<\/strong> The header or payload is not valid Base64Url JSON.<\/div>';\r\n                    return;\r\n                }\r\n\r\n                var now = Math.floor(Date.now() \/ 1000);\r\n                var html = '';\r\n\r\n                \/\/ ----- Claims breakdown -----\r\n                html += '<div class=\"pth-out-box pth-box-purple\"><div class=\"pth-out-head\"><span>Claims Breakdown<\/span><\/div><div class=\"pth-out-body\" style=\"font-family:\\'Inter\\',sans-serif;\">';\r\n\r\n                if (typeof payload.exp !== 'undefined') {\r\n                    var expired = payload.exp < now;\r\n                    html += self.claimRow('Expiration (exp)', payload.exp, self.fmtDate(payload.exp),\r\n                        expired ? 'pth-pill-bad' : 'pth-pill-ok', expired ? 'EXPIRED' : 'ACTIVE');\r\n                }\r\n                if (typeof payload.nbf !== 'undefined') {\r\n                    var notYet = payload.nbf > now;\r\n                    html += self.claimRow('Not Before (nbf)', payload.nbf, self.fmtDate(payload.nbf),\r\n                        notYet ? 'pth-pill-warn' : 'pth-pill-ok', notYet ? 'NOT YET VALID' : 'IN EFFECT');\r\n                }\r\n                if (typeof payload.iat !== 'undefined') {\r\n                    html += self.claimRow('Issued At (iat)', payload.iat, self.fmtDate(payload.iat), '', '');\r\n                }\r\n                if (typeof payload.iss !== 'undefined') { html += self.claimRow('Issuer (iss)', payload.iss, '', '', ''); }\r\n                if (typeof payload.sub !== 'undefined') { html += self.claimRow('Subject (sub)', payload.sub, '', '', ''); }\r\n                if (typeof payload.aud !== 'undefined') { html += self.claimRow('Audience (aud)', (typeof payload.aud === 'object' ? JSON.stringify(payload.aud) : payload.aud), '', '', ''); }\r\n                if (typeof payload.jti !== 'undefined') { html += self.claimRow('Token ID (jti)', payload.jti, '', '', ''); }\r\n\r\n                html += '<\/div><\/div>';\r\n\r\n                \/\/ ----- Security findings -----\r\n                html += '<h3 class=\"pth-side-title\" style=\"margin-top:18px;\">Security Findings<\/h3>';\r\n                var findings = self.securityFindings(header, payload, now);\r\n                for (var i = 0; i < findings.length; i++) {\r\n                    html += '<div class=\"pth-finding ' + findings[i].cls + '\">' + findings[i].text + '<\/div>';\r\n                }\r\n\r\n                box.innerHTML = html;\r\n                self.showToast('Health check complete', 'success');\r\n            },\r\n\r\n            claimRow: function (label, value, human, pillCls, pillText) {\r\n                var right = human ? this.escapeHtml(String(human)) : '';\r\n                if (pillText) {\r\n                    right += ' <span class=\"pth-pill ' + pillCls + '\">' + this.escapeHtml(pillText) + '<\/span>';\r\n                }\r\n                return '<div class=\"pth-claim-row\">' +\r\n                    '<div><span class=\"pth-claim-label\">' + this.escapeHtml(label) + ':<\/span> ' +\r\n                    '<span class=\"pth-claim-val\">' + this.escapeHtml(String(value)) + '<\/span><\/div>' +\r\n                    '<div>' + right + '<\/div><\/div>';\r\n            },\r\n\r\n            securityFindings: function (header, payload, now) {\r\n                var out = [];\r\n                var alg = header.alg;\r\n\r\n                if (alg === 'none' || alg === 'None' || alg === 'NONE') {\r\n                    out.push({ cls: 'pth-f-crit', text: '<strong>Critical &mdash; alg:none.<\/strong> This token is unsigned. An attacker can forge any payload. Servers must reject \"none\".' });\r\n                }\r\n                if (typeof header.kid !== 'undefined') {\r\n                    out.push({ cls: 'pth-f-warn', text: '<strong>Watch &mdash; \"kid\" header present.<\/strong> Key-ID values are a known injection surface (path traversal \/ SQL). Ensure your backend sanitises it.' });\r\n                }\r\n                if (typeof payload.exp === 'undefined') {\r\n                    out.push({ cls: 'pth-f-warn', text: '<strong>Warning &mdash; no expiry (exp).<\/strong> Tokens without an expiration stay valid forever if leaked.' });\r\n                } else if (payload.exp < now) {\r\n                    out.push({ cls: 'pth-f-crit', text: '<strong>Expired.<\/strong> This token is past its \"exp\" time and should be rejected by any compliant server.' });\r\n                } else {\r\n                    var mins = Math.round((payload.exp - now) \/ 60);\r\n                    out.push({ cls: 'pth-f-ok', text: '<strong>Active.<\/strong> Expires in about ' + mins + ' minute(s).' });\r\n                }\r\n                if (typeof payload.iat !== 'undefined' && payload.iat > now + 60) {\r\n                    out.push({ cls: 'pth-f-warn', text: '<strong>Odd timing.<\/strong> \"iat\" is in the future \\u2014 check for clock skew or tampering.' });\r\n                }\r\n                if (alg && alg.indexOf('HS') === 0) {\r\n                    out.push({ cls: 'pth-f-ok', text: '<strong>Symmetric (' + this.escapeHtml(alg) + ').<\/strong> The same secret signs and verifies \\u2014 keep it server-side only, never ship it to a browser or mobile app.' });\r\n                }\r\n                if (alg && alg.indexOf('RS') === 0) {\r\n                    out.push({ cls: 'pth-f-ok', text: '<strong>Asymmetric (' + this.escapeHtml(alg) + ').<\/strong> A private key signs, a public key verifies \\u2014 confirm your verifier pins the expected algorithm to avoid RS\/HS confusion attacks.' });\r\n                }\r\n                return out;\r\n            },\r\n\r\n            \/* ---------- Copy + Toast ---------- *\/\r\n            copyFrom: function (id) {\r\n                var self = this;\r\n                var el = document.getElementById(id);\r\n                if (!el) { return; }\r\n                var text = el.textContent || '';\r\n                if (!text || text.indexOf('Awaiting') === 0) {\r\n                    self.showToast('Nothing to copy yet', 'info');\r\n                    return;\r\n                }\r\n                if (navigator.clipboard && navigator.clipboard.writeText) {\r\n                    navigator.clipboard.writeText(text).then(function () {\r\n                        self.showToast('Copied to clipboard', 'success');\r\n                    }).catch(function () {\r\n                        self.showToast('Copy blocked by browser', 'error');\r\n                    });\r\n                } else {\r\n                    self.showToast('Clipboard not supported', 'error');\r\n                }\r\n            },\r\n\r\n            showToast: function (msg, type) {\r\n                var wrap = document.getElementById('pth-toast-wrap');\r\n                if (!wrap) { return; }\r\n                var t = document.createElement('div');\r\n                t.className = 'pth-toast ' + (type || 'info');\r\n                t.setAttribute('role', 'status');\r\n                t.textContent = msg;\r\n                wrap.appendChild(t);\r\n                setTimeout(function () {\r\n                    if (t.parentNode) { t.parentNode.removeChild(t); }\r\n                }, 3000);\r\n            }\r\n        };\r\n\r\n        \/* ---------- 4-Layer Bootstrap ---------- *\/\r\n        function pthRunInit() {\r\n            if (!window.pthJwtStudioApp.initialized && document.getElementById('pth-jwt-studio-wrapper')) {\r\n                window.pthJwtStudioApp.initialized = true;\r\n                window.pthJwtStudioApp.init();\r\n            }\r\n        }\r\n        \/* Layer 1: immediate *\/\r\n        pthRunInit();\r\n        \/* Layer 2: DOMContentLoaded *\/\r\n        if (document.readyState === 'loading') {\r\n            document.addEventListener('DOMContentLoaded', pthRunInit);\r\n        }\r\n        \/* Layer 3: window.load + 50ms *\/\r\n        window.addEventListener('load', function () { setTimeout(pthRunInit, 50); });\r\n        \/* Layer 4: MutationObserver (auto-disconnect 10s) *\/\r\n        if (!window.pthJwtStudioApp.initialized && typeof MutationObserver !== 'undefined') {\r\n            var pthObs = new MutationObserver(function () {\r\n                if (document.getElementById('pth-jwt-studio-wrapper')) { pthObs.disconnect(); pthRunInit(); }\r\n            });\r\n            pthObs.observe(document.body || document.documentElement, { childList: true, subtree: true });\r\n            setTimeout(function () { pthObs.disconnect(); }, 10000);\r\n        }\r\n    })();\r\n    <\/script>\r\n<\/div>\n\n\n\n<style>\n.pth-container{max-width:1480px;margin:0 auto;padding:0 20px;box-sizing:border-box}\n.pth-feature-wrap{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px;margin-bottom:40px}\n.pth-feat-box{border:1px solid #e2e8f0;background:#fff;padding:24px;border-radius:12px;box-shadow:0 4px 6px -1px rgba(0,0,0,0.02);transition:all .3s ease}\n.pth-feat-box:hover{border-color:#bfdbfe;transform:translateY(-3px);box-shadow:0 10px 15px -3px rgba(0,0,0,0.05)}\n.pth-feat-title{font-size:1.05rem;font-weight:800;color:#0f172a;margin:0 0 10px;display:flex;align-items:center;gap:10px}\n.pth-feat-text{font-size:.9rem;color:#1e293b;line-height:1.6;margin:0;font-weight:500}\n.pth-steps-wrap{border:1px solid #e2e8f0;background:#fff;padding:40px 30px;border-radius:16px;text-align:center;box-shadow:0 4px 6px -1px rgba(0,0,0,0.02);margin-bottom:40px;width:100%;box-sizing:border-box}\n.pth-steps-title{font-size:1.2rem;font-weight:800;color:#0f172a;text-transform:uppercase;margin:0 0 35px;letter-spacing:1px}\n.pth-steps-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:30px}\n.pth-step-item{display:flex;flex-direction:column;align-items:center}\n.pth-step-num{width:45px;height:45px;background:#2563eb;color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:1.2rem;margin-bottom:15px;box-shadow:0 4px 10px rgba(37,99,235,.3)}\n.pth-step-name{font-weight:800;color:#0f172a;margin:0 0 8px;font-size:1rem}\n.pth-step-desc{font-size:.9rem;color:#1e293b;line-height:1.5;margin:0;font-weight:500}\n<\/style>\n<div class=\"pth-container\">\n  <div class=\"pth-feature-wrap\">\n    <div class=\"pth-feat-box\">\n      <p class=\"pth-feat-title\"><span aria-hidden=\"true\">\ud83d\udfe2<\/span> Decode and read any token<\/p>\n      <p class=\"pth-feat-text\">Paste a token and the studio splits it into header, payload and signature, colour-coded and pretty-printed. Each box has its own Copy button, so you can lift just the payload JSON into a bug report without hand-editing.<\/p>\n    <\/div>\n    <div class=\"pth-feat-box\">\n      <p class=\"pth-feat-title\"><span aria-hidden=\"true\">\ud83d\udd35<\/span> Sign your own test tokens<\/p>\n      <p class=\"pth-feat-text\">The Encode &amp; Sign tab builds a real, signed token from header and payload JSON plus a secret. HS256, HS384 and HS512 are all supported, so you can mint a valid token for a local test without standing up a backend first.<\/p>\n    <\/div>\n    <div class=\"pth-feat-box\">\n      <p class=\"pth-feat-title\"><span aria-hidden=\"true\">\ud83d\udfe3<\/span> Verify and audit for safety<\/p>\n      <p class=\"pth-feat-text\">Verify Signature checks whether a token is authentic for a given secret. Token Health then reads every registered claim and flags risks like an expired token or an alg:none header that should never be trusted.<\/p>\n    <\/div>\n  <\/div>\n  <div class=\"pth-steps-wrap\">\n    <p class=\"pth-steps-title\">How to Use the JWT Studio<\/p>\n    <div class=\"pth-steps-grid\">\n      <div class=\"pth-step-item\">\n        <div class=\"pth-step-num\" aria-hidden=\"true\">1<\/div>\n        <p class=\"pth-step-name\">Decode &amp; Inspect<\/p>\n        <p class=\"pth-step-desc\">Paste a token in the first tab, or press \ud83d\udca1 Load Sample. The three parts appear instantly with copy buttons.<\/p>\n      <\/div>\n      <div class=\"pth-step-item\">\n        <div class=\"pth-step-num\" aria-hidden=\"true\">2<\/div>\n        <p class=\"pth-step-name\">Encode &amp; Sign<\/p>\n        <p class=\"pth-step-desc\">Edit the header and payload JSON, pick HS256\/384\/512, add a secret, then press Generate Signed JWT.<\/p>\n      <\/div>\n      <div class=\"pth-step-item\">\n        <div class=\"pth-step-num\" aria-hidden=\"true\">3<\/div>\n        <p class=\"pth-step-name\">Verify Signature<\/p>\n        <p class=\"pth-step-desc\">Drop a token and its secret into the Verify tab. A green Verified or red Invalid badge tells you the truth.<\/p>\n      <\/div>\n      <div class=\"pth-step-item\">\n        <div class=\"pth-step-num\" aria-hidden=\"true\">4<\/div>\n        <p class=\"pth-step-name\">Token Health<\/p>\n        <p class=\"pth-step-desc\">Run the audit for a claim-by-claim breakdown and security findings before you rely on a token in production.<\/p>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Last updated: July 2026<\/p>\n\n\n\n<h2 id=\"\ud83d\udd34-reading-a-token-when-an-api-keeps-returning-401\" class=\"wp-block-heading\">\ud83d\udd34&nbsp;<strong>Reading a token when an API keeps returning 401<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You are wiring up an API call, the request looks right, and the server keeps answering 401 Unauthorized. Nine times out of ten the answer is sitting inside the token you are sending, and you just cannot see it because it looks like one long smear of letters. That is the moment this JWT studio earns its place on your bookmarks bar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Paste the token into the Decode &amp; Inspect tab. Say you drop in a token whose payload decodes to&nbsp;{&#8220;sub&#8221;:&#8221;user_9921&#8243;,&#8221;role&#8221;:&#8221;viewer&#8221;,&#8221;exp&#8221;:1735689600}. Two things jump out at once. First, the role is &#8220;viewer&#8221;, not &#8220;admin&#8221;, so the endpoint you are hitting may simply be refusing a user who lacks permission. Second, that&nbsp;exp&nbsp;value is an epoch timestamp, and the Quick Epoch Helper right below the output turns it into a readable local date. If that date is in the past, your token expired and no amount of retrying will fix it. You needed a fresh one all along.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Everything here runs inside your browser. The token you paste is decoded with native JavaScript, and nothing is sent to a server. That matters, because a JWT often carries a user id, an email, or session detail you would never want to paste into a random online box that quietly logs its inputs.<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe1-signing-your-own-test-tokens-without-a-backend\" class=\"wp-block-heading\">\ud83d\udfe1&nbsp;<strong>Signing your own test tokens without a backend<\/strong><\/h2>\n\n\n<figure class=\"pth-article-figure pth-img-left\" style=\"float:left; width:700px; max-width:100%; margin:4px 28px 16px 0; clear:left;\"><img decoding=\"async\" src=\"https:\/\/schoolict.net\/tools\/wp-content\/uploads\/2026\/07\/jwt-studio-three-parts-800x447.jpeg\" alt=\"JWT split into colour-coded header, payload and signature parts\" width=\"700\" height=\"394\" loading=\"lazy\" data-no-lazy=\"1\" class=\"pth-article-img\" style=\"width:100%;height:auto;display:block;border-radius:10px;border:1px solid #e2e8f0;\"><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Older decoders stop at reading. This one also writes. Open the Encode &amp; Sign tab when you need a valid token for a local test and do not want to spin up an auth server just to get one.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You edit two JSON boxes. The header usually stays as&nbsp;{&#8220;alg&#8221;:&#8221;HS256&#8243;,&#8221;typ&#8221;:&#8221;JWT&#8221;}. The payload is yours to shape: add a&nbsp;sub, a&nbsp;role, maybe an&nbsp;exp&nbsp;a few hours ahead. Pick the algorithm, type a secret such as&nbsp;your-256-bit-secret, and press Generate Signed JWT. Out comes a complete three-part token, signed with HMAC through the browser&#8217;s Web Crypto API. Paste that same token straight into the Verify tab with the same secret and you will watch it come back Verified, which is a satisfying way to confirm the round trip works.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A quick, real use-case: front-end developers building against a mocked API often need a token that &#8220;looks real&#8221; so their auth guard code runs. Minting one here takes about ten seconds. Another: QA testers can craft an already-expired token on purpose to check that the app logs the user out gracefully instead of showing a broken screen.<\/p>\n\n\n\n<h2 id=\"\ud83d\udfe2-catching-an-expired-or-tampered-token-before-it-wastes-an-hour\" class=\"wp-block-heading\">\ud83d\udfe2&nbsp;<strong>Catching an expired or tampered token before it wastes an hour<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Decoding shows you what a token says. Verifying proves whether you can believe it. These are different jobs, and mixing them up is the single most common JWT mistake. A token can decode perfectly and still be a forgery, because anyone can base64-decode the payload. Only the signature, checked against the right secret, tells you the token was issued by who it claims and has not been edited in transit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The Verify Signature tab handles that check for HS256, HS384 and HS512, reading the algorithm from the token&#8217;s own header. The Token Health tab goes one step further. It lays out every registered claim, marks an&nbsp;exp&nbsp;as ACTIVE or EXPIRED, warns when there is no expiry at all, and raises a loud flag if the header says&nbsp;alg:none, which is an unsigned token that a careful server must reject. If you want to understand the theory behind those attacks, our companion piece on&nbsp;<a href=\"https:\/\/schoolict.net\/tools\/securing-api-keys-client-side-data-processing\/\" rel=\"noreferrer noopener\" target=\"_blank\">securing keys during client-side data processing<\/a>&nbsp;walks through why signatures matter.<\/p>\n\n\n\n<h3 id=\"\ud83d\udfe2-a-short-checklist-for-a-token-that-should-work-but-doesnt\" class=\"wp-block-heading\">\ud83d\udfe2&nbsp;<strong>A short checklist for a token that &#8220;should work but doesn&#8217;t&#8221;<\/strong><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\ud83d\udd35 Decode it first and read the payload with your own eyes. Wrong role or wrong&nbsp;sub&nbsp;explains most 403s.<br>\ud83d\udfe0 Convert the&nbsp;exp&nbsp;and&nbsp;nbf&nbsp;claims to local time. Expiry and clock skew are quiet culprits.<br>\ud83d\udfe3 Verify the signature with the secret your server actually uses. A mismatch means a wrong key or an altered token.<\/p>\n\n\n\n<h2 id=\"\ud83d\udd34-what-this-studio-will-not-do-and-where-to-go-instead\" class=\"wp-block-heading\">\ud83d\udd34&nbsp;<strong>What this studio will not do, and where to go instead<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Honesty saves you time, so here are the limits. Verification and signing cover the HMAC family (HS256\/384\/512). RS256 and ES256, which use a public and private key pair, are read and audited but not signed here, because signing those safely belongs on a server that holds the private key. The studio also does not decrypt anything, for the simple reason that a standard JWT is signed, not encrypted; its payload is readable by design. If you assumed the payload was secret, that assumption is the bug.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When your work drifts next door, the site has focused tools ready. For raw hashing and HMAC digests outside the JWT format, use the&nbsp;<a href=\"https:\/\/schoolict.net\/tools\/free-secure-hash-generator\/\" rel=\"noreferrer noopener\" target=\"_blank\">Secure Hash Generator<\/a>. For plain base64 work, the&nbsp;<a href=\"https:\/\/schoolict.net\/tools\/free-base64-encoder-decoder-utf-8-supported\/\" rel=\"noreferrer noopener\" target=\"_blank\">Base64 Encoder \/ Decoder<\/a>&nbsp;handles files and UTF-8 text, and for turning any timestamp into a date the&nbsp;<a href=\"https:\/\/schoolict.net\/tools\/unix-epoch-time-converter\/\" rel=\"noreferrer noopener\" target=\"_blank\">Unix Epoch Time Converter<\/a>&nbsp;gives you full timezone tables. The token format itself is defined in the official&nbsp;<a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc7519\" rel=\"noreferrer noopener\" target=\"_blank\">IETF RFC 7519 specification<\/a>, and the browser signing used here is the standard&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/SubtleCrypto\" rel=\"noreferrer noopener\" target=\"_blank\">Web Crypto SubtleCrypto API<\/a>&nbsp;documented by MDN.<\/p>\n\n\n\n<style>\n.pth-faq-section{margin:50px auto 40px;font-family:inherit;max-width:1480px;padding:0 20px;box-sizing:border-box}\n.pth-faq-header{font-size:1.8rem;font-weight:800;color:#0f172a;margin-bottom:25px;border-bottom:2px solid #e2e8f0;padding-bottom:10px;display:flex;align-items:center;gap:10px}\n.pth-faq-grid{display:grid;grid-template-columns:1fr;gap:20px}\n@media(min-width:768px){.pth-faq-grid{grid-template-columns:repeat(2,1fr)}}\n@media(min-width:1024px){.pth-faq-grid{grid-template-columns:repeat(3,1fr)}}\n.pth-faq-card{background:#f8fafc;padding:24px;border-radius:12px;border:1px solid #e2e8f0;transition:transform .2s ease;break-inside:avoid}\n.pth-faq-card:hover{transform:translateY(-3px);box-shadow:0 4px 12px rgba(0,0,0,.05)}\n.pth-faq-q{color:#0f172a;font-size:1rem;font-weight:700;margin:0 0 12px;line-height:1.4}\n.pth-faq-a{margin:0;font-size:.95rem;color:#1e293b;line-height:1.6;font-weight:500}\n<\/style>\n<div class=\"pth-faq-section\">\n  <div class=\"pth-faq-header\">\u2753 Frequently Asked Questions<\/div>\n  <div class=\"pth-faq-grid\">\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Is my token or secret sent anywhere?<\/p>\n      <p class=\"pth-faq-a\">No. Decoding, signing and verification all run in your browser through the Web Crypto API. Nothing you paste leaves your device, which is why it is safe to inspect production tokens here.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">What is the difference between decoding and verifying?<\/p>\n      <p class=\"pth-faq-a\">Decoding just reads the base64 content, which anyone can do. Verifying checks the signature against a secret to prove the token is authentic and unchanged. A token can decode fine and still fail verification.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Which algorithms can it sign and verify?<\/p>\n      <p class=\"pth-faq-a\">The HMAC family: HS256, HS384 and HS512. Tokens using RS256 or ES256 can be decoded and audited, but signing those requires a private key and belongs on a server.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Why does my valid-looking token still fail?<\/p>\n      <p class=\"pth-faq-a\">Usually the secret does not match, the token expired, or a single character changed during copy-paste. Run Token Health to see the expiry status and check the secret you entered against the one your server uses.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Is the payload encrypted?<\/p>\n      <p class=\"pth-faq-a\">No. A standard JWT is signed, not encrypted, so the payload is readable by anyone. Never place passwords or secrets in a payload. The signature protects against tampering, not against reading.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">What does the alg:none warning mean?<\/p>\n      <p class=\"pth-faq-a\">It means the token declares no signature. A server that accepts alg:none can be tricked into trusting a forged token, so the health check flags it as critical. Real tokens should always be signed.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Can I create an expired token on purpose?<\/p>\n      <p class=\"pth-faq-a\">Yes. In Encode &amp; Sign, set the exp claim to a past epoch value and generate the token. It is a handy way to test that your app logs users out cleanly when a token lapses.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">Does it work offline?<\/p>\n      <p class=\"pth-faq-a\">Once the page has loaded, the studio keeps working without a connection because all the logic lives in your browser. There are no server round-trips for any tab.<\/p>\n    <\/div>\n    <div class=\"pth-faq-card\">\n      <p class=\"pth-faq-q\">What should I do with the copied JSON?<\/p>\n      <p class=\"pth-faq-a\">The per-box Copy buttons give you clean, formatted header or payload JSON. It pastes neatly into bug reports, tickets, or a code comment so teammates can see exactly what a token contained.<\/p>\n    <\/div>\n  <\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>All-in-One JWT Studio | Decode, Sign &#038; Verify Tokens Offline Meta Description: A free offline JWT studio to decode, sign, verify and security-audit JSON Web Tokens in your browser. HS256\/384\/512 support, no token ever uploaded. \ud83d\udfe2 Decode and read any token Paste a token and the studio splits it into header, payload and signature, colour-coded &#8230; <a title=\"JWT Decoder &amp; Inspector (JSON Web Token)\" class=\"read-more\" href=\"https:\/\/schoolict.net\/tools\/jwt-decoder-inspector\/\" aria-label=\"Read more about JWT Decoder &amp; Inspector (JSON Web Token)\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":3483,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-749","page","type-page","status-publish","has-post-thumbnail"],"_links":{"self":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/749","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/comments?post=749"}],"version-history":[{"count":2,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/749\/revisions"}],"predecessor-version":[{"id":6842,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/pages\/749\/revisions\/6842"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/media\/3483"}],"wp:attachment":[{"href":"https:\/\/schoolict.net\/tools\/wp-json\/wp\/v2\/media?parent=749"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}