JWT Decoder, Explained
After reading this you will be able to read any JWT by hand, tell the header from the payload from the signature, check whether a token has expired, and know exactly why decoding proves nothing about who signed it.
What a JWT actually is
A JSON Web Token (JWT) is three Base64Url strings joined by two dots. Take this token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Split it on the dots and you get three parts. The first part decodes to the header, the second to the payload, and the third is the signature. The header and payload are plain JSON that anyone can read. The signature is raw bytes that let a server check the token was not altered.
RFC 7519 defines the JWT itself. The compact serialization with two dots comes from RFC 7515 (JSON Web Signature). The format is:
Here BASE64URL is the URL-safe Base64 alphabet from RFC 4648 section 5: it uses - and _ in place of + and /, and it drops the trailing = padding. Decoding a JWT means splitting on the dots and Base64Url-decoding the first two segments.
Decoding is not verifying. The header and payload are readable by anyone who holds the token. A decoder shows you what the token claims, not whether those claims are genuine. Only checking the signature with the right key does that.
When to decode, and when to verify instead
Decode a token when you want to see what is inside it: which claims a login flow set, when a session expires, which issuer minted it, or why an API returns a 401. Decoding answers "what does this token say?"
Do not rely on decoding when you need to trust the answer. Your backend must verify the signature before it acts on any claim. If code reads sub or role from a decoded payload without checking the signature, an attacker can forge any value by editing the payload and re-encoding it. Decoding is a debugging and inspection step. Verification is a security step, and it needs the signing key, which a decoder never has.
The three parts in detail
The header describes how the token is signed. It almost always has two fields: alg, the algorithm (for example HS256 for HMAC-SHA256 or RS256 for RSA with SHA-256), and typ, usually the literal string JWT.
The payload holds the claims. RFC 7519 section 4.1 registers seven standard claim names. The ones you meet most:
- iss
- Issuer. Who created the token, often a URL or service name.
- sub
- Subject. Who the token is about, usually a user ID.
- aud
- Audience. Who the token is intended for. Your service should reject a token whose
audis not you. - exp
- Expiration time. A NumericDate: seconds since 1970-01-01 UTC. The token must not be accepted at or after this instant.
- nbf
- Not before. The token must not be accepted before this instant.
- iat
- Issued at. When the token was created.
- jti
- JWT ID. A unique identifier, useful for blocking replay.
The three time claims (exp, nbf, iat) are numbers, not strings. RFC 7519 section 2 defines a NumericDate as the number of seconds since the Unix epoch, ignoring leap seconds. So 1516239022 means 2018-01-18 01:30:22 UTC. A decoder converts that number to a readable date and compares it to your clock.
Checking expiry against your clock
Whether a token is expired is a single comparison. Let t be the current time in seconds since the epoch, and e be the exp value. The token is expired when:
The remaining lifetime in seconds is e - t. If that number is negative, the token expired that many seconds ago. A common wrinkle is clock skew: your machine and the server may disagree by a few seconds. RFC 7519 section 4.1.4 allows "some small leeway, usually no more than a few minutes." Many libraries default to 60 seconds. So a token that reads as expired by 5 seconds may still be accepted by a server that allows 60 seconds of skew.
The decoder uses your device clock as t. If your system time is wrong, the expiry verdict is wrong. When a token that should be valid reads as expired, check your clock before you blame the token.
Worked example: decoding the sample token
Load the demo token and reproduce it by hand. Split on the two dots to get three segments.
- First segment
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9Base64Url-decodes to{"alg":"HS256","typ":"JWT"}. So it is signed with HMAC-SHA256. - Second segment
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNTE2MjM5MDIyfQdecodes to{"sub":"1234567890","name":"Alice","iat":1516239022}. - Read the claims: subject is user
1234567890, a custom fieldnameisAlice, and it was issued at1516239022, which is 2018-01-18 01:30:22 UTC. - There is no
expclaim here, so this token never expires on its own. That is common in examples and dangerous in production. - The third segment is the signature. The decoder shows it but cannot check it without the HMAC secret.
Notice why the encoded text starts with eyJ in both cases. The JSON begins with {", and those two bytes Base64-encode to eyI with the next character folding in. Any JWT header or payload you ever see starts with eyJ because every one begins with a JSON object.
Reading the timeline of a token
Three of the claims place the token on a timeline: it is issued at iat, becomes valid at nbf, and dies at exp. Consider a token with iat = 1516239022, nbf = 1516239022, and exp = 1516242622. That is a one-hour window (3600 seconds).
nbf, valid for 3600 seconds, then rejected once your clock reaches exp.Common mistakes
Several errors show up again and again when people work with JWTs.
- Trusting the payload without verifying the signature. The payload is editable by anyone. Never authorize based on a decoded-only token.
- Confusing seconds with milliseconds. NumericDate is seconds. JavaScript's
Date.now()returns milliseconds. A token withexp = 1516242622compared against a millisecond timestamp of1516242622000will always look expired by roughly 48000 years. - Assuming the token is encrypted. A standard signed JWT is not encrypted. Base64Url is encoding, not encryption. Anyone holding the token reads every claim. Put no secrets in the payload.
- Accepting
alg: none. The header can claimnone, meaning no signature. A verifier that honors this accepts unsigned tokens. Rejectnoneand pin the expected algorithm. - Ignoring
aud. A token minted for service A should not be accepted by service B. Check thataudnames your service.
Related tools on this site
A JWT is layered encoding on top of a hash-based signature, so several other tools help you take one apart. Use the Base64 / URL / HTML Encoder-Decoder to decode a single segment by hand and confirm what the JWT decoder shows. When the header says HS256, the signature is an HMAC, which you can reproduce with the HMAC Calculator if you hold the secret. The Hash Generator covers the underlying SHA-256 and related digests. To measure the raw size of a long token in bytes, the String Length Inspector reports UTF-8 byte counts, and the Byte Size Converter translates those into KB or KiB.
Frequently asked questions
Can this decoder tell me if my token is valid?
It checks structure and expiry, not authenticity. It confirms the token has three parts, decodes the header and payload, and compares exp and nbf to your clock. It cannot verify the signature because it does not have the signing key.
Why does the decoder work without the secret?
Because the header and payload are only Base64Url-encoded JSON, not encrypted. The secret is needed only to compute or check the signature. Reading the claims needs nothing but the token itself.
Is it safe to paste a token here?
This tool runs entirely in your browser and sends nothing to any server. Even so, treat production tokens as live credentials. Anyone who reads a valid, unexpired token can often impersonate its subject until it expires, so avoid pasting real production tokens into any online tool that transmits them.
My token reads as expired but the server accepts it. Why?
Two likely reasons. Your device clock may be ahead of the server's, so t is too large. Or the server allows clock skew leeway, commonly 60 seconds, so a token a few seconds past exp is still honored.
What does eyJ at the start of every token mean?
Nothing special beyond the format. Every header and payload is a JSON object starting with {", and those bytes Base64-encode to text beginning eyJ. It is a reliable sign you are looking at a JWT segment.