JWT Decoder Pro: Verify Token Signatures & Analyze Claims
Master JWT token debugging with signature verification, expiry checking, and claims extraction. The free JWT Decoder Pro shows header, payload, and signature status with a single paste — no signup, no server.
What Is a JSON Web Token?
A JSON Web Token (JWT) is an open standard (RFC 7519, IETF 2015) for securely transmitting information between parties as a compact, URL-safe string. The pronounced name is “jot” — and that casualness reflects what makes JWTs attractive: they are small enough to fit in a URL query parameter or an HTTP header, and self-contained enough that the receiver does not need a database lookup to verify them.
JWTs are used across modern web authentication:
- Single-Page Applications — stateless user sessions; the JWT replaces a session cookie
- REST APIs — Bearer token authentication (
Authorization: Bearer <token>) - OAuth 2.0 / OpenID Connect — access tokens and ID tokens from identity providers
- Microservices — service-to-service authentication without shared session state
- Webhooks — verifying the payload came from a trusted sender
The core promise: the token itself carries verifiable claims. No database round-trip is needed to know who the user is or what they are allowed to do — the server just checks the signature and reads the payload.
The Three Parts of a JWT
Every JWT is three Base64URL-encoded JSON objects separated by dots:
header.payload.signatureBase64URL is similar to Base64 but replaces + with -, replaces / with _, and omits padding = characters. This makes the token URL-safe so it can appear in query strings and HTTP headers without further encoding.
1. Header
Specifies the token type and the signing algorithm:
{
"alg": "RS256",
"typ": "JWT"
}The alg field is critical. A verifier MUST check this field and only accept algorithms it explicitly supports. Never trust the algorithm specified in the token blindly — this is the root cause of several serious JWT vulnerabilities.
2. Payload
Contains the claims — data about the subject and the token itself:
{
"sub": "user_abc123",
"name": "Alice Johnson",
"email": "alice@example.com",
"role": "admin",
"iat": 1717747200,
"exp": 1717833600,
"iss": "https://auth.example.com",
"aud": "https://api.example.com"
}Important: The payload is encoded, not encrypted. Anyone who receives the token can decode and read the payload — no key needed. Never include sensitive data (passwords, API keys, payment card numbers) in JWT claims.
3. Signature
The signature is computed over the encoded header and payload:
HMACSHA256(
base64url(header) + "." + base64url(payload),
secret
)If anyone modifies a single character in the header or payload after signing, the signature will not match and the token must be rejected. The signature is what prevents tampering — it is the security foundation of the entire JWT system.
Signing Algorithms: HMAC vs RSA vs ECDSA
| Algorithm | Type | Key | Use when |
|---|---|---|---|
HS256 | HMAC SHA-256 | Shared secret (symmetric) | One service issues and verifies |
HS512 | HMAC SHA-512 | Shared secret (symmetric) | Same as HS256, stronger hash |
RS256 | RSA SHA-256 | Private key signs, public key verifies (asymmetric) | Auth server issues, many services verify |
RS512 | RSA SHA-512 | Private key signs, public key verifies | Same as RS256, stronger hash |
ES256 | ECDSA P-256 | Private key signs, public key verifies | Smaller keys/signatures than RSA |
none | No signature | None | Never use in production — critical vulnerability |
HMAC — symmetric, simple
Both the token issuer and all verifiers share the same secret key. Fast and simple for a single service. The critical limitation: every service that needs to verify the token must know the secret — which means securely distributing it to all consumers and rotating it requires updating all of them simultaneously.
RSA / ECDSA — asymmetric, scalable
The auth server signs with a private key that never leaves the server. Any service that needs to verify tokens uses the corresponding public key, which can be published openly via a JWKS endpoint (/.well-known/jwks.json). Google, GitHub, Okta, and Auth0 all use this approach — their public keys are publicly accessible and rotate on a schedule.
Registered JWT Claims Reference
| Claim | Full name | Type | Meaning |
|---|---|---|---|
iss | Issuer | String/URI | Who issued the token (e.g. auth server URL) |
sub | Subject | String | Who the token is about (user ID) |
aud | Audience | String/Array | Who should accept the token (API URL) |
exp | Expiration | NumericDate | Unix timestamp after which token is invalid |
nbf | Not before | NumericDate | Unix timestamp before which token is invalid |
iat | Issued at | NumericDate | Unix timestamp when token was created |
jti | JWT ID | String | Unique ID for this token (prevents replay attacks) |
Token Expiry and Refresh Patterns
The exp claim controls how long a token is valid. The value is a Unix timestamp (seconds since 1970-01-01T00:00:00Z). JWT Decoder Pro calculates how long ago the token was issued, when it expires, and whether it is currently valid.
Common expiry patterns
- Short-lived access tokens (5–60 minutes): The token must be refreshed frequently. Limits the damage if a token is stolen — it expires before the attacker can do much.
- Refresh tokens (7–90 days): Stored more securely (HttpOnly cookie or secure storage). Used to obtain new access tokens without re-authenticating.
- Long-lived API tokens (months/years): Appropriate for machine-to-machine service accounts where user interaction is impractical.
Clock skew
Different servers may have slightly different system clocks. A token issued atiat: 1717747200 on Server A might arrive at Server B when Server B's clock is 1717747198 — two seconds behind. Strict expiry checking would reject a token that hasn't technically started yet. Most JWT libraries allow a configurable clock skew tolerance (typically 30–300 seconds) to handle this gracefully.
JWT Security Pitfalls
The “none” algorithm attack
Some early JWT implementations accepted alg: "none" in the header, which meant “this token has no signature — accept it anyway.” An attacker could take any valid token, modify the payload, set the algorithm to none, and strip the signature. The server would accept it as valid.
Modern libraries reject none by default. Never accept algorithm values from the token header without validating against a server-side allowlist.
Algorithm confusion attack (RS256 → HS256)
A more subtle attack: if a server configured for RS256 also accepts HS256, an attacker can take the server's public key, use it as the HMAC secret to sign a modified token, then change the header to alg: "HS256". The server — thinking this is an HMAC-signed token — verifies it with the public key used as the HMAC secret. Since the attacker signed it with that same key, verification passes.
Fix: explicitly specify the expected algorithm on the server. Never allow the token header to dictate which algorithm is used for verification.
Missing signature validation
Base64URL decoding the payload is not validation. Some developers decode the payload to read claims without ever verifying the signature, reasoning “I just need to see the user ID.” This completely defeats the security model — any token with any payload passes. Always verify the signature before trusting claims.
Sensitive data in payload
The payload is encoded (Base64URL), not encrypted. Any recipient — including the user, any proxy, any log aggregator — can decode and read it. Do not include passwords, SSNs, credit card numbers, or other sensitive data as JWT claims.
Using JWT Decoder Pro
- Open JWT Decoder Pro
- Paste the JWT token into the input field
- Select No Verification to inspect without verification, or select your algorithm (HS256/RS256/etc.)
- For HMAC: enter the shared secret. For RSA: paste the public key in PEM format
- Click Decode & Verify
- The tool shows: header fields, payload fields, signature validity badge, and expiry status
Inspecting claims
Standard claims (sub, iss, aud, exp,iat) are highlighted in a dedicated section with human-readable values. The exp shows as an ISO 8601 timestamp with a relative label (“expires in 47 minutes” or “expired 3 days ago”). All other claims appear in the full payload JSON.
Verifying JWT Signatures in Code
Node.js (jsonwebtoken)
const jwt = require('jsonwebtoken');
// HMAC verification
try {
const payload = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'], // Explicit allowlist — never omit this
});
console.log(payload.sub); // user ID
} catch (err) {
if (err.name === 'TokenExpiredError') {
// Redirect to refresh flow
} else {
// Reject request — invalid signature
}
}
// RSA verification using public key
const publicKey = fs.readFileSync('public.pem');
const payload = jwt.verify(token, publicKey, { algorithms: ['RS256'] });Python (PyJWT)
import jwt
# HMAC
payload = jwt.decode(
token,
os.environ["JWT_SECRET"],
algorithms=["HS256"], # Explicit allowlist required
options={"require": ["sub", "exp", "iat"]},
)
# RSA — load public key from JWKS endpoint
from jwt import PyJWKClient
jwks_client = PyJWKClient("https://auth.example.com/.well-known/jwks.json")
signing_key = jwks_client.get_signing_key_from_jwt(token)
payload = jwt.decode(token, signing_key.key, algorithms=["RS256"])JWT vs Session Cookies vs Opaque Tokens
| Feature | JWT | Session cookie | Opaque token |
|---|---|---|---|
| Storage | Client (memory/localStorage) | Server (DB/Redis) | Server (DB/Redis) |
| Verification | Signature check only (no DB) | DB lookup every request | DB lookup every request |
| Revocation | Hard — must expire or maintain deny list | Easy — delete session row | Easy — delete token row |
| Scalability | Excellent — stateless, no shared state | Requires sticky sessions or shared cache | Requires shared token store |
| Token size | Larger — entire payload travels on each request | Small — just a session ID | Small — just an opaque string |
| CSRF risk | Lower when stored in memory | Higher — cookies sent automatically | Same as session cookies |
Decode and Verify JWTs Free Online
Paste any JWT — see header, payload, expiry, and signature status instantly. No signup required.
Open JWT Decoder Pro