Developer Tools

How a JWT Is Structured and How to Read One Safely

A JWT is three Base64URL sections joined by dots. Learn what each part holds, how to read the claims, and why decoding a token is not the same as trusting it.

2026-10-266 min readBy ToolHive Team

If you build or test anything that logs users in, you will eventually meet a long string that starts with eyJ. That string is usually a JSON Web Token (JWT), defined in RFC 7519. This guide explains what is inside one, walks through a complete example, and spells out the one mistake that causes real security problems: treating a token that decodes cleanly as a token that can be trusted.

The three parts of a JWT

A signed JWT is three sections joined by dots:

header.payload.signature

  • Header: a small JSON object that names the signing algorithm (alg) and usually the token type (typ). It sometimes carries a key identifier (kid) that tells the server which key to verify with.
  • Payload: a JSON object of claims, which are statements about the user and the token itself.
  • Signature: bytes produced by the issuer from the header and payload using a secret or private key. The signature is not JSON, so it does not decode into readable text.

The header and payload are encoded with Base64URL, a variant of Base64 that swaps + for - and / for _ and normally drops the trailing = padding, so the token can sit safely in a URL or HTTP header. This is why every JWT header starting with {"alg" begins with eyJ: that is simply what the characters {" look like after encoding.

A worked example, step by step

Here is a made-up token you can paste into the JWT decoder. It is not tied to any real account, and its signature is placeholder text, so no server would accept it.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzQ4MjEiLCJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJhdWQiOiJvcmRlcnMtYXBpIiwiaWF0IjoxNzY3MjI1NjAwLCJleHAiOjE3NjcyMjkyMDAsInNjb3BlIjoib3JkZXJzOnJlYWQifQ.dGhpcyBpcyBub3QgYSByZWFsIHNpZ25hdHVyZQ

Splitting at the dots gives three pieces of 36, 170 and 38 characters. The calculator shows the first two as indented JSON.

The header decodes to:

{ "alg": "HS256", "typ": "JWT" }

That says the token was signed with HMAC-SHA256, a shared-secret algorithm. The payload decodes to:

ClaimValueWhat it means
subuser_4821The user this token is about
isshttps://auth.example.comWho issued the token
audorders-apiThe service the token is meant for
iat1767225600Issued at 1 January 2026, 00:00 UTC
exp1767229200Expires at 1 January 2026, 01:00 UTC
scopeorders:readA permission label (the name varies by provider)

The time claims are Unix timestamps in seconds, and the decoder shows them as raw numbers. Subtracting iat from exp gives 3,600 seconds, so this token lives for 60 minutes. To read the dates, convert the numbers yourself; a quick rule is that anything around 1.7 billion is in the mid-2020s.

You can also do the decoding by hand. Paste just the first section, eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9, into the Base64 decoder and you get the same header text. This is a useful way to see that a JWT has no magic in it. If the section contains - or _, swap them back to + and / first, as the Base64 decoder expects the standard alphabet.

Common claims and what to check in each

RFC 7519 registers a handful of standard claims. When a request is rejected, these are the ones to look at first:

  • exp: is the current time before it? Expired tokens are the most common cause of sudden 401 errors.
  • nbf: "not before". A token used a few seconds before this time is rejected, which can happen when two servers' clocks disagree.
  • iss: does it match the issuer your API is configured to trust?
  • aud: does it include your API? A token for one service should not work on another.
  • sub: the identifier your code will use to look up the user.

Anything beyond these, such as roles, email or scope, is a private or provider-specific claim. The names and formats differ between identity providers, so read your provider's documentation rather than guessing.

Decoding is not verifying

This is the most important section of this article. The payload is only encoded, so anyone who holds the token can read it. That has two consequences.

  1. Never rely on what you decoded. Anyone can build a token with any claims they like and attach a made-up signature. The example above decodes perfectly and is still worthless. A token is trustworthy only after the server checks the signature with the correct key and then checks exp, iss and aud. Do that in your server code with a maintained library, never in browser code and never by eye.
  2. Never put secrets in the payload. Passwords, card numbers and personal data do not belong in claims. If you need confidentiality, the standard is a different format, an encrypted JWT (JWE), which a decoder like ours cannot open.

Be careful with the header too. Well-known security guidance for JWTs (RFC 8725) warns against servers that trust whatever alg a token claims. A server should accept only the algorithms it expects, and should reject none, which means "unsigned".

Reading tokens safely

  • Treat a live token like a password. Until it expires, whoever holds it can act as that user. Do not paste production tokens into chat, tickets or public pages. Our decoder runs in your browser and does not send the token anywhere, but your clipboard, screen sharing and shared screenshots are still exposures.
  • Prefer test tokens. Generate one from a development environment when you only need to inspect the structure.
  • Log less. If your application logs full request headers, tokens end up in log files that many people can read. Log the sub and the token's expiry, not the token.
  • Keep lifetimes short. A short exp, such as the 60 minutes in the example, limits the damage from a leaked token. How you then renew sessions depends on your provider's refresh-token design.

A debugging checklist for a rejected token

  1. Copy only the token. Remove the word Bearer and the space after it, along with any quotation marks. The decoder expects the token itself and reports a problem if the first section is not valid Base64URL.
  2. Confirm there are exactly three sections. A truncated copy from a terminal or log line is a frequent cause of "Invalid JWT".
  3. Read exp and nbf against the current time. Convert the Unix seconds to a date.
  4. Compare iss and aud with your API configuration, character for character, including trailing slashes.
  5. Check the header's alg and kid against what the server expects. A key that was rotated is a classic cause of a signature failure.
  6. If a payload looks long or messy, copy the decoded text into the JSON formatter to validate it and read nested objects more easily.

Limits: what this tool does not do

The JWT decoder is a reading aid, nothing more. Specifically:

  • It does not verify the signature, so it cannot tell you whether a token is genuine.
  • It does not check expiry or convert timestamps to dates. You read exp as a number.
  • It cannot decode encrypted tokens (JWE). It handles the common signed format only, where the token has three sections.
  • It does not decode the signature section, which is binary data, and it does not strip a Bearer prefix for you.
  • It reads the decoded bytes as plain single-byte characters. Claims that contain non-English letters, such as an accented name, may appear garbled even though the token is fine. In that case the real value is correct inside the token; only the display is affected.

For security decisions, use your server's JWT library and your identity provider's own documentation. This article is general technical information and not a security audit of your system.

#jwt#authentication#base64#debugging#api