DEV Community

Anup Karanjkar
Anup Karanjkar

Posted on • Originally published at wowhow.cloud

JWT Decoder & Debugger: How JSON Web Tokens Work in 2026

A JWT that you cannot verify is just base64-encoded data with a false sense of security — and that describes the majority of JWT implementations in production today.

JSON Web Tokens (JWTs) appear in auth headers, cookies, and query parameters across virtually every modern web application. They carry user identity, authorization claims, and session state. They are passed between services, stored in browsers, and sent to third-party APIs. Despite their ubiquity, JWT security is frequently misunderstood — and the misunderstandings are exploitable.

This guide explains exactly how JWTs work, what each component contains, how signatures are validated, and where implementations go wrong. Use the free WOWHOW JWT decoder to inspect any token as you read.

Try it yourself: Free JWT Decoder — free, no signup, runs in your browser.

JWT Structure: The Three Parts

A JWT looks like this:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNzE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Enter fullscreen mode Exit fullscreen mode

Three base64url-encoded segments separated by dots: header, payload, signature. Each is independently decodable. None of them are encrypted (unless you use JWE — JSON Web Encryption — which is a separate spec).

The Header

The header declares the token type and the signing algorithm:

{
  "alg": "RS256",
  "typ": "JWT"
}
Enter fullscreen mode Exit fullscreen mode

The alg field is critical from a security standpoint. Common values:

  • HS256: HMAC-SHA256 — symmetric, same key signs and verifies

  • RS256: RSA-SHA256 — asymmetric, private key signs, public key verifies

  • ES256: ECDSA-SHA256 — asymmetric, smaller signatures than RSA

  • none: No signature — never accept this in production

The alg: "none" attack is a well-documented vulnerability: an attacker crafts a token with "alg": "none" and no signature. Naive libraries accept it because the spec technically allows unsigned tokens. Any production JWT library must explicitly disable alg: "none".

The Payload

The payload contains claims — statements about the subject (usually a user) and metadata. The JWT spec defines standard claim names:

  • sub: Subject — the user ID or entity the token represents

  • iss: Issuer — the server that created the token

  • aud: Audience — the service(s) that should accept this token

  • exp: Expiration time — Unix timestamp after which the token is invalid

  • iat: Issued at — when the token was created

  • jti: JWT ID — unique identifier for the token (used to prevent replay)

  • nbf: Not before — the token is invalid before this timestamp

Custom claims carry application-specific data — roles, permissions, tenant IDs. A typical application payload might include role: "admin" or plan: "pro". These are claims your server made at token issuance time.

Critical: The payload is not encrypted. Anyone who holds the token can decode and read the payload. Never put sensitive data (passwords, payment details, PII beyond what's necessary for auth) in a JWT payload.

The Signature

The signature is computed over the concatenation of the encoded header and encoded payload:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)
Enter fullscreen mode Exit fullscreen mode

For RSA (RS256), the private key signs this concatenation. The receiving server verifies using the public key. If the signature does not match, the token was tampered with — either the payload claims were modified, or the token was forged.

Signature verification is not optional. It is the entire security model. A server that decodes a JWT payload without verifying the signature is trusting user-controlled input. The JWT decoder shows you the decoded header and payload instantly — always verify signatures in your application code, not just decode.

JWT Validation: The Complete Checklist

Decoding a JWT is trivial (base64url decode). Validating it is where most implementations fall short. A complete validation requires checking all of the following:

1. Algorithm Verification

Verify that alg matches your expected algorithm. Never accept alg: "none". If you expect RS256, reject HS256 — an attacker can take your public key, sign a token with it using HS256 (treating the public key as the HMAC secret), and a naive library that accepts both will verify it as valid.

2. Signature Verification

Verify the signature using the correct key for the algorithm. For RS256 and ES256, fetch the public key from your identity provider's JWKS endpoint (/.well-known/jwks.json). Cache the JWKS with a reasonable TTL — fetching it on every request is a performance problem and a potential DoS vector.

3. Expiration (exp)

Reject tokens where exp is in the past. Allow a small clock skew (1–2 minutes) for distributed systems where clocks are not perfectly synchronized. Never accept a token without an exp claim unless you have specific requirements for non-expiring tokens.

4. Audience (aud)

Verify that aud matches your service identifier. An access token issued for api.example.com should be rejected by payments.example.com. Skipping audience validation enables token substitution attacks where a token from one service is replayed against another.

5. Issuer (iss)

Verify that iss matches your expected token issuer. If you use multiple identity providers, ensure each service validates against the correct issuer.

JWT vs Session Tokens: When to Use Each

JWTs are stateless — the server does not need to look up anything in a database to validate a JWT. This makes them attractive for microservices and distributed systems. But statelessness comes with a cost: you cannot revoke a JWT before it expires without adding a revocation layer (a blocklist — which reintroduces state).

Session tokens (opaque tokens stored server-side) are revocable instantly. They require a database lookup on every request. The choice depends on your architecture:

  • Use JWTs for short-lived access tokens (15 minutes) in microservices architectures where stateless validation matters

  • Use session tokens (or long-lived refresh tokens with short-lived JWT access tokens) where immediate revocation is required (logout, compromise response)

  • Never use JWTs with long expiration times as substitutes for session management

JWK Sets: How Public Keys Are Distributed

For RS256 and ES256, verifiers need the public key corresponding to the private key that signed the token. JWK Sets (JSON Web Key Sets) are the standard mechanism for publishing these public keys:

{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "kid": "2026-05-key-1",
      "n": "...",
      "e": "AQAB"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The kid (key ID) in the JWT header matches the kid in the JWKS entry, allowing key rotation without breaking existing tokens. Auth0, Okta, and AWS Cognito all publish JWKS endpoints. If you implement your own JWT issuer, publish a JWKS endpoint and rotate keys periodically.

People Also Ask

Is it safe to decode a JWT in the browser?

Decoding (base64url decoding) is safe — it just reads the payload, which is already visible to anyone with the token. What is not safe is making authorization decisions based on the decoded payload without verifying the signature server-side. Never trust client-side JWT decoding for access control decisions.

Why does my JWT expire so quickly?

Short expiration (15–60 minutes for access tokens) is intentional security design. Short-lived tokens limit the window of exposure if a token is stolen. The standard pattern is: short-lived JWT access token + long-lived opaque refresh token. The refresh token is stored securely (HTTP-only cookie or server-side session) and exchanges for new access tokens as needed.

Can I modify a JWT payload?

You can decode and re-encode the payload (it is just base64url), but the signature will no longer match — the server will reject the modified token. That is the entire point of the signature. If you need to include updated claims, issue a new token from your authorization server.

What is the difference between JWT and OAuth 2.0?

OAuth 2.0 is an authorization framework — a protocol for delegating access between services. JWT is a token format — a way to encode claims. OAuth 2.0 access tokens are often (but not always) formatted as JWTs. OpenID Connect (OIDC) builds on OAuth 2.0 and mandates JWT format for ID tokens.

Browse more developer security and productivity tools in the WOWHOW tools catalog.

Originally published at wowhow.cloud

Top comments (0)