DevToolbox
All articles

How to Read and Decode a JWT Token

ยท 8 min read

JSON Web Tokens (JWTs) are everywhere โ€” authentication cookies, API bearer tokens, OAuth flows. Yet many developers who use them daily have never manually looked inside one. This guide explains the structure, shows you how to decode any JWT by hand or with a tool, and highlights the security nuances that catch developers off guard.

The Three-Part Structure

A JWT always looks like three Base64url-encoded strings joined by dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

The three parts are:

  1. Header โ€” algorithm and token type
  2. Payload โ€” claims (the actual data)
  3. Signature โ€” cryptographic proof of authenticity

The Header

Decode the first segment with Base64url and you get JSON like this:

{
  "alg": "HS256",
  "typ": "JWT"
}

alg names the signing algorithm. Common values are HS256 (HMAC-SHA256, symmetric key), RS256 (RSA-SHA256, asymmetric), and ES256 (ECDSA-SHA256). The algorithm determines how the signature is generated and verified. typ is almost always JWT.

Security note: Older JWT libraries honoured the alg header when verifying signatures. An attacker could set "alg": "none" to bypass verification entirely. Always configure your library to accept only the specific algorithm(s) your system uses.

The Payload (Claims)

The second segment decodes to the claims object โ€” the token's actual data:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1716239022
}

The JWT specification defines several registered claims that have standardised meanings:

  • sub โ€” Subject: who the token represents (usually a user ID).
  • iss โ€” Issuer: which service issued the token.
  • aud โ€” Audience: which service(s) should accept the token.
  • exp โ€” Expiration: Unix timestamp after which the token is invalid.
  • iat โ€” Issued At: Unix timestamp when the token was created.
  • nbf โ€” Not Before: Unix timestamp before which the token must be rejected.
  • jti โ€” JWT ID: a unique identifier, useful for blacklisting individual tokens.

Applications add their own private claims alongside these โ€” things like role, email, permissions, or any data the server wants to embed for the client.

The Signature

The signature is computed over the encoded header and payload:

HMACSHA256(
  base64url(header) + "." + base64url(payload),
  secret
)

The signature lets any server with the correct secret (or public key, for RSA/ECDSA) verify that the token has not been tampered with. If an attacker modifies even one character of the payload, the signature will not match.

Critical point: The signature proves integrity (the token has not been modified) but not confidentiality (the payload is not encrypted). Anyone who receives a JWT can read the header and payload โ€” Base64 is reversible. Never put sensitive data like passwords or credit card numbers in a JWT payload.

How to Decode a JWT Manually

You can decode any JWT in a terminal without a library:

# Split on dots and decode each part (bash)
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.abc"
HEADER=$(echo $TOKEN | cut -d'.' -f1 | base64 -d 2>/dev/null)
PAYLOAD=$(echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null)
echo $HEADER | python3 -m json.tool
echo $PAYLOAD | python3 -m json.tool

Note that Base64url does not require padding (=), so the base64 -d command may need the padding added manually, or use a library that handles URL-safe Base64.

Checking Token Expiry

The exp claim is a Unix timestamp (seconds since 1970-01-01 UTC). Compare it against the current time:

# Bash
EXP=$(echo $PAYLOAD | python3 -c "import sys,json; print(json.load(sys.stdin).get('exp','none'))")
NOW=$(date +%s)
if [ "$EXP" != "none" ] && [ "$EXP" -lt "$NOW" ]; then
  echo "Token is EXPIRED"
else
  echo "Token is valid (expires: $(date -r $EXP))"
fi

Common Mistakes with JWTs

  • Storing in localStorage โ€” Accessible to any JavaScript on the page, including XSS payloads. Prefer HttpOnly cookies for auth tokens.
  • No expiry โ€” Tokens without an exp claim are valid forever. Always set a reasonable expiry and use refresh tokens for long sessions.
  • Sensitive data in payload โ€” Payloads are readable by anyone. Use opaque tokens (random IDs) or encrypt the payload if confidentiality matters.
  • Not validating aud โ€” A token issued for service A can be replayed against service B if aud is not checked. Always validate the audience claim.

Summary

A JWT is a signed JSON object encoded as Base64url. The header identifies the algorithm, the payload carries claims about the user or session, and the signature proves the token has not been modified. Decoding is trivial โ€” verifying requires the secret or public key. Treat JWTs as transparent containers: secure for integrity, but not private.

Try it free
JWT Decoder
100% client-side ยท no signup ยท no upload
Open tool โ†’