Skip to main content
AllDevToolsHub
2026-05-23
Last reviewed: Aug 2026
SECURITY
Est Read: 14_MIN

JWT Tokens Explained: Decode, Verify & Common Mistakes (2026 Guide)

JWT Tokens Explained: Decode, Verify & Common Mistakes (2026 Guide)
Processing_Node: 01

#1JWT tokens explained: decode, verify, and avoid common mistakes

JWTs are easy to misuse because the format looks simple while the security rules around it are not.

The practical parts are how the three sections fit together, how verification should work, and which mistakes still break production systems.

#2The Three Parts of a JWT

A JWT looks like this on the wire:

protocol
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjJ4LXAifQ.eyJzdWIiOiJ1c
zJfYWJjMTIzIiwiaXNzIjoiaHR0cHM6Ly9pZHAuZXhhbXBsZS5jb20iLCJhdWQiOiJ
hcGkuZXhhbXBsZS5jb20iLCJleHAiOjE3MjU0NDc1NDksImlhdCI6MTcyNTQ0Mzk0OX
0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Two dots, three sections. Split on the dots and base64url-decode each side:

#31. Header, how this token was signed

json
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "2x-p"
}
  • alg, the signing algorithm. The values you will actually see in 2026: HS256, HS384, HS512 (HMAC with SHA), RS256, RS384, RS512 (RSA-PKCS1-v1_5), PS256, PS384, PS512 (RSA-PSS), ES256, ES384, ES512 (ECDSA), EdDSA (Ed25519).
  • typ, almost always JWT. Sometimes omitted.
  • kid, key ID, an opaque string telling the verifier which key out of a set to use. Critical when the issuer rotates keys (every OIDC provider does).

#32. Payload, the claims

json
{
  "iss": "https://idp.example.com",
  "sub": "us2_abc123",
  "aud": "api.example.com",
  "exp": 1725447549,
  "iat": 1725443949,
  "scope": "read:projects write:projects"
}

The payload is just JSON. It is not encrypted. Anyone who sees the token can base64url-decode this section and read it. The full registered-claim list, iss, sub, aud, exp, nbf, iat, jti, is RFC 7519. Most real tokens add many more from OpenID Connect, OAuth 2.0, and your own application. The JWT Claims Reference is the full menu, with examples and notes.

#33. Signature, the cryptographic seal

The signature is computed over base64url(header).base64url(payload) using the algorithm in alg and a key. The kind of key depends on the algorithm:

  • HMAC family (HS)*, symmetric. Same shared secret used to sign and verify. Anyone with the secret can forge tokens.
  • RSA family (RS, PS)**, asymmetric. Private key signs; public key verifies. Public key can be published, e.g. in a JWKS endpoint.
  • ECDSA family (ES)*, asymmetric, elliptic curve. Shorter signatures than RSA at equivalent strength.
  • EdDSA (Ed25519), modern asymmetric, deterministic, fast. The recommended default for new systems if your stack supports it.

Verification means recomputing the signature over the received header + payload and checking byte-for-byte equality. If it matches, the header and payload are unmodified since signing.

#2How a JWT Travels, Sign → Send → Verify

protocol
+--------+                       +----------+                      +----------+
|        |  1. login            |          |                      |          |
|        |--------------------->|          |                      |          |
|        |                       |          |                      |          |
|        |  2. JWT (signed)      |   IdP    |                      |          |
| Client |<----------------------|          |                      |          |
|        |                       +----------+                      |          |
|        |                                                          |   API    |
|        |  3. Authorization: Bearer <jwt>                          |  Server  |
|        |--------------------------------------------------------->|          |
|        |                                                          |          |
|        |  4. data / 401 / 403                                     |          |
|        |<---------------------------------------------------------|          |
+--------+                                                          +----------+
                                                                    verifies sig
                                                                    using public
                                                                    key (or shared
                                                                    secret) at
                                                                    each request

Three things to take away from this diagram:

  1. The IdP issues, your API verifies. With asymmetric algorithms (RS256, ES256, EdDSA), the API only needs the public key, usually fetched from a JWKS endpoint like https://idp.example.com/.well-known/jwks.json. The private key never leaves the IdP.
  2. The Authorization header pattern is universal. Authorization: Bearer <token>, every framework's middleware expects this. See the HTTP Headers Reference for the surrounding context.
  3. Verification happens on every request. JWTs are stateless, that is their selling point. The API does not call back to the IdP per request; it just verifies the signature locally.

#2The Claims You Will Actually See

The seven registered claims (RFC 7519), every well-behaved JWT has most of these:

