DEVESSENTIALS

JWT Decoder

Paste any JWT token to decode and inspect its header, payload claims and signature. Timestamps are converted to human-readable dates and expiration is checked automatically. Your token never leaves the browser.

JWT Token
Expired
Header
{
  "alg": "HS256",
  "typ": "JWT"
}
Payload
sub

"usr_devessentials"

Subject — who the token refers to

name

"Developer"

Full name

email

"hello@devessentials.dev"

Email address

iss

"devessentials.dev"

Issuer — who issued the token

aud

"api.devessentials.dev"

Audience — who the token is intended for

iat

3/27/2025, 12:00:00 AM(1743033600)

Issued At — when the token was issued

exp

3/31/2026, 11:33:20 PM(1775000000)

Expiration — when the token expires

role

"admin"

User role

plan

"pro"

Raw JSON
{
  "sub": "usr_devessentials",
  "name": "Developer",
  "email": "hello@devessentials.dev",
  "iss": "devessentials.dev",
  "aud": "api.devessentials.dev",
  "iat": 1743033600,
  "exp": 1775000000,
  "role": "admin",
  "plan": "pro"
}
Signature

SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Signature verification requires the secret key and cannot be done client-side securely.

Decoding JWTs in Code

JavaScript (Browser) — Manual Decode

// JWTs are Base64URL-encoded — no library needed to read claims
function decodeJwt(token) {
  const [header, payload] = token.split(".");
  const decode = (str) =>
    JSON.parse(atob(str.replace(/-/g, "+").replace(/_/g, "/")));
  return { header: decode(header), payload: decode(payload) };
}

const { header, payload } = decodeJwt("eyJhbGciOiJIUzI1NiJ9...");
console.log(payload.sub);  // user ID
console.log(payload.exp);  // expiry (Unix timestamp)
// ⚠️  This only reads claims — it does NOT verify the signature.

Node.js — jose (verify + decode)

// npm install jose
import { jwtVerify, decodeJwt } from "jose";

// Decode without verification (inspect claims only)
const claims = decodeJwt("eyJhbGciOiJIUzI1NiJ9...");
console.log(claims);

// Verify HS256 token
const secret = new TextEncoder().encode("your-secret-key");
const { payload } = await jwtVerify("eyJhbGciOiJIUzI1NiJ9...", secret);
console.log(payload.sub);

// Verify RS256 token using JWKS endpoint
import { createRemoteJWKSet } from "jose";
const JWKS = createRemoteJWKSet(new URL("https://auth.example.com/.well-known/jwks.json"));
const { payload: p } = await jwtVerify(token, JWKS);

Python — PyJWT

# pip install PyJWT cryptography
import jwt

# Decode without verification (inspect claims only)
claims = jwt.decode(token, options={"verify_signature": False})
print(claims)

# Verify HS256 token
claims = jwt.decode(token, key="your-secret-key", algorithms=["HS256"])
print(claims["sub"])

# Verify RS256 token
with open("public_key.pem") as f:
    public_key = f.read()
claims = jwt.decode(token, public_key, algorithms=["RS256"])

Go — golang-jwt

// go get github.com/golang-jwt/jwt/v5
import "github.com/golang-jwt/jwt/v5"

// Parse and verify HS256
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
        return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
    }
    return []byte("your-secret-key"), nil
})

if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
    fmt.Println(claims["sub"])
    fmt.Println(claims["exp"])
}

Terminal

# Decode payload (no verification) using base64 and jq
TOKEN="eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTcwMDAwMDAwMH0.sig"
PAYLOAD=$(echo $TOKEN | cut -d. -f2)

# Add padding if needed and decode
echo "${PAYLOAD}==" | tr '_-' '/+' | base64 -d | jq .

# Output:
# { "sub": "user_123", "exp": 1700000000 }

JWT Structure Explained

A JWT (JSON Web Token) is three Base64URL-encoded segments separated by dots: header.payload.signature

PartContainsExample
HeaderAlgorithm and token type{ "alg": "HS256", "typ": "JWT" }
PayloadClaims (user data, expiry){ "sub": "user_123", "exp": 1712686400 }
SignatureHMAC or RSA of header+payloadVerified server-side — cannot be faked without the secret

Standard claims in the payload: sub (subject — user ID), iss (issuer), aud (audience), iat (issued at — Unix timestamp), exp (expiry — Unix timestamp), nbf (not before). Any additional claims (role, email, permissions) are custom and defined by the application.

Decoding ≠ verifying. This tool decodes the payload — it reads the claims but does not check the signature. Anyone can decode any JWT. Only your server, using the correct secret or public key, can verify that it hasn't been tampered with.

Related Tools

Frequently Asked Questions

What is the difference between decoding a JWT and verifying it?

Decoding a JWT just Base64URL-decodes the header and payload — it reads the claims but performs zero cryptographic checks. Anyone can decode any JWT without a secret. Verification confirms the signature using the correct algorithm and key, ensuring the token was issued by a trusted party and hasn't been tampered with. This tool is for debugging and inspection only — your server code must always verify the signature before trusting any claim inside a JWT.

What is the 'alg: none' JWT vulnerability and how does it work?

The JWT spec originally allowed "alg": "none" to indicate an unsigned token. Several JWT libraries had bugs where passing alg: none in the header caused them to skip signature verification entirely, accepting any forged token as valid. A 2015 disclosure showed this affected multiple popular libraries across languages. Always configure your JWT library to explicitly whitelist the expected algorithm(s) and reject none — never trust the algorithm specified in the token header without validation.

What is the difference between HS256 and RS256, and when should I use each?

HS256 (HMAC-SHA256) uses a single shared secret — the same key signs and verifies the token, so every service that needs to verify tokens must possess the secret. RS256 (RSA-SHA256) uses a private key to sign and a public key to verify — you can distribute the public key freely via a JWKS endpoint so any service can verify tokens without ever seeing the signing key. Use RS256 (or ES256) when multiple independent services need to verify tokens, or when integrating with third-party identity systems.

What causes 'token expired' errors on a freshly issued JWT?

The exp claim is a Unix timestamp (seconds since epoch) checked against the validating server's current time. A common cause of unexpected expiry errors is clock skew between the issuing and validating servers — even a 1–2 minute difference will cause a freshly issued token to appear expired. Most JWT libraries allow configuring a 'leeway' (e.g., 60 seconds) to tolerate minor clock drift. A decoded token that looks valid in a browser debugger may still be rejected server-side due to this timing mismatch.

Why do the three parts of a JWT use Base64URL encoding instead of standard Base64?

Standard Base64 uses + and / characters, which have special meaning in URLs (+ = space, / = path separator) and would require percent-encoding if the JWT were passed as a URL parameter or cookie. Base64URL (RFC 4648 §5) substitutes - for + and _ for /, and omits the = padding characters, producing a compact token that is safe in HTTP headers, query strings, and cookies without any further encoding. This is why the three dot-separated segments of a JWT never contain those characters.