ByteTools Logo

How to Decode JWT Tokens: Complete 2025 Developer Guide

15 min readAuthentication & Security

Master JWT decoding for debugging authentication issues, inspecting token claims, and understanding web security. Learn the structure, security considerations, and best practices.

You're debugging a 401 Unauthorized error. You have a JWT token from the Authorization header, but what's inside it? Is the token expired? Does it have the right claims? Is the algorithm correct? To answer these questions, you need to decode the JWT—and understand what you're looking at.

🎯 Quick Start: Decode JWT Now

Need to inspect a JWT token immediately? Use our free JWT decoder to view headers, payloads, and signatures instantly—100% client-side, your tokens never leave your browser.

Decode JWT Token →

What is a JWT Token?

A JWT (JSON Web Token) is a compact, URL-safe way to represent claims to be transferred between two parties. It's the modern standard for authentication and authorization in web applications, APIs, and microservices.

Example: Real JWT Token Structure

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Red: Header (algorithm & token type)
Blue: Payload (claims/data)
Green: Signature (verification)

JWT Structure Explained: The Three Parts

1. Header: Algorithm & Token Type

The header specifies the signing algorithm (HMAC SHA256, RSA, etc.) and token type (JWT).

{
  "alg": "HS256",    // Algorithm: HMAC SHA-256
  "typ": "JWT"       // Token type
}

⚠️ Security Alert: Algorithm Confusion Attacks

Always validate the algorithm matches your application's expectations. Attackers can change "alg" from "RS256" (asymmetric) to "HS256" (symmetric) or even "none" to bypass signature verification.

Defense: Explicitly specify allowed algorithms in your JWT library configuration.

2. Payload: Claims & User Data

The payload contains claims—statements about the user and additional metadata. There are three types of claims:

Claim TypeExamplesPurpose
Registerediss, sub, exp, iat, audStandard claims defined by JWT specification
Publicname, email, adminCustom claims defined by your application
PrivateuserId, companyIdCustom claims agreed between parties
{
  // Registered claims
  "iss": "https://api.myapp.com",      // Issuer
  "sub": "user-12345",                 // Subject (user ID)
  "aud": "https://myapp.com",          // Audience
  "exp": 1732752000,                   // Expiration time (Unix)
  "iat": 1732748400,                   // Issued at (Unix)
  "nbf": 1732748400,                   // Not before

  // Public/custom claims
  "name": "John Doe",
  "email": "john@example.com",
  "roles": ["admin", "user"],
  "permissions": ["read:users", "write:posts"]
}

3. Signature: Verification & Integrity

The signature ensures the token hasn't been tampered with. It's created by:

  1. Base64-encoding the header and payload
  2. Concatenating them with a period: encodedHeader.encodedPayload
  3. Signing the result with a secret key (HMAC) or private key (RSA)

🔐 Important: Decoding vs. Verification

Decoding reads the JWT's contents without checking validity. Verificationvalidates the signature to confirm authenticity and integrity.

Never trust decoded JWT data without verification. Anyone can create a JWT with any claims. Always verify signatures server-side before trusting token contents.

Step-by-Step: How to Decode JWT Tokens

Method 1: ByteTools JWT Decoder (Recommended for Development)

  1. Copy your JWT token from browser DevTools (Network → Headers → Authorization), application logs, or API responses
  2. Visit ByteTools JWT Decoder in your browser
  3. Paste the token into the input field—the tool instantly decodes it
  4. Inspect the decoded sections: Header (algorithm, type), Payload (claims, expiration), Signature (raw bytes)
  5. Check critical claims: exp (expiration), iss (issuer), sub (user ID), aud (audience)
  6. Identify issues: Expired tokens, wrong user IDs, missing permissions, algorithm mismatches

Privacy Note: All decoding happens in your browser using JavaScript. Tokens are never transmitted to any server.

Method 2: Command Line (jq + base64)

# Decode JWT header (first part)
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" | base64 -d | jq

# Decode JWT payload (second part)
echo "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0" | base64 -d | jq

# Decode both parts in one line
JWT="eyJhbGci...full.token.here"
echo $JWT | cut -d'.' -f1 | base64 -d | jq  # Header
echo $JWT | cut -d'.' -f2 | base64 -d | jq  # Payload

Method 3: JavaScript/Node.js

const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.xyz";

// Split JWT into parts
const [headerB64, payloadB64, signature] = jwt.split('.');

// Decode header
const header = JSON.parse(atob(headerB64));
console.log("Header:", header);

// Decode payload
const payload = JSON.parse(atob(payloadB64));
console.log("Payload:", payload);

// Check expiration
if (payload.exp && Date.now() >= payload.exp * 1000) {
  console.log("Token expired!");
}

Method 4: Python

import base64
import json

jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.xyz"

# Split and decode
header, payload, signature = jwt.split('.')

# Decode (add padding if needed)
def decode_jwt_part(part):
    padding = 4 - len(part) % 4
    part += '=' * padding
    return json.loads(base64.urlsafe_b64decode(part))

header_data = decode_jwt_part(header)
payload_data = decode_jwt_part(payload)

print("Header:", header_data)
print("Payload:", payload_data)

Common JWT Debugging Scenarios

Scenario 1: 401 Unauthorized - Token Expired

Symptom: API returns 401 with message "Token expired" or "Unauthorized"

Debug: Decode the JWT and check the exp claim

"exp": 1732748400 // Unix timestamp

