PublicSoftTools
Tools16 min read·PublicSoftTools Team·June 2026

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:

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.signature

Base64URL 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

AlgorithmTypeKeyUse when
HS256HMAC SHA-256Shared secret (symmetric)One service issues and verifies
HS512HMAC SHA-512Shared secret (symmetric)Same as HS256, stronger hash
RS256RSA SHA-256Private key signs, public key verifies (asymmetric)Auth server issues, many services verify
RS512RSA SHA-512Private key signs, public key verifiesSame as RS256, stronger hash
ES256ECDSA P-256Private key signs, public key verifiesSmaller keys/signatures than RSA
noneNo signatureNoneNever 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

ClaimFull nameTypeMeaning
issIssuerString/URIWho issued the token (e.g. auth server URL)
subSubjectStringWho the token is about (user ID)
audAudienceString/ArrayWho should accept the token (API URL)
expExpirationNumericDateUnix timestamp after which token is invalid
nbfNot beforeNumericDateUnix timestamp before which token is invalid
iatIssued atNumericDateUnix timestamp when token was created
jtiJWT IDStringUnique 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

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

  1. Open JWT Decoder Pro
  2. Paste the JWT token into the input field
  3. Select No Verification to inspect without verification, or select your algorithm (HS256/RS256/etc.)
  4. For HMAC: enter the shared secret. For RSA: paste the public key in PEM format
  5. Click Decode & Verify
  6. 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

FeatureJWTSession cookieOpaque token
StorageClient (memory/localStorage)Server (DB/Redis)Server (DB/Redis)
VerificationSignature check only (no DB)DB lookup every requestDB lookup every request
RevocationHard — must expire or maintain deny listEasy — delete session rowEasy — delete token row
ScalabilityExcellent — stateless, no shared stateRequires sticky sessions or shared cacheRequires shared token store
Token sizeLarger — entire payload travels on each requestSmall — just a session IDSmall — just an opaque string
CSRF riskLower when stored in memoryHigher — cookies sent automaticallySame 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