ClaimNameTypeWhat it does
issIssuerstring/URIWho minted the token. Verify it matches your IdP exactly.
subSubjectstringWho the token is about (the user ID, machine ID, etc.). Stable per user.
audAudiencestring or arrayWho the token is for. Verify your service is in this list.
expExpirationNumericDate (sec since epoch)When the token stops being valid. Must be in the future.
nbfNot BeforeNumericDateWhen the token starts being valid. Defends against clock skew abuse.
iatIssued AtNumericDateWhen the token was minted.
jtiJWT IDstringUnique ID for the token. Enables one-time-use tokens and revocation lists.

Beyond those, you will see OpenID Connect claims (name, email, email_verified, preferred_username, nonce, auth_time, acr, amr, azp), OAuth 2.0 access-token claims (scope, client_id, groups, roles, entitlements), and your own custom claims (always namespace these with a URL prefix to avoid collisions, e.g. "https://yourapp.com/tenant_id": "acme").

The full table with examples is in the JWT Claims Reference.

#2Decoding a JWT Safely

There is exactly one safe place to paste a production JWT: a tool that does the work entirely in your browser, with the network panel showing zero outbound requests carrying the token. This is the AllDevToolsHub JWT Decoder.

The flow:

  1. Paste the JWT.
  2. The header, payload, and signature appear instantly, decoded but unverified.
  3. (Optional) Paste the HMAC secret or public key to verify the signature locally.
  4. The decoder flags expired tokens, mismatched aud, weak algorithms, and missing claims.

For language-specific verification code, the JWT Decoder has companion guides for the four most-asked stacks:

Never paste a production token into a third-party online decoder you do not trust. Many popular "JWT debugger" sites silently log the tokens they receive. Treat a JWT like a password: it grants whatever access the bearer is allowed to do.

#2How to Verify a JWT Correctly

A correct verification, in order:

  1. Parse the header without trusting it. Read alg and kid, but treat them as untrusted hints.
  2. Reject alg: "none" and any algorithm you do not expect. Maintain an allow-list, not a deny-list.
  3. Pick the verification key. For asymmetric keys, fetch the JWKS, pick the entry matching kid. Cache JWKS, but invalidate on kid miss.
  4. Verify the signature using the algorithm you pinned, not the one in the header.
  5. Check exp is in the future, with a small leeway (≤60 seconds) for clock skew.
  6. Check nbf is in the past, same leeway.
  7. Check iss matches your trusted issuer string exactly.
  8. Check aud contains your service identifier. If aud is missing, reject, never default to "accept."
  9. Check any scope / role claims as your authorization layer requires.

Most libraries do steps 4–8 with a single call when you pass options correctly. The danger is when you skip steps 7–8 because they "felt optional", see the audience-confusion attack below.

A Node.js example using jose:

javascript
import { jwtVerify, createRemoteJWKSet } from "jose";

const JWKS = createRemoteJWKSet(new URL("https://idp.example.com/.well-known/jwks.json"));

const { payload, protectedHeader } = await jwtVerify(token, JWKS, {
  issuer: "https://idp.example.com",
  audience: "api.example.com",
  algorithms: ["RS256", "ES256"],
  clockTolerance: 30,
});

The algorithms allow-list is the single most important parameter, it defends against the alg=none and key-confusion attacks at the same time.

#2The Eight JWT Vulnerabilities That Still Bite in 2026

#31. alg: "none" accepted

Libraries that interpret alg: "none" as "no signature required" accept any token with no signature at all. Fixed in every modern library by default, but check yours, and always pass an explicit algorithms: ["RS256"] allow-list.

#32. Weak HMAC secrets

HS256 with a five-character shared secret is brute-forceable in seconds with hashcat. If you use HMAC, the secret must be at least 32 random bytes (256 bits) and rotated periodically. Better: switch to RS256 / ES256 / EdDSA so the verifier never holds signing material.

#33. Key confusion (HS256 vs RS256)

A historic bug: a server expecting RS256 but accepting any algorithm. An attacker takes the public RSA key (which is public!) and signs a forged token with HS256 using the public-key bytes as the HMAC secret. If the library uses the public key as both the RSA verification key and the HMAC secret, it accepts the forgery. Defence: lock algorithms to a single asymmetric algorithm at verify time.

#34. kid injection / path traversal

If kid is used as a file path or SQL lookup without validation, an attacker can supply kid: "../../../etc/passwd" or kid: "x' UNION SELECT ...". Defence: treat kid as untrusted input, validate against an allow-list of known IDs, never concatenate into a path or query.

#35. Missing aud validation

A token issued for service A is replayed against service B. If service B does not check aud, it accepts a token never intended for it. Defence: every service has a unique audience string and verifies its own audience on every token. See the JWT Claims Reference for the aud validation rules.

