Decoding a JWT vs Verifying It: Reading exp and iat
Updated 2026.10.03 · 3 min read
Decoding a JWT only reads Base64URL text and does not verify the signature. See the token structure, how to read exp, iat and nbf, and why alg none is rejected.
Open JWT DecoderDecoding is not signature verification. Anyone can read the contents of a JWT, so never put secrets in it. Paste a token into the JWT Decoder and you can see its header, its payload and when it expires, but that is only reading. Whether the token can be trusted is decided by the server that receives it, which verifies the signature with a key and checks claims such as exp and aud.
A JWT has three parts
A signed JWT has the form header.payload.signature, and each part is written in Base64URL without padding (RFC 7515). Here is a sample token, signed with the HS256 algorithm and the demonstration secret zekilo-secret.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlpFS0lMTyIsImlhdCI6MTc2NzIyNTYwMCwiZXhwIjoxNzY3MjI5MjAwfQ.218DR4r7DRNkKO3XJcQYXE0ztwbqxYMeQpkIp9S18oE
| Part | Decoded content |
|---|---|
| Header | {"alg":"HS256","typ":"JWT"} |
| Payload | {"sub":"1234567890","name":"ZEKILO","iat":1767225600,"exp":1767229200} |
| Signature | A 32-byte HMAC-SHA256 value (43 Base64URL characters) |
The header and payload are not encrypted. They are JSON written in Base64URL, and no key is needed to read them. A JWT whose content is encrypted is a different format called JWE, which has five dot-separated parts.
Decoding and verification compared
| Item | Decoding | Signature verification |
|---|---|---|
| What it does | Reads the JSON behind Base64URL | Checks the signature with a key |
| What it needs | Nothing | The secret (HS256) or the public key (RS256, ES256) |
| What it tells you | What is written in the token | That the content is what the key holder signed |
| Who can do it | Anyone | Whoever holds the key |
The signature covers the string made of BASE64URL(header), a dot, and BASE64URL(payload) (RFC 7515). Computing HMAC-SHA256 over the first two parts of the token above with zekilo-secret gives the same value as the third part. If even one character of the payload is different, the computed value is different too, and that is how the verifying side notices that the content was changed.
Verification does not end with the signature. The receiver also has to check the expiration time (exp), the issuer (iss) and the audience (aud). RFC 7519 requires a token to be rejected when it has an aud claim and the party processing it does not identify itself with one of its values.
Reading exp, iat and nbf
The value of these three claims is a NumericDate: the number of seconds since 1970-01-01T00:00:00Z (RFC 7519, section 2). It is not milliseconds. JavaScript’s Date.now() returns milliseconds, so divide it by 1000 before comparing.
| Claim | Meaning | Value in the sample | Date |
|---|---|---|---|
iat |
When the token was issued | 1767225600 | 2026-01-01 00:00:00 UTC |
exp |
Must not be accepted on or after this time | 1767229200 | 2026-01-01 01:00:00 UTC |
nbf |
Must not be accepted before this time | Not present | — |
exp minus iat is 3,600 seconds, so this token is valid for one hour after it is issued. RFC 7519 allows a small leeway, usually no more than a few minutes, to account for clock differences between servers. All registered claims are optional, so a token may not have them.
To turn the number into a date, enter 1767225600 in the Unix Timestamp Converter and it shows 2026-01-01T00:00:00Z. The JWT Decoder also shows a date next to exp, nbf and iat, and displays an expiry warning when exp has passed. That display compares the claim with your browser’s clock for reference; it does not replace verification.
alg: none and checking the algorithm
Section 6 of RFC 7519 defines an Unsecured JWT, a token without a signature. Its header has alg set to none and its signature part is empty. The format exists for cases where the token is protected by some other means; it is not something to accept as an ordinary authentication token.
- RFC 7518, section 3.6: implementations must not accept unsecured tokens by default.
- RFC 8725, section 3.1: libraries must let the caller specify a supported set of algorithms and must not use any other algorithm.
A server should therefore fix the algorithms it accepts in its own code and should not simply follow the alg value written in the token’s header. The JWT Decoder shows a warning that there is no signature when it reads a token whose alg is none.
Rules for daily work
- Do not put passwords, national ID numbers, card numbers or similar data in the payload.
- A token is a pass that works for whoever holds it. Do not paste production tokens into chats, issue trackers or logs.
- Verify on the server with a well-reviewed library: fix the algorithm, then check the signature and the
exp,nbf,issandaudclaims. - Use values decoded on the client only for display. Decide permissions from what the server has verified.