This project is a purely client-side SD-JWT inspection tool built with Vite + React + Tailwind CSS v4.
It decodes Selective Disclosure JWTs (SD-JWT) and can optionally verify the issuer signature using a supplied public key (JWK), entirely in the browser.
No runtime Babel, no CDN dependencies, and compatible with a strict Content Security Policy (CSP).
- Paste an SD-JWT in the common
JWT~disclosure~disclosure~...format; newlines/whitespace are auto-cleaned. - Instant JWT header/payload decode and pretty-print panels with one-click Copy.
- Parses and lists disclosures, showing raw Base64URL and decoded tuples
[salt, claimName, claimValue]. - SD digest verification (sha-256 / sha-384 / sha-512 per
_sd_alg): recomputes each disclosure's digest and checks it against the payload's_sdanchors — including nested anchors and digests embedded in other disclosures (recursive disclosures). Duplicate digests are detected and flagged as a spec violation. - Builds Reconstructed Claims spec-compliantly: only digest-verified disclosures are re-inserted at their exact structural position (nested
_sdmembers, array{"...": digest}elements, recursive disclosures unfold);_sd/_sd_algare stripped and disclosures that fail the digest check are listed but never merged. - Optional signature verification using JOSE with a provided public key (JWK); the permitted algorithms are derived from the key (never from the attacker-controlled JWT header) to prevent algorithm-confusion attacks.
- Key Binding JWT (KB-JWT) verification when present: checks
typiskb+jwt, recomputes and comparessd_hashover the presented SD-JWT, and verifies the holder signature againstcnf.jwk. - Load Example SD-JWT and Load Example Key buttons included for quick testing (from the IETF draft example).
- Clean, responsive UI with Tailwind; inline SVG icons (Lucide-style) – no external icon package required.
The project is built as a small Vite application and outputs static assets for deployment.
.
├── index.html # Vite HTML entry
├── src/
│ ├── main.jsx # React bootstrap
│ ├── App.jsx # SD-JWT decoder UI (React component)
│ ├── sdjwt.js # SD-JWT parsing/verification logic (pure, unit-tested)
│ ├── sdjwt.test.js # Vitest unit tests for sdjwt.js
│ └── index.css # Tailwind CSS entry
├── tailwind.config.js
├── postcss.config.cjs
├── vite.config.js
└── dist/ # Production build output (generated)All dependencies are resolved at build time via npm.
The production output (dist/) contains only static HTML/CSS/JS.
- Node.js 18+ (recommended)
npm installnpm run devThen open:
http://localhost:5173/npm testUnit tests (Vitest) cover the SD-JWT logic in src/sdjwt.js: digest verification
(including forged/duplicate/recursive disclosures), the key-derived algorithm
allowlist, issuer signature verification, KB-JWT verification, parsing, and
prototype-pollution guards.
npm run buildThe static output is generated in dist/.
-
Paste SD-JWT into “SD-JWT Input”. Newlines and spaces are automatically stripped so you can paste directly from specs.
-
Click Decode SD-JWT to view:
- JWT Header
- JWT Payload (Original)
- Disclosures (raw + decoded)
- SD Digest Verification (matched / missing / extra / duplicate digests)
- Reconstructed Claims (payload with digest-verified disclosed values applied)
- KB-JWT (if detected, with
typ/sd_hash/ holder-signature checks)
-
(Optional) Verify Signature:
- Paste the issuer’s public key (JWK) into “Issuer Public Key (JWK format)”.
- Click Verify Signature to validate using JOSE; the UI reports success/failure and algorithm. All of the above flows and panels are implemented in the single HTML file’s React component.
- Parsing: Splits on
~to separate the signed JWT from disclosures and an optional KB-JWT (only the last segment can be a KB-JWT, per spec); handles whitespace/newlines; decoding is UTF-8 safe. - Decoding: Base64URL-decodes header/payload; disclosures are decoded and displayed; tuples are interpreted as
[salt, claimName, claimValue]. - Digest verification: Computes the
_sd_algdigest (sha-256/384/512) over each base64url disclosure string and matches it against all digest anchors (_sdarrays and"..."values) found in the payload and inside other disclosures; reports matched / missing / extra / duplicate digests. - Reconstruction: Walks the payload and re-inserts each digest-verified disclosure at its structural position — object
_sddigests become object members, array{"...": digest}elements are replaced by the disclosed value (undisclosed ones are dropped), disclosed values are processed recursively, and_sd/_sd_algare stripped (with prototype-pollution guards). - Signature verification: Parses the provided JWK, derives the set of permitted algorithms from its
kty/crv/alg(symmetricoctkeys are rejected), requires the JWT headeralgto be in that set, then verifies with JOSE restricted to that allowlist. - KB-JWT verification: Checks the
kb+jwtheader type, recomputessd_hashover the presentation (issuer JWT + disclosures + trailing~), and verifies the signature with the holder key from the issuer payload'scnf.jwkusing the same key-derived algorithm allowlist. - UI: Tailwind components and inline SVG icons; React state tracks decode/verify results and copy feedback.
-
Client-side only: No data leaves the browser; everything happens locally.
-
Signature verification verifies the JWT signature with your supplied public key (JWK).
- This tool does not fetch keys from JWKS endpoints and does not perform issuer/audience validations (JOSE's
exp/nbfchecks do apply when those claims are present).
- This tool does not fetch keys from JWKS endpoints and does not perform issuer/audience validations (JOSE's
-
Algorithm handling: The permitted algorithms are derived from the supplied JWK (
kty/crv/alg), never from the JWT header, preventing algorithm-confusion / key-type-confusion attacks. Symmetric (oct) keys are rejected. -
SD digest verification:
_sdhash binding is recomputed and validated (sha-256, sha-384, sha-512; other_sd_algvalues are reported as unsupported). Duplicate digests fail verification, as required by the spec. Only digest-verified disclosures are merged into "Reconstructed Claims" — forged disclosures appended to a signed SD-JWT are flagged and excluded. -
KB-JWT:
typ,sd_hashbinding, and the holder signature (viacnf.jwk) are verified. Note thatcnf.jwkis read from the issuer JWT payload, so the KB-JWT result is only meaningful if the issuer signature verification also succeeded.aud/noncefreshness is not checked (the tool has no verifier context). -
Intended as a developer/educational tool; please review and extend before using in security-critical workflows.
The application is intentionally designed to run under a strict CSP.
- No external scripts or styles
- No inline script execution
- No
unsafe-eval
This makes it suitable for deployment alongside security-sensitive sites.
- This project uses Vite + React with JSX compiled at build time.
- There is no runtime transpilation (no
@babel/standalone). - All JavaScript and CSS assets are self-hosted.
joseis imported as an npm dependency and bundled during build.- The application is compatible with CSP without
unsafe-evalor external script sources.
Use the built-in Load Example button to populate the editor with an example SD-JWT from the IETF draft and a matching example P-256 JWK for signature checks. The app also strips newlines so examples can be pasted directly from specs.
This project builds to a fully static site.
Deploy the contents of dist/ to any static hosting environment:
- Apache / nginx
- GitHub Pages
- Netlify / Vercel
- S3 + CloudFront
- Cloudflare Pages
The application is designed to work with a strict Content Security Policy such as:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
connect-src 'self';
font-src 'self';
object-src 'none';
base-uri 'none';
form-action 'none';
frame-ancestors 'none'The repository ships public/.htaccess, which sets the CSP above plus
X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and
Cross-Origin-Opener-Policy. Vite copies it into dist/ on every build, so simply
deploying the contents of dist/ to Apache enables the headers.
Why .htaccess instead of a <meta http-equiv> tag: a CSP meta tag would also apply to
the Vite dev server, whose HMR client injects inline scripts and would be blocked. Setting
the policy at the web-server layer keeps development unaffected while fully protecting the
deployed site (and allows directives like frame-ancestors that meta tags cannot express).
Steps / caveats:
- Build (
npm run build) and deploy the contents ofdist/— including the (hidden).htaccessfile; make sure your copy method doesn't skip dotfiles (rsync -ais fine, a barecp *is not). - Apache must have
mod_headersenabled. The directives are wrapped in<IfModule mod_headers.c>, so a server without it serves the site silently without the security headers — verify withcurl -sI https://your-host/sd-jwt-decoder/ | grep -i content-security-policy. - The directory (or a parent) needs
AllowOverridepermitting.htaccessheader directives (e.g.AllowOverride Allor at leastFileInfo). WithAllowOverride Nonethe file is ignored — in that case move theHeader always set ...lines verbatim into the vhost/directory config instead. - Serve over HTTPS (or localhost): the digest and signature checks use
crypto.subtle, which browsers only expose in secure contexts. On plain HTTP the decoder works but verification fails with a SubtleCrypto error. vite.config.jsusesbase: "./", so serving from a subdirectory (e.g./sd-jwt-decoder/) works out of the box; the.htaccessapplies to that directory tree only.
For non-Apache hosts (nginx, Netlify, Cloudflare Pages, S3/CloudFront), .htaccess is
ignored — set the same headers with that platform's mechanism (add_header, _headers
file, response-header policies, etc.).
- Support JWKS discovery (
kid) and issuer metadata resolution. - Optional claims validation (iss/aud) and KB-JWT
aud/noncechecks. - UI: file import/export, JSON download of decoded artifacts.
- Additional JOSE algs and error details. (These are straightforward to add within the current structure.)
- Example SD-JWT and example JWK flow reference the IETF OAuth SD-JWT draft specification; see the About section and example link referenced in the UI.
MIT License.
- Build system: Vite
- UI: React + Tailwind CSS v4
- Crypto: jose
- Topics:
sd-jwt,jwt,jose,selective-disclosure,verifiable-credentials,react,tailwind,security-tools