JWT Decoder
Decode and verify JWT tokens online for free. Inspect payload and header instantly.
Advertisement
Header
{
"alg": "HS256",
"typ": "JWT"
}Payload
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}Signature
Advertisement
How to Use This Tool
Paste Your JWT Token
Paste your full JWT token string (the three dot-separated parts) into the input field.
Inspect the Decoded Parts
The tool instantly decodes and displays the Header (algorithm and token type) and Payload (claims like user ID, name, expiry) in formatted JSON.
Check Token Expiry
The status bar at the top shows whether the token is valid or expired, and if valid, how much time remains until expiry.
Advertisement
Related Tools
Frequently Asked Questions
What is a JWT token?
Is it safe to paste a JWT in this tool?
What are common JWT claims?
Can I verify a JWT signature with this tool?
About JWT Decoder
Your frontend logs show a 401 from a protected endpoint, the backend says 'token expired', but the user just logged in 30 seconds ago. Or you are debugging a Supabase Row Level Security rule that checks auth.jwt() -> 'role' and the claim is not landing where you expect. Paste the token here and the three dot-separated sections split into decoded header (algorithm, key ID, token type), payload (issuer, subject, expiration, custom claims), and signature bytes. The tool does NOT verify the signature — it only decodes — so you can safely paste tokens signed with secrets you do not have, but it will warn you if exp is in the past, show exactly how many hours until expiration, and flag the algorithm in the header (RS256 tokens alg-switched to HS256 is the canonical JWT vulnerability from 2016 that still appears in CTFs and the occasional real-world audit). All decoding is client-side, which matters because JWTs you paste here often contain production PII or admin role claims you cannot ethically upload to a third-party service.
How it works
- 1
Split on dots and base64url-decode each part
A JWT is three segments joined by '.', each segment is base64url-encoded (using - and _ instead of + and /, with padding stripped). We split, re-pad with '=' characters to make the length a multiple of 4, translate - → + and _ → /, then atob and JSON.parse the first two segments. The third segment (signature) is raw binary and is shown as its base64url string for reference, not decoded further.
- 2
Expiration calculated from the exp claim
If the payload contains an exp claim (Unix timestamp measured in seconds per RFC 7519), we compare to Math.floor(Date.now() / 1000). A negative difference means expired and the banner turns red. A positive difference renders as 'X days Y hours until expiry'. Tokens without an exp claim show a yellow warning — non-expiring JWTs are a known anti-pattern because there is no safe way to revoke them without maintaining a denylist.
- 3
No signature verification — by design
Verifying RS256 requires the issuer's public key from a JWK endpoint, and verifying HS256 requires the shared secret. We cannot fetch external URLs without introducing privacy leaks, and asking for the secret would encourage pasting production secrets into a web form. For verification, use jwt.io with your keys locally or jose in Node — our scope is read-only inspection of claims.
Pro tips
Always check the alg in the header, not just the payload
The canonical JWT attack is alg confusion: a server configured to verify RS256 tokens will happily accept an HS256 token if the library does not check the header's alg matches the expected one. The attacker signs with the public key (which is, by definition, public) treated as an HMAC secret. If you decode a token and see alg: 'HS256' where your service should be issuing RS256, escalate immediately — this is either a real attack or a misconfigured service. alg: 'none' tokens should never be accepted by any modern library but legacy code sometimes does.
Decode the iss and aud claims before trusting any token
A JWT from issuer A is not valid for service B even if the payload looks correct. The aud (audience) and iss (issuer) claims exist precisely to prevent cross-service token reuse. When debugging an auth flow involving multiple services (Auth0 issuing tokens for a Supabase frontend calling a custom API), decode here and confirm aud matches the expected identifier for each hop. Mismatched aud is the second-most-common cause of '401 but it looks fine' bugs after clock skew on exp.
Watch for iat significantly before the login event
The iat (issued-at) claim is a Unix timestamp of when the token was minted. If you are debugging a session and see iat that is hours or days before the user's last login event, the app is probably reusing a cached token past its useful lifetime. Some IdPs issue tokens with long exp (24h) but expect you to refresh proactively — check whether iat was more than half the exp-minus-iat window ago and trigger a refresh in your client rather than waiting for a 401 that surprises the user mid-action.
Honest limitations
- · Decode only — we do not verify signatures because that requires secrets or public keys we cannot ask for or fetch safely.
- · Encrypted JWTs (JWE) with five dot-separated parts cannot be decoded without the decryption key; only signed JWTs (JWS) work here.
- · Custom claims with binary or encrypted values will display as unreadable base64 strings after the JSON parse — use a dedicated tool if your payload uses nested JWE claims.
Frequently asked questions
Is it safe to paste a production JWT into this tool?
Safer than most alternatives because decoding happens entirely in the browser tab — no network request leaves the page with the token contents. That said, a JWT is bearer-authenticated: anyone who has the token can use it until it expires. If the token has a long exp and you are worried about clipboard history, browser extensions, or someone glancing at your screen, revoke the token on the issuer side after debugging (most IdPs expose a 'revoke session' API or dashboard). For tokens signed by a service you control, you can also invalidate the signing key which kills every token at once — nuclear but effective.
Why does my token decode fine here but fail verification on the server?
Decoding is base64url + JSON.parse, which never fails for a well-formed token regardless of the signature. Verification checks that the signature bytes, when recomputed from header.payload and the signing key, match the third segment. The mismatch is almost always one of: the server is using the wrong signing key (especially with key rotation), the algorithm declared in the header differs from what the library expects, or the exp claim is past and the library rejects before checking the signature. Check each in order: exp first, then alg, then key ID if the header includes kid.
What does the exp claim actually mean, and why is my token 'expired' on the backend but not the frontend?
exp is seconds since Unix epoch, and the spec (RFC 7519 section 4.1.4) says tokens 'MUST NOT be accepted for processing' after that time. The usual culprit for 'expired on backend only' is clock skew: if the backend's system clock is 60 seconds ahead of the issuer's, a token with 30 seconds left when minted arrives already-expired. Most libraries allow a small leeway (default 60s in node-jsonwebtoken) but configured values often differ between services. Run NTP everywhere, set leeway to at least 30 seconds on verification, and log the exact exp-vs-now difference when rejecting tokens so future debugging is cheap.
Can I decode an encrypted JWT (JWE) here?
No. JWE tokens have five dot-separated segments (not three) and the payload is encrypted with a content encryption key that is itself encrypted with a key management key. Without the recipient's private key we cannot recover any of the content. If your token has more than three dots, it is JWE. To inspect JWE contents you need the relying party's decryption key and a library like jose — decryption is inherently key-dependent and cannot be done by a read-only tool. Most modern stacks use JWS (signed JWT) rather than JWE because signing without encryption is simpler to audit.
Why is the signature shown as gibberish?
Because it is. The signature segment of a JWT is raw binary bytes — the output of an HMAC-SHA256, RSA-PKCS1-v1.5, or ECDSA operation — encoded as base64url. Those bytes are not text and were never intended to be human-readable. We show the base64url string as a reference for comparison (you can copy it to a verification tool) and display the length in bytes so you can sanity-check the algorithm (HS256 produces 32 bytes, RS256 produces 256 bytes from a 2048-bit key). If you need the actual binary content, pipe the base64url through atob in a console.
JWT debugging connects to nearly every other developer tool in this set. The base64-encoder is what happens under the hood to the header and payload segments, and is useful for decoding the signature bytes into a hex or binary representation when comparing against a server-side HMAC output. The json-formatter cleans up deeply-nested payload claims (especially in enterprise SSO tokens with 50+ fields) so you can actually read them. When a JWT-signed webhook needs to be scheduled, the cron-generator builds the recurrence, and the regex-tester helps you pattern-match the 'Bearer eyJ...' prefix inside log streams when tracking down which service is sending malformed tokens.
Advertisement