JWT Decoder — Free Online Tool
Decode a JWT's header and payload instantly, read iat/nbf/exp as ISO timestamps with an expiry flag, and optionally verify HS256/384/512 signatures locally.
Use this free online JWT Decoder directly in your browser. No signup required, no data leaves your device. Part of Utilier — a collection of 133+ developer utilities.
What is JWT Decoder (Decode Header, Payload & Verify HMAC)?
A JWT decoder splits a JSON Web Token on its two dots and Base64url-decodes the first two segments back into readable JSON, so you can see the header and the claims a server will act on. Decoding is not verification: the three parts of a JWT are encoded, not encrypted, and anyone holding the token can read them. This tool decodes as you type and, when you supply a shared secret, additionally checks the HMAC signature.
- Header and payload, side by side: Both segments are pretty-printed as JSON in separate panes, so you can compare the alg/typ/kid metadata against the claims it protects without scrolling through one blob.
- Time claims resolved to ISO timestamps: iat, nbf and exp are NumericDate values — seconds since the Unix epoch — which are unreadable at a glance. The tool renders each one as an ISO 8601 string and appends (EXPIRED) to exp when it is already in the past.
- Optional HMAC verification: Paste the shared secret into the verify field and the tool recomputes the signature with Web Crypto for HS256, HS384 or HS512, reporting "Signature valid" or "Signature invalid". Leave it blank and the status line says plainly that the signature was not verified.
- Honest about what it cannot check: Only HMAC algorithms are supported. Hand it an RS256, ES256 or PS256 token and it decodes the segments fine but tells you it cannot verify that alg here, because doing so needs the issuer's public key rather than a secret.
- Entirely local: Parsing runs in the page and the HMAC uses crypto.subtle in your browser. No token, secret or claim is sent anywhere — nothing about the token leaves the tab.
Why use the JWT Decoder?
Most JWT bugs are visible the moment you can read the token. A decoder turns an opaque 300-character string into the two JSON objects that explain why a request was rejected, why a session ended early, or why an authorisation check took the wrong branch.
- Expiry maths becomes obvious: "exp: 1516242622" tells you nothing; "2018-01-18T01:30:22.000Z (EXPIRED)" tells you the whole story. Clock skew, a too-short TTL and a token cached past its lifetime all look identical in logs but are immediately distinguishable here.
- Catches claim-shape mismatches: Authorisation code usually reads aud, iss, scope, roles or a custom namespaced claim. Seeing the payload confirms whether a role arrived as a string, an array, or under a different key than your middleware expects — the most common cause of a 403 that "should not happen".
- Reveals the algorithm the token declares: The header is attacker-controlled data. Reading alg for a real token from your system shows what your issuer actually emits, which is the baseline you need before pinning an allowlist in the verifier.
- Verifies HMAC secrets without writing a script: When a service rejects a token you believe is valid, pasting the secret settles whether the signature is genuinely wrong or the failure is elsewhere — expiry, audience, issuer, or a key-rotation mismatch.
- Safe for shared screens and tickets: Because nothing is uploaded, you can decode a token from a bug report during a call without it landing in a third-party log. Treat the token itself as a live credential regardless: if it is still valid, it grants access.
When to use the JWT Decoder
Reach for it whenever a token-based request behaves differently from what the token appears to say.
- Debugging a 401 or 403 from an API when the client believes it is authenticated — decode the token and check exp, aud and iss before touching the server.
- Investigating sessions that expire too soon or refuse to expire: compare iat and exp to work out the real TTL your issuer is applying.
- Confirming what an identity provider actually puts in an ID token or access token after you change scopes, add a custom claim, or move to a new tenant.
- Checking a suspected clock-skew problem, where nbf is a few seconds in the future relative to the verifying server.
- Validating a shared HMAC secret between two services during a rotation, when one side accepts tokens and the other rejects them.
- Reviewing a token in a security assessment: read the header to see whether the issuer emits a kid, and whether alg is what the verifier should be pinning.
- Teaching or reviewing: showing a colleague that the payload is Base64url, not encrypted, is faster than arguing about it.
How to use the JWT Decoder
Decoding is automatic; verification is opt-in.
- Paste the token: Drop the full header.payload.signature string into the JWT box. Decoding is debounced, so results appear about a fifth of a second after you stop typing. Leading and trailing whitespace is trimmed for you.
- Read the header and payload panes: The left pane shows the JOSE header (alg, typ, and kid if present); the right pane shows the claims. Each pane has copy, download and expand controls if you need the JSON elsewhere or want a full-screen view.
- Check the time claims card: Below the panes, any iat, nbf or exp present is listed as an ISO 8601 timestamp. An exp already in the past is tagged (EXPIRED) — that one line answers most "why was my request rejected" questions.
- Optionally verify the signature: Type the shared secret into the "Verify secret" field. The status line switches to "✓ Signature valid" or "✗ Signature invalid". Only HS256/HS384/HS512 can be checked; for RS/ES/PS tokens the tool says so instead of guessing.
- Clear before you walk away: Use the clear control on the JWT field to wipe both the token and the secret. A live token is a bearer credential, so do not leave one sitting in a browser tab on a shared machine.
Key features
- Live decoding: A 200 ms debounced parse updates both panes as you edit, so truncated or mis-pasted tokens surface immediately rather than after a button press.
- NumericDate rendering with expiry flag: iat, nbf and exp are converted from Unix seconds to ISO 8601 and compared against your clock, with (EXPIRED) appended when exp has passed.
- HS256 / HS384 / HS512 verification: Signature checking uses Web Crypto HMAC with SHA-256, SHA-384 or SHA-512, selected from the token's own alg header, and compares against the recomputed value.
- Clear diagnostics on malformed input: Fewer than two dot-separated segments produces "Not a JWT: expected header.payload.signature" rather than a stack trace; invalid Base64url or non-JSON segments are reported as errors.
- Copy, download and expand: Header and payload each have a copy button, a download button (jwt-header.json / jwt-payload.json) and an expand view for long claim sets.
- Nothing leaves the browser: Both the Base64url decode and the HMAC run client-side. There is no upload step, so tokens and secrets stay in the tab.
Common use cases
- API debugging: Decode the Authorization: Bearer value from a failing request and compare the claims against what the endpoint requires.
- Identity provider integration: Inspect ID and access tokens from Auth0, Okta, Entra ID, Cognito or Keycloak to confirm scopes, audience and custom claims after a config change.
- Session lifetime tuning: Measure real token TTLs from exp − iat before changing refresh logic, instead of trusting the value in a config file.
- Secret rotation checks: Verify that a token signed with the old HMAC secret fails and one signed with the new secret passes, before flipping traffic.
- Security review: Read the header to document which algorithms your issuer emits, which is the input to an allowlist in the verifier and to a check for alg confusion.
- Support and triage: Turn a token pasted into a ticket into a readable claim set to confirm which tenant, user or role the reporter was actually using.
Examples
Tokens below are signed with the literal secret "secret" so you can reproduce the verification result.
Standard HS256 token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.1VAmegkQOBsQ-iSZU96z6c8NY7QR9OhhYWYHwHTeGT4Header: { "alg": "HS256", "typ": "JWT" }
Payload: { "sub": "1234567890", "name": "Jane Doe", "iat": 1516239022 }
iat 2018-01-18T01:30:22.000ZWith secret "secret" the status line reads ✓ Signature valid. There is no exp, so this token never expires on its own — a design smell in most systems.
Expired token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDI2MjJ9.3LTR8Y3M6AMp673WIb-x3XDzZH5SbMPif1YjkSdx1xMiat 2018-01-18T01:30:22.000Z
exp 2018-01-18T02:30:22.000Z (EXPIRED)exp − iat is 3600, so the issuer used a one-hour TTL. The signature is still valid — validity and expiry are independent checks, and a verifier must do both.
HS512 token
eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.RhvLwSdLXxBvPxdizGznjDD3guUmmCxGtifVUZwHc59RUpkIeX5jxVDHfG1Hxg1kdGuA0gHQB2Bqj1FmKED5KwHeader: { "alg": "HS512", "typ": "JWT" }
Status: ✓ Signature validThe signature segment is 86 Base64url characters here versus 43 for HS256, because SHA-512 produces 64 bytes of output instead of 32.
An "alg": "none" token — never accept this
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.Header: { "alg": "none", "typ": "JWT" }
Payload: { "sub": "admin", "role": "admin" }
Status: Cannot verify alg "none" here (HMAC only)Anyone can craft this in seconds — the signature segment is empty. RFC 8725 §3.1 tells verifiers to reject unsigned tokens; a library that honours the header's alg blindly will accept it as admin.
Truncated paste
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9Not a JWT: expected header.payload.signatureOne segment means the copy was cut short — usually a line-wrapped token from a log or a terminal. Re-copy the whole value including both dots.
Technical reference
Facts this tool relies on:
- Specifications
- RFC 7519 (JWT claims), RFC 7515 (JWS compact serialisation), RFC 7518 (JWA algorithms), RFC 8725 (JWT Best Current Practices)
- Structure
- base64url(header) . base64url(payload) . base64url(signature) — three segments, dot-separated, no padding characters
- Encoding
- Base64url per RFC 4648 §5: - and _ replace + and /, and trailing = padding is stripped
- Verifiable algorithms
- HS256 (HMAC-SHA-256), HS384, HS512. RS*, PS*, ES* and EdDSA decode but cannot be verified here — they need a public key
- Time claims
- iat, nbf, exp are NumericDate: seconds (not milliseconds) since 1970-01-01T00:00:00Z
- Registered claims
- iss, sub, aud, exp, nbf, iat, jti — all optional in RFC 7519; a verifier must decide which it requires
- Minimum HMAC key size
- RFC 7518 §3.2 requires a key of at least the hash output length — 256 bits for HS256, 384 for HS384, 512 for HS512
- Confidentiality
- None. A signed JWT is integrity-protected only; the payload is readable by anyone. Encrypted tokens use JWE (RFC 7516), which this tool does not decrypt
Common mistakes to avoid
Treating a decoded payload as trustworthy
Why it happens: Decoding proves only that the segments are valid Base64url JSON. It says nothing about who produced them. A client can mint any payload it likes, drop the signature, and the header and claims will still render perfectly here. Teams that read claims from a token in a gateway, a log pipeline, or a debug endpoint and then act on them have effectively let the caller set their own user id and role.
How to avoid it: Verify the signature against a key you control before any claim influences a decision, and do it on the server, not in the browser. In this tool that means supplying the secret and seeing "✓ Signature valid" before you draw conclusions. In code, use a library call that both verifies and validates, and confirm exp, nbf, iss and aud in the same step — a valid signature on a token issued by a different tenant is still the wrong token.
Letting the token choose its own verification algorithm
Why it happens: The alg value lives in the attacker-controlled header, and older libraries used it to pick the verification path. Two classic exploits follow: "alg": "none", where the verifier accepts an unsigned token outright, and RS256→HS256 confusion, where an attacker re-signs the token using the server's public RSA key as an HMAC secret. Because the public key is published, the forged signature validates. Both attacks need no secret material at all.
How to avoid it: Pin the expected algorithm in the verifier rather than reading it from the token — pass an explicit allowlist (for example algorithms: ['RS256']) and reject anything else, matching alg case-sensitively as RFC 8725 §3.1 and §3.2 require. Never load the same key material for both an asymmetric and a symmetric algorithm, and never fall back to a default when the header is missing. Use this decoder to confirm which alg your issuer really emits before you write the allowlist.
Putting secrets or personal data in the payload
Why it happens: A signed JWT is integrity-protected, not confidential. Base64url is an encoding, so the payload is one paste away from plain text for anyone who obtains the token — and tokens end up in browser storage, server access logs, proxy logs, crash reports and screenshots. API keys, internal database ids, national identifiers, email addresses and full permission maps in a JWT are all effectively public once the token leaves your control.
How to avoid it: Keep the payload to the minimum a verifier needs: a subject identifier, an audience, a short expiry, and a coarse scope or role. Look up anything sensitive server-side from the subject. If a claim genuinely must be hidden in transit, use JWE (RFC 7516) rather than assuming Base64url hides it, and shorten token lifetimes so a leaked token has a small window.
Confusing NumericDate seconds with JavaScript milliseconds
Why it happens: RFC 7519 defines exp, nbf and iat in seconds since the epoch, while Date.now() and new Date().getTime() return milliseconds. Setting exp from Date.now() produces a timestamp roughly a thousand times too large — a token valid until the year 51,000, which every expiry check silently passes. The inverse mistake, feeding NumericDate seconds straight into new Date(), yields a date in January 1970 and makes valid tokens look ancient.
How to avoid it: Divide by 1000 and floor when writing claims: Math.floor(Date.now() / 1000). Multiply by 1000 when reading them: new Date(exp * 1000). This decoder does the conversion for you, so a nonsensical year in the claims card is the fastest way to spot the bug. Sanity-check the magnitude too — current NumericDate values are ten digits, and a thirteen-digit value is a millisecond timestamp.
Assuming a decoded token can be revoked
Why it happens: A JWT is self-contained: the verifier checks a signature and some timestamps, with no lookup against your database. That is the performance benefit and the operational trap. Once issued, a token stays acceptable until exp passes, so a logout, a password reset, a role downgrade or a compromised-account response does not invalidate the tokens already in the wild. Decoding a token tells you when it stops working, not whether it should still work.
How to avoid it: Keep access token lifetimes short — minutes, not days — and put longevity in a refresh token you can revoke server-side. Include a jti claim so individual tokens can be denylisted, and bump a per-user token version or key id so that a security event can invalidate everything issued before it. Where immediate revocation is a hard requirement, use opaque session tokens with server-side state instead of self-contained JWTs.
Frequently asked questions
Is decoding a JWT the same as verifying it?
No. Decoding just Base64url-decodes the header and payload into JSON, which anyone can do with no key at all. Verifying recomputes the signature over the header.payload string using a key and compares it to the third segment, proving the token was issued by whoever holds that key and has not been altered. This tool decodes automatically and verifies only when you supply an HMAC secret, and the status line always tells you which happened. Verification also is not the whole job: a verifier must additionally check exp, nbf, iss and aud, because a genuinely signed token can still be expired or intended for a different service.
Can I decode a JWT without the secret key?
Yes, and that is the point people most often get wrong. The header and payload are Base64url-encoded, not encrypted, so any decoder reads them without a key — which is why a JWT is never a place to hide data. The secret is only needed to check the signature or to create a new token. If you have a token but no secret, you can still see every claim, spot an expired exp, and confirm which algorithm and key id the issuer used. You just cannot prove the token is authentic, so treat its contents as a claim, not a fact.
Why does my JWT show as expired when the server accepts it?
Expiry is judged against a clock, and there are at least three clocks involved: the issuer's, the verifier's, and yours. This tool compares exp to your local machine time, so a browser with a drifting clock reports (EXPIRED) for a token the server still honours. Most libraries also apply a small leeway — commonly 30 to 60 seconds — to absorb skew, so a token a few seconds past exp is often still accepted. Check the ISO timestamp against a trusted time source, and if the gap is large, fix clock sync with NTP on the affected host rather than widening the leeway.
What does "alg": "none" mean and why is it dangerous?
"none" is the unsecured JWS defined in RFC 7515: a token with an empty signature segment and no integrity protection at all. It exists for cases where the transport already guarantees integrity, but in practice it is an attack primitive. An attacker takes a real token, rewrites the payload to grant themselves admin, sets alg to none, deletes the signature, and sends it. Any verifier that reads alg from the token and dispatches on it will accept the forgery. RFC 8725 §3.1 tells implementations to reject unsigned tokens, and the fix is to pin an explicit algorithm allowlist in your verifier rather than trusting the header.
Which JWT algorithms can this tool verify?
HS256, HS384 and HS512 — the HMAC family, which use one shared secret for both signing and verification. The tool reads the alg from the token's header, maps it to SHA-256, SHA-384 or SHA-512, and recomputes the signature with Web Crypto. RS256, PS256, ES256 and EdDSA tokens decode normally but cannot be verified here, because they require the issuer's public key rather than a secret; the tool says so explicitly instead of failing silently. That distinction matters in production too: HMAC secrets must be shared with every verifier, while asymmetric signing lets you distribute a public key freely and keep the private key with the issuer alone.
Is it safe to paste a production JWT into an online decoder?
This tool never transmits it — the Base64url decode and the HMAC both run in your browser via Web Crypto, with no upload step. That still leaves two risks worth naming. A valid token is a live bearer credential, so anything that captures your screen, clipboard or browser session captures access; and a secret you paste to verify a signature is the key to minting new tokens. For production material, prefer a token you can revoke or one that has already expired, clear the fields when you are done, and rotate the secret if you have any doubt about the machine you used.
How long should a JWT be valid?
Short enough that a leaked token stops being useful quickly. A common pattern is an access token of 5 to 15 minutes paired with a longer-lived refresh token held server-side, because refresh tokens can be revoked while a self-contained JWT cannot. Decoding a real token and reading exp − iat from the claims card shows the TTL your issuer actually applies, which is often not what the config suggests. If you find multi-day access tokens, treat that as a finding: there is no way to log a user out, downgrade a role, or respond to a compromise until every one of them expires.
References
Privacy and availability
- Runs entirely in your browser — zero server processing
- No signup or account required
- Works offline once loaded
- Fast, lightweight, no external dependencies
- Available as a browser extension for Chrome and Firefox