JSON Web Tokens (JWTs), defined in RFC 7519, are the dominant mechanism for stateless authentication across APIs and distributed systems. They're lightweight, self-contained, and travel easily in HTTP headers or URLs.
But that simplicity is deceptive. Developers routinely confuse encoding with encryption — and that confusion leads directly to data exposure and broken security models.
This guide covers JWT structure, the security implications of parsing tokens, and how to decode and debug them without leaking credentials.
1. The Anatomy of a JWT: Three Dots, Three Layers
A raw JWT looks like a long string of characters separated by two periods (.). It follows the JSON Web Signature (JWS) specification and consists of three parts: the Header, the Payload, and the Signature. Each segment is individually encoded before being concatenated into the final token.
Base64URL vs. Standard Base64
JWTs do not use standard Base64 (RFC 4648). They use Base64URL encoding. Standard Base64 produces + and / characters, both of which have special meaning in URLs and can break HTTP routing or query parameters.
Base64URL swaps out the problematic characters:
- Index 62 (
+) becomes-(minus). - Index 63 (
/) becomes_(underscore). - Padding characters (
=) are stripped entirely.
This makes the token safe to pass in an Authorization: Bearer <token> header or as a URL query parameter without additional encoding.
Encoded ≠ Encrypted: The Readability Trap
Because a JWT looks like gibberish, developers assume it's secure. It isn't. Base64URL encoding is reversible — it's not encryption. The header and payload are fully readable by anyone who has the token string.
The cryptographic signature at the end guarantees integrity (the data hasn't been tampered with), but provides zero confidentiality. If you need to hide the payload contents, use the JSON Web Encryption (JWE) standard or encrypt the payload before embedding it (e.g., with AES-GCM).
2. RFC 7519 Standard Claims
The payload — the middle segment — carries the actual data. Individual pieces of data are called "claims." RFC 7519 defines three categories: registered, public, and private.
The Seven Registered Claims
These are standardized names to ensure interoperability:
iss(Issuer): The entity that generated and signed the token (e.g., your auth server). Case-sensitive string.sub(Subject): A unique identifier for the user or entity the token describes.aud(Audience): The intended recipient(s). If an API doesn't find itself in theaudvalue, it must reject the token.exp(Expiration Time): The time after which the token is invalid.nbf(Not Before): The time before which the token must not be accepted.iat(Issued At): When the token was created — useful for calculating session age.jti(JWT ID): A unique identifier for the token itself, typically used to prevent replay attacks.
Timestamps: Epoch Seconds and Expiration
Timestamp claims (exp, nbf, iat) are Unix Epoch Seconds — the number of seconds since January 1, 1970 UTC. Note: seconds, not milliseconds (a common source of bugs in JavaScript, where Date.now() returns milliseconds).
When checking expiration, backend systems should account for clock skew — the natural time drift between servers. Most JWT libraries let you configure a "leeway" (typically a few seconds to a few minutes) for the exp check. And because JWTs are stateless, revoking a token before it expires requires maintaining a server-side denylist, usually keyed on the jti claim.
3. Signature Algorithms: Symmetric vs. Asymmetric
The signature is the final segment. It proves the header and payload haven't been altered in transit. The choice of signing algorithm shapes your entire security architecture.
HMAC-SHA256 (HS256) — Symmetric
HS256 uses a single shared secret to both create and verify the signature.
- How it works: The server computes an HMAC-SHA256 hash over the encoded header and payload, using the secret key.
- The tradeoff: Every service that verifies tokens needs a copy of the same secret. If any one of them is compromised and the key leaks, an attacker can forge valid tokens for the entire system.
RSA-SHA256 (RS256) — Asymmetric
RS256 uses a public/private key pair, which separates signing authority from verification.
- How it works: The Identity Provider signs tokens with a private key. Consuming services verify signatures using the corresponding public key.
- Key distribution: Public keys are typically exposed via a JWKS (JSON Web Key Set) endpoint. Even if a verifying service is compromised, the attacker only obtains the public key — useless for forging tokens.
Decoding ≠ Verifying
One of the most dangerous patterns in backend code is using decoded payload data without first verifying the signature.
Decoding is a text transformation. Verification is a cryptographic operation. Without it, your system is blindly trusting data that may have been modified by an attacker. Always validate the signature before acting on the claims.
4. Common Security Pitfalls
Broken JWT implementations cause real breaches. These are the vulnerabilities that show up most often.
The alg:none Attack
Some early JWT parsers trusted the alg field in the header at face value. An attacker could set the header to "alg": "none", strip the signature, and send the modified token. Vulnerable libraries would see none, skip signature verification, and accept a forged payload. Modern libraries prevent this by requiring you to explicitly specify the expected algorithm in your validator configuration — never trust the token's own alg claim.
Storing Secrets in the Payload
Since standard JWTs are encoded (not encrypted), placing API keys, passwords, or PII directly in the payload means any intermediary — proxy, CDN, browser extension — can read them. Store sensitive data behind an opaque reference ID, or use JWE.
The Danger of Online JWT Debuggers
When debugging auth flows, developers habitually paste tokens into random online JWT decoders. This is a real security risk.
Many of these tools send your token to a remote server for processing. If you paste a production token, you're handing a live session credential to a third party. The result can be session hijacking and full account takeover.
Debug Safely: Use a Client-Side Decoder
The safest approach is decoding tokens locally, with no network requests. The RaveTools JWT Decoder runs entirely in your browser — it parses Base64URL and formats the JSON using local JavaScript, with zero server communication. Your tokens and user data never leave your machine.
5. Practical Walkthrough: Dissecting a Test JWT
Here's a test token signed with HS256 using the dummy key "secretkey":
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI
Split by the two periods, you get three Base64URL-encoded segments:
Part 1: Header
- Encoded:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 - Decoded:
{
"alg": "HS256",
"typ": "JWT"
}
This tells the parser to expect an HMAC-SHA256 signature and confirms the token type.
Part 2: Payload
- Encoded:
eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9 - Decoded:
{
"loggedInAs": "admin",
"iat": 1422779638
}
A private claim (loggedInAs) defines user privileges; a registered claim (iat) shows the token was issued at Unix epoch 1422779638 (February 1, 2015).
Part 3: Signature
- Encoded:
gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI
This is the HMAC-SHA256 hash of the header and payload, encoded as Base64URL. Notice the underscore (_)? That's the Base64URL alphabet at work — a / in standard Base64 becomes _ to avoid URL conflicts.
Frequently Asked Questions (FAQ)
Can anyone read my JWT? Yes. Standard JWTs (JWS) are only Base64URL-encoded, not encrypted. Anyone with the token string can decode it and read the header and payload JSON.
Is a JWT encrypted by default? No. JWTs are signed (ensuring integrity) but not encrypted (no confidentiality). To hide the payload data, use the JWE standard.
How do I check if a JWT expired?
Decode the token and look at the exp claim. It contains a Unix timestamp in seconds. If your server's current time exceeds that value, the token is expired.
What is the difference between decoding and verifying a JWT? Decoding is a text transformation (Base64URL → JSON) that requires no key. Verifying is a cryptographic operation that uses a secret key (HS256) or public key (RS256) to prove the token was issued by a trusted authority and hasn't been tampered with.
Why is pasting a production JWT into an online debugger dangerous? Many online debuggers transmit your token to their servers. Pasting a live production token risks exposing active sessions and credentials to third parties, enabling session hijacking. Use a client-side JWT Decoder that processes everything in-browser instead.