What Is a JWT and How Do You Decode One Safely?
If you have ever built or worked with a web application that has user authentication, you have almost certainly encountered a JWT. You might have seen one in a browser's local storage, in an Authorization header, or in a network request. They look like a long random string of characters split into three parts by dots, and at first glance they are completely unreadable.
This guide explains what a JWT actually is, what is stored inside it, why it is used, and how to decode one to inspect its contents — without needing to install a library or write any code.
What Does JWT Stand For?
JWT stands for JSON Web Token. It is an open standard (defined in RFC 7519) for securely transmitting information between two parties as a compact, self-contained string. The information inside a JWT is encoded — not encrypted — which is an important distinction we will come back to.
JWTs are most commonly used for authentication and authorization. When a user logs in to a web application, the server creates a JWT and sends it to the client. The client stores it (usually in local storage or a cookie) and includes it in every subsequent request. The server reads the token, verifies it has not been tampered with, and uses the information inside it to identify who the user is and what they are allowed to do.
The Three Parts of a JWT
A JWT consists of exactly three parts, separated by dots (.):
header.payload.signature
Here is what a real JWT looks like:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Each part is Base64URL-encoded, which means it uses a slightly modified version of Base64 that is safe to include in URLs without any additional encoding.
Part 1 — The Header
The header contains metadata about the token itself. When decoded, it is a small JSON object that typically looks like this:
{
"alg": "HS256",
"typ": "JWT"
}
algspecifies the algorithm used to sign the token — in this case HS256 (HMAC with SHA-256)typsimply identifies this as a JWT
Part 2 — The Payload
The payload is the most interesting part. It contains the actual data being transmitted, called claims. Claims are statements about an entity — usually the authenticated user — plus additional metadata. A decoded payload might look like:
{
"sub": "1234567890",
"name": "John Doe",
"email": "john@example.com",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}
Some of these fields are registered claims — standardised fields defined in the JWT specification:
sub(subject) — the user ID or entity the token refers toiat(issued at) — the Unix timestamp of when the token was createdexp(expiration) — the Unix timestamp after which the token is no longer validiss(issuer) — who created the token (usually the server's domain)aud(audience) — who the token is intended for
The rest (name, email, role) are private claims — custom fields added by the application developer to carry whatever information the application needs.
Part 3 — The Signature
The signature is what makes a JWT secure. It is calculated by taking the encoded header, the encoded payload, a secret key (known only to the server), and the algorithm specified in the header, then hashing them together.
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret_key
)
When the server receives a token, it re-calculates this signature using its own secret key. If the result matches the signature in the token, it knows the payload has not been tampered with. If even one character in the payload was changed after the token was issued, the signature will not match and the server will reject the token.
How to Decode a JWT Without a Library
Because the header and payload are Base64URL-encoded (not encrypted), you can decode them without knowing the secret key. This is useful for debugging — for example, checking what user ID is embedded in a token, or verifying that an expiration date is set correctly.
The easiest way to do this is with the JWT Decoder tool. Paste the full token string into the input, and it will immediately show you the decoded header and payload as formatted JSON. You will also see the expiration time converted to a human-readable date so you can tell at a glance whether the token is still valid.
To decode manually without a tool: split the token at the dots, take the second part (the payload), replace any - with + and _ with / to convert from Base64URL to standard Base64, then decode it. In JavaScript, this is atob(payload.replace(/-/g, '+').replace(/_/g, '/')).
Is It Safe to Decode a JWT?
Decoding the payload of a JWT reveals the information stored inside it — which is why you should never store sensitive data like passwords, credit card numbers, or private keys in a JWT payload. The payload is readable by anyone who has the token, just as anyone can read the address written on a sealed envelope.
What the signature protects is integrity — it proves the payload has not been modified since the server issued it. It does not protect confidentiality — it does not hide what is written in the payload.
If you need to store genuinely sensitive data inside a token, you would use a JWE (JSON Web Encryption) instead, which encrypts the payload. Standard JWTs use JWS (JSON Web Signature), which only signs.
Common JWT Debugging Scenarios
Token is being rejected by the server — Decode the payload and check the exp field. If the current time is past the expiration timestamp, the token has expired and the user needs to log in again.
User does not have the right permissions — Decode the payload and look at the role or permissions claim. If the value is wrong, the issue is in how the token was created, not in how it is being checked.
Token looks malformed — Check that the token has exactly two dots. If it has more or fewer, it is not a valid JWT. Also check that it was not accidentally URL-encoded (if it contains %2E instead of ., it needs to be decoded first).
"Invalid signature" error — This usually means the token was issued with a different secret key than the one the server is currently using, or the payload was modified after issuance. Decoding the payload can help confirm whether the data looks correct.
Conclusion
JWTs are a compact, portable way to transmit user identity and claims between a client and a server, and understanding their structure makes debugging authentication issues significantly easier. The next time you see a long dotted string in a network request or browser storage, paste it into the JWT Decoder to immediately see what it contains — the header algorithm, the user claims, and the exact expiration time — all without needing to write a single line of code.