JWT (JSON Web Token) is everywhere in modern authentication. Every OAuth flow, most REST APIs, and nearly all SPA auth systems use JWTs. But the token itself is just a Base64URL-encoded blob — three parts separated by dots. Without a decoder, you can’t read it.
This guide covers what’s inside a JWT, how to read each part, what the claims mean, and how signature verification works in the browser.
The Structure of a JWT
A JWT looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsIm5hbWUiOiJBbGljZSIsInJvbGVzIjpbImFkbWluIl0sImlhdCI6MTcwMDAwMDAwMCwiZXhwIjoxNzAwMDg2NDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Three Base64URL-encoded segments separated by . (for a deep dive into the encoding format itself, see the Base64 guide):
<header>.<payload>.<signature>
Header
The header describes the token format and algorithm:
{
"alg": "HS256",
"typ": "JWT"
}
alg is the signature algorithm. typ is always JWT for a standard JWT.
Common algorithm values:
| Algorithm | Type | Notes |
|---|---|---|
HS256 | HMAC-SHA256 | Symmetric; requires shared secret |
HS384 | HMAC-SHA384 | Symmetric; stronger than HS256 |
HS512 | HMAC-SHA512 | Symmetric; strongest HMAC |
RS256 | RSA-SHA256 | Asymmetric; uses public/private key pair |
RS384 | RSA-SHA384 | Asymmetric |
RS512 | RSA-SHA512 | Asymmetric |
ES256 | ECDSA-SHA256 | Asymmetric; smaller keys than RSA |
PS256 | RSASSA-PSS | Asymmetric; modern RSA variant |
none | No signature | Security risk — see below |
Payload
The payload contains the claims — statements about the token subject and additional metadata:
{
"sub": "user_123",
"name": "Alice",
"roles": ["admin"],
"iat": 1700000000,
"exp": 1700086400
}
Signature
The signature is not JSON — it’s a binary cryptographic signature, Base64URL-encoded. It proves the header and payload weren’t tampered with (for signed tokens) or is empty (for alg: none).
Standard Claims
The JWT spec defines a set of registered claims. These have specific meaning:
| Claim | Name | Type | Description |
|---|---|---|---|
sub | Subject | string | Who the token is about (typically a user ID) |
iss | Issuer | string | Who created the token (e.g., your auth server URL) |
aud | Audience | string/array | Intended recipients |
exp | Expiration | Unix timestamp | When the token expires |
iat | Issued At | Unix timestamp | When the token was created |
nbf | Not Before | Unix timestamp | Token not valid before this time |
jti | JWT ID | string | Unique identifier for the token |
The decoder renders timestamps as human-readable dates. exp: 1700086400 becomes something like Tuesday, Nov 15, 2023 at 20:00:00 UTC — immediately readable.
Reading the Expiry
The exp claim is the most commonly inspected claim. The decoder shows:
- The formatted date and time
- Whether the token is currently valid or expired
This is useful when debugging authentication failures — a common cause is simply that a token has expired and the refresh flow failed silently.
The decoder also shows iat (issued at) and nbf (not before) as formatted timestamps.
Security Warnings the Decoder Shows
The decoder highlights three specific security issues:
1. alg: none
The none algorithm means no signature is applied. The token is not authenticated — anyone can create a none token with any claims and it will pass format validation.
This was a real CVE (CVE-2015-9235) — some JWT libraries accepted alg: none tokens even in contexts that should require a signature. If you see this in a real token your application is accepting, it’s a critical bug.
2. Missing exp
A JWT without an expiry never expires. If an attacker gets hold of such a token, they have permanent access. Legitimate tokens should always include exp.
3. Expired token
The token has an exp claim but the time has passed. In some authentication systems, expired tokens should be handled gracefully (refresh the token), but accepting an expired token without error is a bug.
Signature Verification: HMAC
For HMAC tokens (HS256, HS384, HS512), the decoder can verify the signature if you provide the secret. For generating and verifying HMACs independently — useful for webhook payload verification (GitHub, Stripe, Shopify) and API request signing — the Hash Generator guide includes a dedicated HMAC tab for SHA-256, SHA-384, and SHA-512.
How HMAC signing works:
signature = HMAC-SHA256(base64url(header) + "." + base64url(payload), secret)
If you have the same secret that signed the token, you can verify that the header and payload haven’t been tampered with.
Where to get the secret:
- For tokens from your own application, the secret is in your config (usually an environment variable)
- For third-party tokens (OAuth, identity providers), you won’t have the HMAC secret — those typically use RS256 or ES256 with public keys
Enter the secret in the Signature panel. The decoder uses the SubtleCrypto API to compute the HMAC in-browser and compares it to the token’s signature. No data leaves your machine.
Result states:
- ✅ Valid — signature matches; the token was signed with this secret and hasn’t been tampered with
- ❌ Invalid — signature doesn’t match; either wrong secret or the token was modified
- ⚠️ Unsupported — the algorithm can’t be verified in this mode (RS256, ES256, etc.)
- 🔴 Error — malformed token or key
Signature Verification: RSA and ECDSA
For asymmetric algorithms (RS256, RS384, RS512, ES256, ES384, ES512, PS256), verification requires the public key from the issuer — not the private key.
Public keys are commonly distributed via JWKS (JSON Web Key Set) endpoints:
https://your-auth-server.com/.well-known/jwks.json
A JWKS response looks like:
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "abc123",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2...",
"e": "AQAB"
}
]
}
The decoder supports pasting either:
- A single JWK (the key object)
- A full JWKS (the
{ "keys": [...] }object) — it finds the matching key bykid(key ID) from the token header
This lets you verify tokens from Google, Auth0, Okta, Keycloak, and any other OIDC-compliant provider — entirely in-browser.
The Claims Inspector
The decoder renders a table of claims with human-readable descriptions for known fields:
| Raw claim | Friendly label | Example rendered value |
|---|---|---|
sub | Subject | user_123 |
exp | Expires At | Thu Mar 12 2026 09:00:00 UTC |
iat | Issued At | Thu Mar 12 2026 08:00:00 UTC |
nbf | Not Before | Thu Mar 12 2026 08:00:00 UTC |
iss | Issuer | https://auth.example.com |
aud | Audience | api.example.com |
jti | JWT ID | abc-123-def |
Custom claims (anything outside the registered set) are displayed as-is.
Arrays in claims (like roles: ["admin", "editor"]) are rendered as badge pills for readability.
Practical Workflows
Debugging an authentication failure
User reports they’re getting 401 errors. You grab their token from the request header and paste it in:
- Check
exp— is it expired? - Check
iss— is it from the right issuer? - Check
aud— does it match the API? - Verify the signature if you have the secret — was the token tampered with?
Most auth failures fall into these categories.
Understanding a third-party token
You’re integrating with a new OAuth provider and you want to understand what’s in the token before you write code to consume it. Decode it, read the claims, understand the structure.
Checking token freshness
Before making an API call in a long-running script, decode the token and check if exp is within the next few minutes. If so, refresh it first.
Security audit
You’re reviewing an old authentication system and someone mentions tokens don’t expire. Decode a sample token — if there’s no exp claim, you’ve confirmed the problem.
What the Decoder Doesn’t Do
Encrypt or decrypt. JWTs can be encrypted (JWE — JSON Web Encryption), which is different from signed JWTs (JWS). Encrypted JWTs look like 5-part tokens. The decoder handles signed JWTs (JWS), not encrypted ones.
Validate claims semantically. It tells you that exp is in the past, but it doesn’t know if iss matches what your application expects, or if the aud is correct for your service. Claim validation is your application’s job.
Store or transmit your tokens. The decoder runs entirely in-browser. Nothing is sent to any server.
Decoding a JWT takes three seconds with the right tool. The value is being able to read the claims, understand the expiry, and check the signature — all in your browser, all without any server roundtrip.