ToolSite
All posts

How to Decode a JWT Token (What's Inside It)

Learn to decode a JWT token: the header, payload, and signature. Understand what's inside each section, how base64url works, and why to verify before trust.

By ToolSite5 min readguides

What Is a JWT?

JSON Web Tokens (JWT, pronounced "jot") are compact, URL-safe tokens used for authentication and information exchange. When you log into a web application, the server often issues a JWT that your browser sends with every subsequent request. The server verifies the token without needing a database lookup.

A JWT looks like a long, random string with two dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Each segment between the dots is independently Base64url-encoded. You can decode the first two parts by hand. No library needed.

Structure: header.payload.signature

A JWT consists of three parts separated by dots:

  1. Header (first segment). Metadata about the token: which signing algorithm was used and the token type.
  2. Payload (second segment). The claims, or data being transmitted. This is where user ID, expiry time, and roles live.
  3. Signature (third segment). A cryptographic output that proves the token has not been tampered with. This is not readable text.

Each part is independently Base64url-encoded. Standard Base64 (+, /, =) is never used in JWTs. The dots are literal separators, not part of the encoding.

Decoding the Header

Take the first segment (before the first dot) and decode it with any Base64url decoder:

Encoded: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Decoded: {"alg":"HS256","typ":"JWT"}

The header tells you:

  • alg: which algorithm was used to sign the token. HS256 (HMAC-SHA256), RS256 (RSA-SHA256), ES256 (ECDSA), or none (no signature, a dangerous setting that should never appear in production).
  • typ: the token type, almost always JWT.

You may also see kid (Key ID), which tells the server which specific key to use for verification when multiple signing keys are active.

Decoding the Payload

The payload is the middle segment (between the two dots):

Encoded: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
Decoded: {"sub":"1234567890","name":"John Doe","iat":1516239022}

Standard registered claims you might see:

  • sub: subject (user ID). The primary identifier for the user.
  • iat: issued at (Unix timestamp). When the token was created.
  • exp: expiration time (Unix timestamp). After this time, the token must be rejected.
  • nbf: not before (Unix timestamp). The token is invalid before this time.
  • iss: issuer. Who created this token (often a URL like https://auth.example.com).
  • aud: audience. Who this token is intended for. The server must verify this matches its own identifier.

Custom claims are anything the server adds: role, email, permissions, tenant_id, and so on. There is no schema enforcement. The payload is whatever JSON the issuing server chooses to put there.

The Signature: Verify, Do Not Just Decode

The third segment is not meant to be decoded into readable text. It is a cryptographic output:

SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

The signature is computed by taking the encoded header, a dot, the encoded payload, and signing that combined string. For HMAC-based tokens (HS256):

signature = HMAC-SHA256(
  secret,
  base64url(header) + "." + base64url(payload)
)

For RSA and ECDSA (RS256, ES256), the server signs with a private key and clients verify with the corresponding public key. This allows anyone to verify the token without knowing a shared secret.

The server recomputes the signature on every request. If the computed signature does not match the one in the token, the token has been tampered with and must be rejected. There is no partial trust. One bit wrong, one byte off, and the entire token is invalid.

How Tokens Expire and Refresh

JWTs typically have short lifetimes, often 15 minutes to a few hours. When a token expires, the client uses a refresh token (a separate, longer-lived credential) to obtain a new access token. This limits the damage if an access token is stolen: the attacker has a small window to use it.

Stateless JWTs mean the server does not store the token. Revocation is tricky because there is nothing server-side to delete. Common approaches include maintaining a blocklist of revoked token IDs or keeping access tokens short-lived enough that revocation is not needed for individual tokens.

Critical Security Rules

Decoding a JWT header and payload is trivial. Anyone can base64url-decode them in a browser console. This has consequences:

  • Never put secrets in the payload. The payload is readable by anyone who captures the token, not just the intended server. Assume every JWT payload is public.
  • Always verify the signature before trusting claims. A client that decodes {"role":"admin"} from an unverified token is a security hole. Attackers can craft their own tokens with arbitrary payloads. The server must validate the signature before using any claim data.
  • Check the algorithm. Some JWT libraries have been vulnerable to "alg=none" attacks where the header specifies no algorithm and the library skips verification. Always enforce a specific algorithm whitelist on the server.
  • Validate every claim the spec provides. Check exp (reject expired tokens), nbf (reject premature tokens), iss (reject tokens from unknown issuers), and aud (reject tokens not meant for you).

Try it yourself: open the JWT Decoder, paste the example token from the top of this article, and inspect the decoded header and payload. You will see the algorithm, user ID, name, and issue timestamp in plain text. Then open the Base64 Encoder/Decoder and manually decode one of the segments to confirm the tool uses Base64url. Copy a payload segment, modify one character in the decoded JSON, re-encode it, and reconstruct the token. Paste the modified token into the JWT Decoder. The signature will show as invalid.

Related Reading