What is inside a JWT, and why decoding is not verifying
A JSON Web Token (JWT) is three pieces of text joined by dots: a header, a payload and a signature. The first two are only encoded, not encrypted, so anyone who holds the token can read them. The signature lets a server check that a trusted party issued the token and that nobody changed it. Decoding a token tells you what it says. Only verifying it tells you whether to believe it.
JWT decoderToken expired, or is it valid?Do it freeThe three parts
A token looks like header.payload.signature. The first part of the well-known example token from jwt.io is:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Each of the first two parts is JSON converted to base64url, a form of text that is safe in URLs. The first part decodes to:
{"alg":"HS256","typ":"JWT"}
The payload of the same example decodes to:
{"sub":"1234567890","name":"John Doe","iat":1516239022}
The payload holds the claims, which are statements about the user or the session. The signature is computed from the first two parts and a key, so changing one character makes it stop matching. Base64url needs no key to reverse, so never put a password, a private key or personal data in a payload.
Registered claims
RFC 7519, the standard for JWTs, reserves a few short claim names:
iss(issuer): who created the token.sub(subject): who the token is about, usually a user ID.aud(audience): the service the token is meant for.exp(expiration time): the moment after which the token is invalid.nbf(not before): the earliest moment the token is valid.iat(issued at): when the token was created.
Times are seconds since 1 January 1970 UTC, called Unix time. The example has no exp, so under the standard it never expires. A real token should have one. Other names, such as role or email, belong to the application.
Decoding is not verifying
Decoding splits the token at the dots, reverses the base64url and reads the JSON. It shows what the token claims, and it does the same for a forged token, because anyone can write any claims with any signature.
Verifying recomputes the signature with the right key and checks that it matches. It also checks that exp and nbf bracket the current time, and that iss and aud are the values the service expects. An app that reads a role claim without verifying trusts whoever wrote the token. That is a common mistake.
HS256, RS256 and ES256
The alg value names the method. Three are common:
- HS256 is HMAC with SHA-256. One shared secret both signs and verifies, so the secret must stay on the server that issues tokens.
- RS256 is an RSA signature with SHA-256. The issuer signs with a private key, and anyone with the public key can verify. Other services can check tokens without being able to create them.
- ES256 is an ECDSA signature with the P-256 curve and SHA-256. It works like RS256 with smaller keys and signatures.
Use HS256 when one service both issues and checks its tokens. Use RS256 or ES256 when other services must check tokens they did not issue.
Problems that come from trusting the header
The header is written by whoever sends the token, so the verifier must not let it choose the rules.
alg: none. A token with no signature can declare"alg":"none". Some libraries once accepted such tokens and skipped the check. Set the allowed algorithms in your own code, never take them from the token.- Algorithm confusion. A server expects RS256 and holds a public key. An attacker sends an HS256 token signed with that public key as the HMAC secret. A verifier that accepts whatever algorithm the header names accepts it. Bind each key to exactly one algorithm.
- Weak secrets. The attacker has the header, payload and signature, so they can test candidate secrets offline. A short secret or a common word falls quickly to such a list. RFC 7518 says an HS256 key should be at least 256 bits, and random.
Pasting a production token into a website
A valid, unexpired token works for whoever holds it, until it expires. Treat it like a password. Before you paste one into a site, find out whether the site sends it to a server. A tool that runs entirely in the browser tab has no need to. You can check any page by watching its network requests in the browser's developer tools.
Tokens usually cannot be revoked before they expire unless the server keeps a list. If a live token leaks, ask the service to revoke the session or rotate its keys. Use a test-environment token for debugging where you can. The JWT decoder reads the three parts in your browser tab, and nothing you paste is sent anywhere.
Webhooks: the same idea with a shared secret
Most webhook providers send an HMAC of the request body, computed with a secret you share with them, in a header. Your receiver computes the same HMAC and compares the two. Common failures are small. The JSON is re-formatted before hashing, so the bytes change. The test secret is used against live traffic. Or the signatures are compared with ordinary string equality, which can leak timing information. Use a constant-time comparison.
The webhook signature tester computes the signature from the secret and body you paste, for GitHub, Stripe, Slack, Shopify or any HMAC scheme, so you can see whether the two values match.
Quick answers
Is a JWT encrypted?
No. A standard JWT is signed, and anyone can read its header and payload. Only the signature protects it from changes.
What happens if I edit the payload?
The signature stops matching, so a verifying server rejects the token. Reading the claims does not check the signature, so the decoder still shows your edited version.
Comments
No comments yet. Be the first.


