Understand JWT structure, claims, and how to debug authentication issues
Your API returns a 401. You grab the JWT token from the request header, stare at the long string of characters, and wonder: is the token expired? Is the audience wrong? Is the payload even what you expect? Decoding a JWT is the fastest way to debug authentication problems, and you do not need any special tools to do it — every JWT payload is just Base64-encoded JSON. This guide explains JWT structure, walks through every standard claim, and shows you how to diagnose the most common auth failures.
A JWT (JSON Web Token) consists of three parts separated by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE3MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Each part is a URL-safe Base64-encoded string. Let us break them down.
The header is a JSON object that describes how the token is signed. Decoding the first part of the example above gives:
{"alg": "HS256", "typ": "JWT"}
The most common header fields are:
alg — The signing algorithm. HS256 (HMAC-SHA256) uses a shared secret. RS256 (RSA-SHA256) uses a public/private key pair. ES256 (ECDSA) uses elliptic curve cryptography. The none algorithm means the token is unsigned — servers should reject this in production.typ — Token type, almost always JWT.kid — Key ID. When a server uses multiple signing keys (common in key rotation), the kid tells the verifier which key was used. The verifier looks up the matching key from a JWKS (JSON Web Key Set) endpoint.The payload contains the claims — statements about the user and metadata. Decoding the second part gives:
{"sub": "1234567890", "name": "John", "iat": 1716239022}
Claims are the actual data your application uses for authorization decisions. They fall into three categories: registered (standard), public (collision-resistant custom claims), and private (application-specific claims agreed upon by parties).
The signature is created by taking the encoded header, the encoded payload, and signing them with the algorithm specified in the header using a secret key. For HS256:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
The signature ensures the token has not been tampered with. If someone changes any character in the header or payload, the signature will not match when verified. However, the signature does not encrypt the data — the header and payload are always readable by anyone who has the token.
The JWT specification (RFC 7519) defines seven registered claims. Understanding these is essential for debugging auth issues.
| Claim | Full Name | Purpose | Example |
|---|---|---|---|
iss | Issuer | Who created and signed the token | "https://auth.example.com" |
sub | Subject | The user or entity the token represents | "user-12345" |
aud | Audience | The intended recipient (your API) | "https://api.example.com" |
exp | Expiration Time | When the token becomes invalid (Unix timestamp) | 1716325422 |
nbf | Not Before | Token is not valid before this time | 1716239022 |
iat | Issued At | When the token was created | 1716239022 |
jti | JWT ID | Unique identifier to prevent token replay | "a1b2c3d4-e5f6" |
The exp and iat values are Unix timestamps (seconds since January 1, 1970). To read them, convert to a human-readable date. For example, 1716239022 is May 21, 2024 at 00:03:42 UTC. When debugging, always convert these timestamps to verify the token is not expired.
The aud claim is one of the most common sources of auth failures. If your API expects aud to be "https://api.example.com" but the token contains "https://api.example.com/" (trailing slash), the validation will fail even though the token is otherwise valid.
This distinction trips up many developers and has security implications.
Decoding means reading the header and payload by Base64-decoding them. Anyone can do this — no secret key required. The header and payload are not encrypted, just encoded. Decoding a JWT with our Base64 tool or any JWT decoder shows you the full contents.
Verifying means checking that the signature is valid — that the token was created by a trusted issuer and has not been modified. Verification requires the secret key (for HS256) or the public key (for RS256). Only the server should verify tokens.
This means you should never put sensitive information in a JWT payload. Passwords, credit card numbers, social security numbers, or any secret data should never appear in claims. Anyone with the token can read them. JWTs are designed for identity claims and authorization metadata, not secret data.
To quickly inspect a JWT's contents, split the token at the dots, take the first two parts, and Base64-decode each one. You will get the header and payload as readable JSON.
When your API returns a 401 or 403 and you suspect a JWT problem, decode the token first. Here are the most common issues and how to identify them.
Decode the payload and check the exp claim. Convert the Unix timestamp to a date and compare it to the current time. If exp is in the past, the token is expired. The fix: obtain a new token using your refresh token flow, or re-authenticate.
Check the aud claim against what your API expects. Common mismatches include trailing slashes, HTTP vs HTTPS, different subdomains, or different casing. The audience must match exactly — most JWT libraries do a strict string comparison.
If a token appears to expire seconds before it should, the server and token issuer clocks may be slightly out of sync. Most JWT libraries accept a small clock skew tolerance (typically 30-60 seconds). If you are seeing intermittent expiration errors, clock skew is a likely culprit. Ensure your servers use NTP for time synchronization.
The server expects RS256 but the token was signed with HS256, or vice versa. Decode the header and check the alg field. This happens when different environments (dev, staging, production) use different signing configurations. It can also be a security attack — the "algorithm confusion" attack tricks an RS256 server into accepting HS256 tokens signed with the public key. Always validate the algorithm on the server side.
Some APIs require specific custom claims (like roles, permissions, or scope). If a token is valid but authorization fails, decode the payload and verify that all required claims are present and have the expected values.
If the nbf (not before) claim is set to a future time, the token will be rejected until that time arrives. This is less common but can happen with pre-issued tokens or clock skew issues.
JWTs are a powerful authentication mechanism, but they require careful handling to remain secure.
iss, aud, exp, and alg on every request. Reject tokens with unexpected values.none algorithm. The "alg": "none" header means the token has no signature. Some libraries accept this by default, which lets attackers forge tokens. Explicitly disallow none in your JWT validation configuration.jti) or use short-lived tokens with refresh token rotation.JWTs are not the only authentication method. Here is how they compare:
| Feature | JWT | Session Cookie | API Key |
|---|---|---|---|
| Statefulness | Stateless | Stateful (server stores session) | Stateless |
| Scalability | Excellent (no session store) | Requires shared session store | Excellent |
| Revocation | Difficult (need blocklist) | Easy (delete session) | Easy (delete key) |
| Cross-domain | Yes (sent in headers) | Limited (SameSite rules) | Yes |
| Payload data | Yes (claims in token) | No (data on server) | No |
| XSS vulnerability | If stored in localStorage | Protected by HttpOnly | If exposed in client code |
| CSRF vulnerability | Not vulnerable (header-based) | Vulnerable (needs CSRF token) | Not vulnerable |
| Token size | Large (1-2 KB typical) | Small (session ID only) | Small (key string) |
| Best for | Microservices, SPAs, mobile | Traditional web apps | Server-to-server, third-party |
Use JWTs when you need stateless authentication across multiple services, mobile app authentication, or single-page application auth. Use session cookies for traditional server-rendered web applications where you control the entire stack and need easy revocation. Use API keys for server-to-server communication and third-party integrations where you need simple, revocable access control.
Decode a token now: Paste your JWT into our Base64 Decoder to inspect the header and payload instantly. Use our JSON Formatter to pretty-print the decoded claims for easy reading. For verifying token signatures and generating hashes, try our Hash Generator.
exp (expiration) claim is in the past, the server should reject it with a 401 Unauthorized response. The client must then obtain a new token, typically by using a refresh token or re-authenticating. Expired tokens cannot be renewed — they must be replaced.← Blog index | Base64 Decoder | JSON Formatter | Hash Generator | All tools