JWT Verifier
Check a JWT's signature against your own key — HS, RS, ES, PS or EdDSA — with claim and clock-skew checks, entirely on this device.
This page cannot read a JWKS URL — a static page cannot call another origin's endpoint, so paste the key set itself. Nothing you paste leaves this tab.
Tokens up to 65,536 characters are read here.
Masked so nobody reads it over your shoulder. Masking is a display choice — the value is still in this page, which is why it is never put in a link, a file or storage.
Keys up to 64 KB are read here.
A shared link carries the token and your settings. It never carries the key — there is nowhere in the link's format to put one.
Paste a token and a key, then press Verify. Nothing runs until you do.
A match proves this key signed this token. It does not prove the token was issued to you, or that it still grants anything.
Registered claims
The registered claims appear here once a token has been checked.
Findings
Findings appear here — the checks that ran, and what they say.
What this page refuses to pretend it can do
Four algorithm spellings are declared out of scope here, and one needs a recent browser. Each is stated rather than hidden, so a missing option is never mistaken for a bug.
- alg: none — an unsecured JWS carries no signature. Libraries that once honoured that header let anyone mint any token; nothing here treats it as a pass.
- ES256K — no browser implements secp256k1 in Web Crypto.
- Ed448 — no browser implements it in Web Crypto.
- X448 — a key-agreement curve; it does not sign at all.
- EdDSA (Ed25519) needs Chrome/Edge 137+, Firefox 130+, Safari 17+.
Reading a token without a key is the JWT Decoder's job — open it there.
Ctrl ↵ Verify now · Ctrl ⇧ C Copy the verdict digest
Do it offline:$
How to use it
- Paste the token into the left pane. It is read structurally first, so a five-segment JWE or a two-segment paste is named, not answered with a cross.
- Choose the algorithm you expect on the right. That selection — not the token's header — decides how the check runs; a header that disagrees becomes a finding.
- Supply the key in the shape you have: a JWK, a whole JWKS, a PEM block, or a shared secret with its encoding named. Key text is masked until you reveal it, and the detected mode is an overridable badge.
- Set the clock-skew allowance and any aud, iss or sub you require, then verify. The banner answers the signature; the claims table and findings answer the rest.
Paste a token, paste the key that should have signed it, and read one answer: VALID or INVALID. Keys arrive as a JWK, JWKS, PEM block or raw secret, across thirteen algorithms — HS, RS, PS, ES and EdDSA. Expiry, not-before, audience, issuer and subject are checked beside the signature, with a clock-skew allowance you set. Nothing leaves the tab.
How it works
Verification recomputes, it never compares text. RFC 7515 §5.2 fixes the order: split the compact form on its dots, take the first two segments exactly as they arrived, join them with a dot, and hand that signing input plus the decoded signature to the algorithm. The segments are used as received, never re-serialised — reformatting a payload changes the bytes the issuer signed.
Thirteen algorithms map onto what the browser's own WebCrypto implementation offers. HS256/384/512 are HMAC over SHA-2; RS* is RSASSA-PKCS1-v1_5; PS* is RSA-PSS with the salt length equal to the hash output — 32, 48 and 64 bytes (RFC 7518 §3.5); ES256, ES384 and ES512 are ECDSA over P-256, P-384 and P-521; EdDSA is Ed25519. A JWS ECDSA signature is the raw r‖s pair — 64, 96 or 132 bytes, not the ASN.1 wrapper.
The key is imported before anything is checked, which is where most answers come from. A JWK imports on its own; a JWKS is narrowed by the kid you give or the one in the header, and a set with no match names what it does carry (RFC 7517 §4.5). A PEM block is read by its label: a public key imports, a certificate yields the key inside, a private key is refused. A shared secret decodes as UTF-8, Base64 or base64url — three byte strings, same characters.
The algorithm comes from your selector because taking it from the token is the flaw RFC 8725 §3.1 exists to close. Rewrite a header from RS256 to HS256, sign with the RSA public key, and a verifier obeying that header accepts it — the public key was never secret. Here the header's alg is only compared, and a difference reported.
Examples
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImdvbGRlbi1IUzI1NiJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwic3ViIjoidXNlci0xMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZSIsImlhdCI6MTcwMDAwMDAwMCwibmJmIjoxNzAwMDAwMDAwLCJleHAiOjE3MDAwMDM2MDAsImp0aSI6ImI5ZjBhMWMyLTNkNGUtNGY1MC04YTZiLTdjOGQ5ZTBmMWEyYiJ9.bv6d0AqDRECOxJdk7jEDJJxtiKLMrVHFe5PZUky0TP8verdict: valid
header alg: HS256 · verified with: HS256 · timing: active
findings: noneThe reference case: a 32-byte shared secret in base64url, against a token an unrelated library signed. Nothing is flagged and the token is inside its window.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImdvbGRlbi1IUzI1NiJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwic3ViIjoidXNlci0xMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZSIsImlhdCI6MTcwMDAwMDAwMCwibmJmIjoxNzAwMDAwMDAwLCJleHAiOjE3MDAwMDM2MDAsImp0aSI6ImI5ZjBhMWMyLTNkNGUtNGY1MC04YTZiLTdjOGQ5ZTBmMWEyYiJ9.bv6d0AqDRECOxJdk7jEDJJxtiKLMrVHFe5PZUky0TP8verdict: valid
header alg: HS256 · verified with: HS256 · timing: expired
findings:
- `exp` has passed: the token expired 1 hour ago. RFC 7519 §4.1.4 requires the current time to be BEFORE `exp`, so a token expires the instant its `exp` is reached.The same token, checked an hour after it expired. The signature is still genuine and still says so: verdict and claims are two readings, and collapsing them is how people debug the wrong half.
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImdvbGRlbi1FUzI1NiJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwic3ViIjoidXNlci0xMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZSIsImlhdCI6MTcwMDAwMDAwMCwibmJmIjoxNzAwMDAwMDAwLCJleHAiOjE3MDAwMDM2MDAsImp0aSI6ImI5ZjBhMWMyLTNkNGUtNGY1MC04YTZiLTdjOGQ5ZTBmMWEyYiJ9.-NJ7QPBOPUFve32Ac65vlQCF6UnkDhQ4HmM-xanGANWREOSiu4HCDmAlgVgjlWY0XXw1bOiJMhQ2l_mt2Xhm5Averdict: valid
header alg: ES256 · verified with: ES256 · timing: active
findings: noneA public JWK, four members, checking an ECDSA token. The third segment is 64 bytes: two 32-byte integers end to end, unwrapped by nothing — which is why the last example fails.
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImdvbGRlbi1SUzI1NiJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwic3ViIjoidXNlci0xMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZSIsImlhdCI6MTcwMDAwMDAwMCwibmJmIjoxNzAwMDAwMDAwLCJleHAiOjE3MDAwMDM2MDAsImp0aSI6ImI5ZjBhMWMyLTNkNGUtNGY1MC04YTZiLTdjOGQ5ZTBmMWEyYiJ9.j9YviU05z41MMN3bkl-Xo2ToS8rrczglPnXdwjEuvUG6hzx4puu6KzrEW1cpQ_ro3TO09Ou1NKAryd_AEwF5aZ14i_F-XWlVzWVdvwkwrLlSUPQf7xgkmMcUns9VOEoksEYFOSL_RjV_Hjd6ANDL1dCpFrfJ2ioo_cP3Bduyr_hyMV6Z4YEE3M3wezD3cPk57hPFLF-3dlHFZTR-ShBFshtsHge2k2Gk8YZk26W77tcLI88muBSQql0YMfpUxkTyVL9IV7R67zQpI2LcDYUI_yiJHUQYI0ZGoX12zpwf0jBNVZjIUx60qfgQVSxO_YIDsI6ekwTKN2Z-ms33OxSToQverdict: invalid
header alg: RS256 · verified with: HS256 · timing: active
findings:
- The token header says `alg` "RS256", but this check used HS256 — the algorithm comes from your selection, never from the token. That is deliberate: accepting the header's word is the RS256→HS256 confusion attack, where an attacker rewrites the header to HS256 and signs with the RSA PUBLIC key as if it were the shared secret (RFC 8725 §3.1).The confusion attempt, run for real: an RS256 token, HS256 selected, the public key pasted where the shared secret goes. The check runs as HMAC because that is what was asked for; the finding explains the disagreement.
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImdvbGRlbi1FUzI1NiJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwic3ViIjoidXNlci0xMjM0NTY3ODkwIiwiYXVkIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZSIsImlhdCI6MTcwMDAwMDAwMCwibmJmIjoxNzAwMDAwMDAwLCJleHAiOjE3MDAwMDM2MDAsImp0aSI6ImI5ZjBhMWMyLTNkNGUtNGY1MC04YTZiLTdjOGQ5ZTBmMWEyYiJ9.MEYCIQD40ntA8E49QW97fYBzrm-VAIXpSeQOFDgeYz7FqcYA1QIhAJEQ5KK7gcIOYCWBWCOVZjRdfDVs6IkyFDaX-a3ZeGbkverdict: invalid
header alg: ES256 · verified with: ES256 · timing: active
findings: noneThe edge case: example three's token with the very same signature re-encoded as ASN.1 DER, the form command-line tools emit. The mathematics is untouched and the answer is still INVALID, because compact JWS is defined over the raw pair. Accepting both shapes would misstate what a recipient does.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
A key set is narrowed before anything is checked
Paste a whole JWKS and one member has to be chosen: by the kid you type, or the kid in the header. When nothing matches, the refusal prints the identifiers present. Two members sharing one kid are refused as ambiguous; more than a hundred keys is declined.
The encoding beside a shared secret decides the bytes
The same characters are three different keys depending on whether they are read as text, Base64 or base64url, and only one matches the issuer. Text that would also decode cleanly as Base64 raises a finding — the usual explanation for an HS256 token that will not verify against a secret you know is right.
Skew is an allowance you set, and it announces itself
The window runs from zero to one hour, in the direction that grants time. When the allowance is what turned a failure into a pass, a finding says so — set it back to zero for the strict reading. Timestamps above 1e12 get a note of their own: those are milliseconds, and RFC 7519 §2 counts seconds.
Three algorithms and one keyword are declared, not faked
ES256K, Ed448 and X448 appear nowhere as working options because no browser implements them; a token declaring one is reported unchecked. Ed25519 arrived in Chrome and Edge 137, Firefox 130 and Safari 17, so EdDSA is feature-detected and, where missing, refused by name.
Common errors
Invalid base64url: character `+` at position 1 of the signature segment — that is the standard Base64 alphabet ('+' and '/'); decode again with standard Base64 accepted.- Compact tokens are base64url, so + and / are the error, not the input — usually a token re-encoded in transit. The position is named and a rerun offered, never applied silently.
`PRIVATE KEY` is a private key, and verifying a signature needs the PUBLIC half. Derive it with `openssl rsa -in key.pem -pubout -out pub.pem` and paste the `PUBLIC KEY` block instead — a private key never has to leave your machine to check a token.- Checking a signature needs only the public half, so a private block is refused with the command that derives it. Certificates differ: the key inside is extracted and used.
No key in this JWKS has `kid` "signing-key-2024" — the set carries `golden-RS256`, `golden-RS384`, `golden-RS512`, `golden-PS256`, `golden-PS384`, `golden-PS512`, `golden-ES256`, `golden-ES384`, `golden-ES512`, `golden-EdDSA`.- Falling back to the first member would produce an INVALID that means nothing. Listing what the set holds settles it: a rotated identifier, or the wrong environment's key set.
This is a five-segment JWE (an ENCRYPTED token), not a signed JWS. Decrypting a JWE needs the recipient's private key and is out of scope for this tool. Decode it without a key at /jwt-decoder to see what you have.- Five dot-separated parts mean the payload is encrypted rather than signed, and no key changes that. The shape is named, with a pointer to the page that still shows the header.
FAQ
Can this page fetch my JWKS URL?
No, and there is no field for one. A page served as static files cannot read another origin's endpoint unless that origin permits it, and a proxy would mean routing your tokens through someone else's server. Fetch it yourself and paste it.
What exactly does VALID mean here?
That the key you supplied produced this signature over these bytes, so nothing has been altered since signing. It says nothing about whether the token is authorised or meant for you — hence the claims and findings beside the verdict.
Why do I have to pick the algorithm myself?
Because a token's header is written by whoever sent it, and verifying with whatever it asks for is the confusion class RFC 8725 §3.1 documents. Your selection runs; the header's value is reported next to it.
Is it safe to paste an HMAC secret here?
The secret is used by this tab and dropped. It cannot reach a share link or the state file — that state has no field for key material at any version — and nothing is written to storage. No page can vouch for the machine it runs on, so prefer test credentials.
The signature verifies elsewhere but not here — why?
Usually, in this order: the secret was read in the wrong encoding; the kid selected a different key; an ECDSA signature arrived in DER form; or a segment changed in transit. The findings say which.
What happens with a token whose header says alg none?
It is read, its claims are shown, and the verdict stays unchecked: there is no signature to check. A finding explains that accepting one is the original JWT vulnerability, addressed by RFC 8725 §3.2. ES256K, Ed448 and X448 land there too.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Verify now, from anywhere in the tool.
- ⌘/Ctrl + Shift + C
- Copy the verdict digest regardless of what is selected.