#36. Sensitive data in the payload

Passwords, full PII, internal IDs you never want public. The payload is base64url-encoded, not encrypted. Defence: put references in the payload (sub: "us2_abc123"), keep the actual data in your database, and use JWE (JSON Web Encryption) only when you genuinely need an opaque-to-the-bearer token. JWE is rare; the right answer is almost always "do not put it in the JWT."

#37. Long-lived tokens with no revocation

A JWT does not phone home. If you set exp a year in the future and the user changes their password, the old token still works for a year. Defence: short-lived access tokens (5–15 minutes) plus rotating refresh tokens stored in HttpOnly; Secure; SameSite=Strict cookies, and a jti-based revocation list for emergency invalidation.

#38. JWT in the URL / referer leak

Putting a JWT in a query string (?token=...) leaks it into access logs, browser history, and Referer headers when the page links elsewhere. Defence: carry tokens only in the Authorization header or in cookies. The narrow exception, a one-time, short-lived ticket consumed on first use, is fine because exposure is bounded.

#2The Authorization Header Pattern

This is the canonical way a JWT travels on every request after login:

http
GET /api/projects HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Accept: application/json

A few notes from production:

  • Never log Authorization headers. Strip them in your logging middleware, your error tracker, and your APM tool. A single leaked log line with a still-valid token is a security incident.
  • Pair with Cache-Control: no-store on responses to authenticated endpoints, so intermediate caches do not store responses that depend on identity.
  • CORS preflight will not carry the header, only the actual request. Add Authorization to Access-Control-Allow-Headers in your preflight response.

The full surrounding HTTP context, WWW-Authenticate challenges, cookie attributes, CORS preflights, is in the HTTP Headers Reference.

#2Auditing a Token Without Leaking It

Most "paste your JWT here" websites decode server-side. That means a token you are debugging, one that is very often still valid, lands in someone else's request logs. For a production access token that is a credential disclosure, not a debugging step. Decode locally instead: jwt-cli, a two-line atob() in the browser console, or a tool that does the work in client-side JavaScript and never makes a network request (open DevTools → Network and confirm it stays empty).

Once you can read the token safely, run this audit:

  1. Algorithm. Header alg matches what your verifier pins. Not none. Not a symmetric algorithm where you expect asymmetric.
  2. Signature. Verifies against the current key. If you rotate keys, the kid resolves to a key you still publish.
  3. Lifetime. exp is present and short (minutes to an hour for access tokens). iat is not in the future. nbf, if present, has passed.
  4. Audience and issuer. aud is your service. iss is the identity provider you trust.
  5. Payload contents. No passwords, no full PII, no secrets, the payload is base64, not encryption.
  6. Transport and storage. Sent in the Authorization header, not the URL. Stored where your threat model allows, and cleared on logout.

Anything that fails step 1 or 2 is an untrusted token. Stop and treat it as hostile input.

#2When Not to Use a JWT

JWTs are not free; they have real downsides for some use cases:

  • You need instant revocation. JWTs are stateless and only revoke at exp. Either accept the lag, or pair with a revocation list (which gives up the stateless win).
  • The session is long and the data is sensitive. A 24-hour JWT in a browser is a 24-hour window of risk. Opaque session IDs in a secure cookie + server-side store can be safer.
  • The token grows unbounded. Every claim you add inflates every request. If your token is approaching 8 KB, you have overstuffed it; move data back to the database and keep the JWT lean.

Server-side opaque sessions remain the right answer for many web apps; JWTs shine for cross-service auth, mobile clients, and stateless API gateways.

#2Frequently Asked Questions

#3What is a JWT in simple terms?

A JWT is a small, URL-safe string that wraps a JSON object of facts about a user or client (their ID, what they can do, when the token expires) together with a cryptographic signature that proves the facts were not modified after signing. Servers issue JWTs at login; clients send them on every subsequent request. Anyone who holds the token gets whatever access the token grants, so JWTs are treated like passwords. The payload is encoded, not encrypted; the security comes from the signature, not from hiding the contents.

#3What is the difference between a JWT and an OAuth access token?

A JWT is a format, three base64url-encoded parts with a signature. An OAuth access token is a role, a credential that grants access to an API on behalf of a user. They are often the same thing (modern OAuth 2.0 access tokens are usually JWTs following RFC 9068), but they do not have to be: opaque OAuth access tokens that look like random strings are also valid. When you see a token starting with eyJ, that is a JWT regardless of what role it is playing, an OAuth access token, an OpenID Connect ID token, an internal service token, etc.

#3How do I decode a JWT safely?

