A JSON Web Token (JWT) is the long string that follows Bearer in an Authorization header. It looks like random characters, but it is just three pieces of Base64 text joined by dots, and two of them are readable JSON. Decoding one is the quickest way to debug “why am I getting 401?” problems.
Anatomy of a token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxNyIsIm5hbWUiOiJBZGEiLCJleHAiOjE3NjAwMDAwMDB9.signature...
A JWT has three parts, separated by dots:
- Header. Says how the token is signed, for example
{"alg":"HS256","typ":"JWT"}. - Payload. The claims: facts about the user and the token, for example
{"sub":"17","name":"Ada","exp":1760000000}. - Signature. A cryptographic value the server uses to detect tampering.
The header and payload are Base64URL-encoded JSON. That is encoding, not encryption: anyone holding the token can read them.
Decode a token
- Copy the token. In Postman, it is in the Authorization tab or the
Authorizationheader; in a browser, check the Network tab or the application’s storage. - Paste it into the JWT Decoder.
- Read the header and payload as formatted JSON.
Decoding runs in your browser, which matters here: a live token is a credential. A decoder that uploads it to a server asks you to hand over access to the account.
Standard claims
| Claim | Name | Meaning |
|---|---|---|
iss |
Issuer | Who created the token |
sub |
Subject | Who it is about, usually a user ID |
aud |
Audience | Who it is intended for |
exp |
Expiration | When it stops being valid (Unix seconds) |
nbf |
Not before | When it starts being valid |
iat |
Issued at | When it was created |
jti |
JWT ID | A unique identifier for the token |
Applications add their own, such as roles, email, scope or tenant_id.
Time claims
exp, nbf and iat are Unix timestamps in seconds, so 1760000000 is a moment in October 2025. Two common mistakes: treating them as milliseconds (the result is a date in the year 55,000), and forgetting clock skew between servers. The decoder converts them to readable dates and tells you whether the token has expired.
Debugging checklist
When a request fails with 401 or 403, decode the token and check:
- Expired? Compare
expto the current time. This is the most common cause. - Not yet valid? Check
nbfandiat. A server clock that is slightly behind can reject a brand new token. - Right audience?
audmust match the API you are calling. - Right issuer?
issmust be the identity provider the API trusts. Staging tokens sent to production fail here. - Missing scope or role? A 403 usually means the token is valid but lacks a permission. Look at
scope,scporroles. - Right algorithm? The
algin the header must match what the server expects.
Decoding is not verifying
This is the most important point about JWTs. Reading a payload tells you what the token claims. It does not prove the token is genuine. Anyone can write a token with "role":"admin" and any signature at all. Only verifying the signature with the right key shows the token was issued by someone who holds that key and has not been altered.
Never trust claims from an unverified token in a security decision. A server must verify the signature, and also check exp, iss and aud, before acting on a token.
Verifying a signature
How you verify depends on the algorithm in the header.
HS256, HS384, HS512 use a shared secret (HMAC). Anyone who has the secret can both create and verify tokens. You can verify these in the decoder by entering the secret, and it stays in your browser. This is useful for local development and test tokens.
RS256, ES256 and similar use a public/private key pair. The provider signs with a private key and publishes a public key, usually at a JWKS URL such as https://issuer.example.com/.well-known/jwks.json. Verifying needs that public key, and is normally done in your server code with a library.
alg: none means “unsigned”. A server must never accept it. If you see it in a token meant for a real API, something is wrong.
Security notes
- Do not put secrets in a JWT payload. It is readable by anyone who gets the token, including in logs and browser storage.
- Treat tokens as passwords. Do not paste live production tokens into tools you do not trust, or into tickets and chat. Use short-lived tokens and revoke if one leaks.
- Keep expiry short and use refresh tokens for longer sessions.
- Verify on the server, always. Client-side decoding is for display only.
Tokens that are not JWTs
Not every bearer token is a JWT. Opaque tokens, such as random strings or GitHub personal access tokens, have no readable contents, and the decoder will report them as invalid. They can only be checked by the server that issued them.
Working with the payload
The payload is JSON, so everything else on this site applies. Paste it into the JSON Formatter to pretty-print it. Diff two tokens from different environments to find what differs. If the token arrives as part of a cURL command or a Postman collection, pull it from the Authorization header first.