Convert to human-readable: new Date(1732748400 * 1000)

Solution: Refresh the token using your refresh token endpoint before it expires

Scenario 2: Wrong User Data - Missing Permissions

Symptom: User can't access resources they should have permission for

Debug: Decode token and verify claims match database/expected values

{ "sub": "user-12345", "roles": ["user"], // Missing "admin" role "permissions": ["read:posts"] // Missing "write:posts" }

Solution: Token was issued before role/permission change. Re-authenticate to get fresh token with updated claims

Scenario 3: Algorithm Mismatch

Symptom: Server rejects token with "Invalid signature" or "Algorithm not allowed"

Debug: Check token header's alg claim

"alg": "RS256" // Server expects HS256

Solution: Auth provider and API server must use the same algorithm. Update configuration or regenerate tokens

Scenario 4: Wrong Issuer or Audience

Symptom: Token rejected with "Invalid issuer" or "Invalid audience"

Debug: Verify iss and aud claims

{ "iss": "https://dev.auth.myapp.com", // Dev issuer "aud": "https://api.myapp.com" // Production audience }

Solution: Using dev token in production. Get token from correct environment

JWT Security Best Practices

✅ Do

  • Always verify signatures server-side before trusting token claims
  • Use short expiration times (15-30 minutes) with refresh tokens
  • Validate iss, aud, exp, nbf claims on every request
  • Use strong signing algorithms (RS256, ES256) for production
  • Rotate signing keys regularly (every 6-12 months)
  • Store tokens in HttpOnly cookies or secure storage (not localStorage for sensitive apps)

❌ Don't

  • Never trust decoded JWT data without verification
  • Don't store sensitive data in tokens (passwords, credit cards, SSNs)
  • Don't use "alg": "none" or allow it in your JWT library config
  • Don't decode JWTs on untrusted online tools (production tokens)
  • Don't use weak signing secrets (use 256+ bit random keys)
  • Don't set expiration too long (no longer than 1 hour for access tokens)

When to Decode vs. Verify JWT Tokens

OperationUse CaseTrust Level
Decode Only
  • Debugging authentication issues
  • Inspecting token contents during development
  • Reading non-security-critical data (e.g., username for UI display)
❌ Never trust
Decode + Verify
  • Production API authentication
  • Authorization decisions (permissions, roles)
  • Any security-critical operation
✅ Safe to trust

Common JWT Claims Reference

ClaimFull NameDescriptionExample
issIssuerWho created and signed the tokenhttps://auth.myapp.com
subSubjectUser identifier (user ID)user-12345
audAudienceIntended recipient of the tokenhttps://api.myapp.com
expExpiration TimeWhen token expires (Unix timestamp)1732752000
nbfNot BeforeToken not valid before this time1732748400
iatIssued AtWhen token was created1732748400
jtiJWT IDUnique token identifier (for revocation)abc123xyz

Privacy & Security When Decoding JWTs Online

⚠️ Critical Warning: Never Decode Production Tokens on Untrusted Tools

Many online JWT decoders transmit your tokens to backend servers for processing. This exposes sensitive authentication tokens to third parties.

Risks: Token theft, unauthorized access, data breaches, GDPR/compliance violations

✅ ByteTools JWT Decoder: 100% Client-Side Processing

Our JWT decoder runs entirely in your browser using JavaScript. Zero network requests. No data transmission. Your tokens never leave your device.

  • No tracking or analytics on decoded tokens
  • Works offline after initial page load
  • Open source - verify the code yourself on GitHub
  • No server-side code - static site hosted on Cloudflare

Frequently Asked Questions

Can I decode a JWT without the secret key?

Yes. Decoding (reading the contents) doesn't require the secret key—only verification does. JWTs are Base64-encoded, not encrypted. Anyone can decode and read the header and payload. The signature validation is what requires the secret key.

Are JWTs encrypted?

No, standard JWTs (JWS - JSON Web Signature) are signed, not encrypted. The contents are Base64-encoded and visible to anyone. There's a separate standard called JWE (JSON Web Encryption) for encrypted JWTs, but it's rarely used.

Never put sensitive data in standard JWTs. Treat them as publicly readable.

How do I check if a JWT is expired?

Decode the token and check the exp claim. It's a Unix timestamp (seconds since 1970).

const payload = JSON.parse(atob(jwt.split('.')[1])); const expired = Date.now() >= payload.exp * 1000; console.log(expired ? "Expired" : "Valid");

What's the difference between access tokens and refresh tokens?

Access tokens (usually JWTs) grant access to APIs. Short-lived (15-30 min). Refresh tokens are long-lived (days/weeks) and used to obtain new access tokens without re-authenticating. Refresh tokens are often opaque (not JWTs) and stored server-side for revocation.

Key Takeaways

  • JWTs have three parts: Header (algorithm), Payload (claims), Signature (verification)
  • Decoding reads contents; verification validates authenticity. Never trust decoded data without verification.
  • Use ByteTools JWT Decoder for safe, client-side token inspection
  • Always validate exp, iss, aud, and alg claims server-side
  • Use short expiration times (15-30 min) with refresh token rotation for security
  • Never store sensitive data in JWTs—they're Base64-encoded, not encrypted

Ready to Decode Your JWT Tokens?

Inspect headers, payloads, and signatures instantly with our privacy-first JWT decoder. No data transmission. No tracking. Just pure client-side decoding.

Decode JWT Token Now →

Related Privacy-First Tools