Your API returns a 401 and the only clue you have is a token string. It starts with eyJ, continues for about 200 characters, and tells you nothing at a glance. This is a JSON Web Token, and the good news is that most of what it contains is not encrypted. It is encoded, which means you can read it directly.
Understanding what is inside a JWT changes how you debug authentication issues. Instead of guessing whether a token is expired, using the wrong algorithm, or missing a required claim, you read the answer in seconds.
The Three-Part Structure
Every JWT has exactly three sections, separated by dots:
header.payload.signature
The first two sections are Base64URL-encoded JSON. The third is a cryptographic signature. Because Base64URL encoding is reversible without a key, any JWT decoder can show you the header and payload immediately. The signature is a different matter -- verifying it requires knowing the secret or public key used to sign the token.
Splitting a JWT at the dots and decoding each Base64URL-encoded part is the whole job. The JWT Decoder on EvvyTools does this visually and adds expiry countdowns, claim explanations, and signature verification on top.
The Header: Algorithm and Type
The first section decodes to a small JSON object, usually with two fields:
{
"alg": "HS256",
"typ": "JWT"
}
The alg field tells you how the signature was created. Common values:
HS256-- HMAC with SHA-256. Uses a symmetric shared secret. Both the signing service and the verifying service need the same key.RS256-- RSA with SHA-256. Uses a private/public key pair. The issuer signs with the private key; verifiers check with the public key.ES256-- Elliptic Curve Digital Signature Algorithm with SHA-256. More compact signatures than RSA with equivalent security.none-- No signature. Any token claimingalg: noneshould be rejected by a secure implementation immediately.
The algorithm matters during verification. A service configured for RS256 should reject a token claiming HS256, because accepting both creates a known attack vector where an attacker re-signs a modified payload using the public key as if it were a shared secret.
The typ field is always JWT for standard tokens. Some systems use at+JWT for access tokens in OpenID Connect contexts.
The Payload: Claims and What They Mean
The second section is the payload, which contains claims. Claims are key-value pairs with defined meanings. The JWT specification, documented in RFC 7519 via the IETF datatracker, defines seven registered claim names, though most tokens use only a subset.
iss (Issuer): Who created the token. Usually a URL identifying the authorization server. Verifying services often check this against a known list of trusted issuers.
sub (Subject): Who the token is about. In user authentication, this is typically the user's unique identifier in the issuing system.
exp (Expiry): When the token stops being valid. This is a Unix timestamp expressed in seconds since January 1, 1970. A value of 1747353600 is a specific moment in time, not a duration. Converting it to a readable date is where most people reach for a decoder.
iat (Issued At): When the token was created. Also a Unix timestamp. The difference between exp and iat tells you the token's intended lifetime.
aud (Audience): Who the token is intended for. Typically the API or service that should accept it. An audience mismatch is a valid reason to reject a token.
jti (JWT ID): A unique identifier for this specific token. Used for revocation lists and detecting replay attacks.
nbf (Not Before): The token is not valid before this timestamp. Rarely used but worth checking when it appears.
Beyond registered claims, any key-value pair the issuer adds is a custom claim. An authorization system might include roles, permissions, email, tenant_id, or whatever the application needs. None of these standard or custom claim names are encrypted. They are fully readable once the payload is decoded.
Photo by QuinceCreative on Pixabay
Converting the Expiry Timestamp
The exp field is the one developers interact with most often when debugging. Here is how to convert it without a tool:
In JavaScript: new Date(exp * 1000).toISOString()
In Python: datetime.fromtimestamp(exp)
The multiplication by 1000 in JavaScript is because Date expects milliseconds, while the JWT spec uses seconds.
A decoder adds context you cannot get from a raw timestamp. An expiry countdown tells you whether a token is valid right now. A timeline view shows the issued-at, not-before, and expiry as a visual range, which makes it obvious whether a token is about to expire or was issued hours ago. When debugging a production auth failure, that context -- especially whether a token expired two seconds ago or two weeks ago -- usually points directly to whether you are looking at a code bug or a configuration problem.
The Signature: Tamper Detection, Not Encryption
The third section is a Base64URL-encoded signature. Its job is to confirm that the header and payload were not modified after the token was issued.
The signature is computed from the encoded header, a dot, the encoded payload, and the signing key. Change one character in the payload and the signature no longer matches. A verifier that checks the signature will reject the modified token.
What the signature does not do is protect the payload from being read. Anyone with the token string can decode the header and payload. The security of JWTs depends on the fact that the payload cannot be modified without invalidating the signature, not that the payload is secret. If the payload contains sensitive data that must not be readable by third parties, use a JWE (JSON Web Encryption) token rather than a plain JWT.
"When teams hit auth bugs that look mysterious, the first thing I tell them is to paste the token into a decoder. Half the time the answer is right there -- an expired timestamp or a missing audience claim. The other half, the token is fine and the bug is elsewhere, which you also learn immediately." - Dennis Traina, founder of 137Foundry
Verifying a Signature
Decoding a token and verifying it are two different operations. Decoding reads the payload. Verification confirms the token was issued by the expected party and was not tampered with in transit.
To verify a signature, you need the algorithm (from the header) and the signing key (a shared secret for HMAC algorithms, a public key for RSA and EC algorithms). Most JWT libraries handle this automatically when you pass the key at initialization.
The OWASP Top 10 includes guidance on authentication weaknesses, and JWT misconfiguration is a recurring source of vulnerabilities. Accepting unsigned tokens, trusting the alg header without restriction, and failing to verify iss and aud claims are the three most common mistakes. Tools that show token contents without requiring a key are for inspection and debugging only. They are not substitutes for proper signature verification in application code.
Token Comparison and Debugging Workflows
When you have two tokens from the same user and want to know what changed between them, a comparison tool is faster than reading two payloads in parallel. Typical differences between consecutive tokens include a new iat, an updated exp, a changed jti, and occasionally updated role or scope claims.
Comparison is particularly useful after a token refresh to confirm the refresh actually extended the expiry and did not return a cached copy of the original token.
Common debugging scenarios and what to look for in the decoded token:
401 on a request that worked yesterday: Check exp. The token probably expired.
Token accepted in staging but rejected in production: Check aud and iss. Staging and production environments typically have different values for both.
Auth failure with no clear error message: Check nbf. If it is set to a future timestamp, the token is not yet valid.
Intermittent auth failures across distributed services: Check iat and exp against current server time. Clock drift between servers causes tokens that should verify to fail, and the failures are inconsistent because they depend on which server handles the request.
Photo by Markus Spiske on Pexels
Using a JWT Decoder in Practice
The JWT Decoder on EvvyTools breaks a token into its three sections and shows decoded JSON for the header and payload alongside plain-English explanations of each claim. The expiry countdown updates in real time, so you can see immediately whether a token is valid at the moment you paste it. Signature verification is supported for tokens where you have the signing key available.
The tool also handles side-by-side token comparison: paste two tokens and differences in claims are highlighted directly.
The jwt.io reference and the OpenID Connect specification are the definitive technical references for token structure and how JWTs are used in identity protocols. For the full range of developer utilities -- from DNS record readers to UUID generators -- the EvvyTools developer tools collection has them in one place.
Authentication failures are rarely caused by something exotic. In most cases the token is expired, the audience does not match, or the algorithm expectation is wrong. Reading the token directly is the fastest way to rule all three out. The EvvyTools blog covers other developer tools in the same vein if you are looking for more utilities in this category.
Photo by
Photo by