If you've worked with any modern web API, you've probably encountered a JWT. They show up as bearer tokens in HTTP Authorization headers, as the payload you get back from an OAuth flow, and as session tokens in browser cookies. They look like a wall of random characters, but they have a precise structure — and you can read them.
What a JWT Looks Like
A JWT is a string of three base64url-encoded parts joined by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Split it at the two dots and you get three segments:
- Header — describes the token type and the signing algorithm used
- Payload — carries the claims (the actual data the token asserts)
- Signature — cryptographic proof the token was issued by a trusted authority and hasn't been tampered with
Decoding vs. Verifying — A Critical Distinction
These two operations are commonly confused, and the distinction matters for security.
Decoding means base64url-decoding the header and payload to turn them from opaque strings into readable JSON. This requires no key and no secret — anyone can do it. All you're doing is reversing the encoding.
Verifying means checking the signature to confirm the token was genuinely issued by the expected authority and hasn't been modified since. This requires the signing key: either the HMAC shared secret (for HS256, HS384, HS512) or the public key (for RS256, ES256, and other asymmetric algorithms).
The security implication is this: a decoded token is not a verified token. Anyone can construct a JWT with any payload they like. The signature is what proves legitimacy. If your server-side code accepts a JWT's claims without verifying the signature, an attacker can forge tokens for any user they choose.
Verification must always happen server-side, in your application code, using a secret or key you control. A browser-based decoder — including the one on ToolHive — is for inspection only. Never paste your signing secret into any online tool.
Reading the Header
Decode the first segment and you get JSON like this:
{
"alg": "HS256",
"typ": "JWT"
}
alg— the algorithm used to sign the token.HS256is HMAC-SHA256 (symmetric, uses a shared secret).RS256is RSA-SHA256 (asymmetric, uses a public/private key pair).ES256uses elliptic curve cryptography.typ— token type, almost always"JWT".
Reading the Payload
The payload holds claims — key-value pairs asserting facts about the subject or the token itself. Some claim names are standardized by the JWT spec (RFC 7519):
sub— subject: who the token is about, typically a user IDiss— issuer: which service created the tokenaud— audience: which service the token is intended forexp— expiry: Unix epoch timestamp after which the token should be rejectediat— issued at: when the token was creatednbf— not before: earliest time the token is valid
A decoded payload might look like:
{
"sub": "user_8a3f12",
"name": "Jane Doe",
"email": "jane@example.com",
"role": "admin",
"iat": 1749996400,
"exp": 1750000000
}
The exp field is a Unix timestamp (seconds since January 1, 1970 UTC). To check whether a token has expired, convert that number to a date and compare it to now. An expired token will still decode — it just shouldn't be accepted by a server doing proper validation.
What the Signature Segment Contains
The third segment is the raw signature bytes, base64url-encoded. It's computed from the header and payload using the signing key, and it changes if either part is modified. You can see the raw signature in a decoder, but you can't verify it without the key — and that's by design.
When You'd Want to Decode a Token
Even without verifying the signature, decoding is genuinely useful during development and debugging:
- Checking what claims a token carries — is the
rolefield present? Is the user ID format correct? - Confirming when a token expires by reading the
expclaim - Debugging an OAuth flow where a downstream service is rejecting tokens — what does it actually contain?
- Inspecting a token from a request to understand why a server returned a 401
In production systems, your library (jsonwebtoken, python-jose, go-jwt, etc.) handles both decoding and verification together. Manual decoding is for debugging and inspection.
Try It
If you have a token in your clipboard — from a browser DevTools network panel, an API response, or a .env file — paste it into the JWT Decoder on ToolHive to see the header and payload decoded instantly. Decoding happens locally in your browser using the built-in atob / Web Crypto APIs; nothing is sent to a server.
One quick tip: if a token looks malformed or the decoder returns an error, check for extra spaces or line breaks. Tokens copied from email clients or Slack sometimes get whitespace inserted around the dots.