Use a tool that runs entirely in your browser, with no token data ever sent over the network. The AllDevToolsHub JWT Decoder shows the header, payload, and signature instantly, flags expired tokens and weak algorithms, and lets you paste a key to verify the signature locally. Avoid third-party "online JWT debuggers" that you do not trust, many silently log the tokens they receive, which is identical to handing over the user session.

#3Is HS256 secure in 2026?

Yes, if the shared secret is at least 32 random bytes and is rotated periodically. But HS256 has an architectural downside: the verifier needs the same secret as the signer, so every service that verifies tokens can also forge them. For systems with more than one verifier, switch to RS256, ES256, or EdDSA so the private key stays at the IdP and verifiers only hold public keys. HS256 is fine for single-process internal use; RS256/ES256/EdDSA is the right default for anything that crosses a service boundary.

#3What happens if I do not check the aud claim?

You become vulnerable to the audience-confusion attack: a token legitimately issued by your IdP for a different service is replayed against yours, and you accept it because the signature is valid and the issuer is right. This is one of the most common JWT bugs in real-world bug-bounty reports. The fix is one line: pass audience: "your-service-id" to your JWT verifier and let the library reject mismatches. See the JWT Claims Reference for the full aud semantics, it can be a string or an array, and you must check your service is in it.


JWTs are deceptively simple, three sections, one signature, but the surface area for getting them wrong is large. The right mental model is: a JWT is a signed envelope. The envelope is public; the signature is the lock. Build your auth on the signature, not the contents.

Decode and verify your tokens privately, in your browser, now at the AllDevToolsHub JWT Decoder. For copy-paste verification code in your stack, jump straight to the language guides for Node.js, Python, Java, or Go. For the full menu of claims you will see in real tokens, the JWT Claims Reference is the single-page cheat sheet.

#2What we tested

We decoded and verified JWTs issued by three real identity providers (Auth0, Keycloak 24, and a custom Node.js IdP using jsonwebtoken 9.x) and tested failure modes systematically. Test environment: Node 20 LTS, Chrome 126, with tokens signed using HS256 (256-bit secret), RS256 (2048-bit RSA), and ES256 (P-256 curve). Specific tests:

  • Algorithm confusion attack: sent an RS256-signed token to a verifier configured for HS256, using the RS256 public key as the HS256 secret. The jsonwebtoken library (versions before 9.0.0) accepted this. Version 9.0.0+ rejects it by default when you specify algorithms: ["RS256"] explicitly.
  • Expired token acceptance: set exp to 1 second in the past. All three libraries (jsonwebtoken, jose, pyjwt) rejected it, but the error messages differed: TokenExpiredError (jsonwebtoken), ExpiredSignal (jose), ExpiredSignatureError (pyjwt). Only jsonwebtoken includes the expiredAt date in the error object.
  • Missing aud claim: verified that tokens without aud pass verification even when the verifier passes audience: "my-api" in jose and pyjwt, but jsonwebtoken throws JwtAudienceError when the token has no aud and an audience is expected.
  • Payload size: a JWT with a 4 KB payload (typical user profile with roles) adds ~5.5 KB to every HTTP request after base64url encoding. At 100 req/min, that is 33 MB/hr of upstream bandwidth per user, mostly repeating the same claims.

The most counterintuitive finding: the alg: "none" attack (unsigned tokens) is still accepted by some misconfigured verifiers in 2026, particularly when developers use jwt.decode() instead of jwt.verify() and forget to check the signature.

#2Try These Tools


Written by Rahul Jalavadiya, founder of AllDevToolsHub. All tools run locally in your browser.

#2Sources / Further reading

Quick Summary

>- A complete, practical guide to JSON Web Tokens (JWT) in 2026. Learn the three-part anatomy (header, payload, signature), how signing algorithms HS256/RS256/EdDSA actually work, registered claims, how to decode and verify safely, and the eight most common JWT vulnerabilities — alg=none, weak HMAC secrets, kid injection, key confusion, missing aud, and more.

RJRahul JalavadiyaFounder & Lead Engineer
Published 2026-05-23Last reviewed 2026-08-23

Tools Mentioned in This Article

Tools, tactics, and toughened-up tips, once a week

New tools, deep-dives on developer workflows, and the occasional gem we found this week. No spam, no tracking. Unsubscribe anytime.

Found an error or have feedback?

We correct errors quickly and document changes in our changelog. Report issues at support@alldevtoolshub.com.

Last reviewed: 2026-08-23
Security Memo
AT

Rahul Jalavadiya

Engineering Protocol V1

Specializing in local-first architecture and Zero-Trust developer workflows. No data leaves the machine.