Skip to main content
Home/Blog/Development/JWT Decode: How to Decode JWT Tokens (With Code Examples)
Development

JWT Decode: How to Decode JWT Tokens (With Code Examples)

Learn how to decode JWT tokens step-by-step with code examples in JavaScript, Python, Java, Go, and PHP. Understand JWT structure, decode payloads, and troubleshoot common issues.

By Inventive HQ Team
JWT Decode: How to Decode JWT Tokens (With Code Examples)
Want to decode a token right now? Use our free JWT Decoder — it decodes any JWT instantly and runs entirely in your browser, so your token never leaves your device. Read on for the full breakdown of how decoding works under the hood.

What is JWT Decode?

JWT decode is the process of extracting and reading the information stored inside a JSON Web Token. When you decode a JWT, you convert the Base64URL-encoded header and payload back into readable JSON format. This allows you to inspect the token's claims, expiration time, and other metadata without needing the secret key.

Unlike JWT verification (which validates the signature), decoding simply reveals the token's contents. Anyone can decode a JWT—the information isn't encrypted, just encoded. This is an important security consideration: never store sensitive data like passwords in JWT payloads.

Try our free JWT Decoder Tool to instantly decode any JWT token in your browser.

Understanding JWT Structure

JWT Decoder & Validator

Decode and inspect JWT tokens instantly. View header, payload, and verify signatures with security validation.

Open the full JWT Decoder & Validator tool →
Loading interactive tool...

Before diving into how to decode JWTs, let's understand their structure. A JWT consists of three parts separated by periods (.):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

This breaks down into:

PartPurposeExample Decoded
HeaderAlgorithm & token type{"alg":"HS256","typ":"JWT"}
PayloadClaims (user data){"sub":"1234567890","name":"John Doe","iat":1516239022}
SignatureVerification hashCannot be decoded to readable text

The Header

The header contains metadata about the token:

{
  "alg": "HS256",
  "typ": "JWT"
}
  • alg: The signing algorithm (HS256, RS256, ES256, etc.)
  • typ: Token type (always "JWT")

The Payload (Claims)

The payload contains the actual data—called "claims." There are three types:

Registered Claims (standardized):

  • iss (issuer): Who created the token
  • sub (subject): Who the token is about (usually user ID)
  • aud (audience): Who the token is intended for
  • exp (expiration): When the token expires (Unix timestamp)
  • iat (issued at): When the token was created
  • nbf (not before): Token not valid before this time
  • jti (JWT ID): Unique identifier for the token

Public Claims: Custom claims registered with IANA (like email, name)

Private Claims: Custom claims agreed upon between parties

The Signature

The signature is created by:

  1. Encoding the header and payload
  2. Combining them with the secret key
  3. Applying the specified algorithm
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

The signature cannot be decoded into readable text—it's a cryptographic hash used only for verification.

How to Decode a JWT: Step-by-Step

Step 1: Split the Token

Separate the JWT into its three parts using the period (.) as a delimiter:

const parts = token.split('.');
const header = parts[0];
const payload = parts[1];
const signature = parts[2];

Step 2: Decode Base64URL

Each part (except the signature) is Base64URL encoded. Decode them to get JSON:

// Base64URL to Base64
function base64UrlToBase64(str) {
  return str.replace(/-/g, '+').replace(/_/g, '/');
}

// Decode
const decodedHeader = JSON.parse(atob(base64UrlToBase64(header)));
const decodedPayload = JSON.parse(atob(base64UrlToBase64(payload)));

Step 3: Parse the JSON

After decoding, you'll have JavaScript objects you can work with:

console.log(decodedHeader);
// { alg: "HS256", typ: "JWT" }

console.log(decodedPayload);
// { sub: "1234567890", name: "John Doe", iat: 1516239022 }

JWT Decode Code Examples

JavaScript (Node.js)

Using the jsonwebtoken library:

const jwt = require('jsonwebtoken');

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// Decode without verification
const decoded = jwt.decode(token);
console.log(decoded);
// { sub: '1234567890', name: 'John Doe', iat: 1516239022 }

// Decode with complete output (header + payload)
const complete = jwt.decode(token, { complete: true });
console.log(complete.header); // { alg: 'HS256', typ: 'JWT' }
console.log(complete.payload); // { sub: '1234567890', ... }

Vanilla JavaScript (Browser):

function decodeJWT(token) {
  const parts = token.split('.');
  if (parts.length !== 3) {
    throw new Error('Invalid JWT format');
  }

  const base64Url = parts[1];
  const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
  const jsonPayload = decodeURIComponent(
    atob(base64)
      .split('')
      .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
      .join('')
  );

  return JSON.parse(jsonPayload);
}

const payload = decodeJWT(token);
console.log(payload.name); // "John Doe"

Python

Using PyJWT:

import jwt

token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# Decode without verification
decoded = jwt.decode(token, options={"verify_signature": False})
print(decoded)
# {'sub': '1234567890', 'name': 'John Doe', 'iat': 1516239022}

# Get header
header = jwt.get_unverified_header(token)
print(header)
# {'alg': 'HS256', 'typ': 'JWT'}

Without external libraries:

import base64
import json

def decode_jwt(token):
    parts = token.split('.')
    if len(parts) != 3:
        raise ValueError("Invalid JWT format")

    # Add padding if needed
    payload = parts[1]
    payload += '=' * (4 - len(payload) % 4)

    decoded_bytes = base64.urlsafe_b64decode(payload)
    return json.loads(decoded_bytes)

payload = decode_jwt(token)
print(payload['name'])  # "John Doe"

Java

Using java-jwt (Auth0):

import com.auth0.jwt.JWT;
import com.auth0.jwt.interfaces.DecodedJWT;

String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

DecodedJWT decoded = JWT.decode(token);

// Access claims
String subject = decoded.getSubject();
String name = decoded.getClaim("name").asString();
Date expiresAt = decoded.getExpiresAt();

System.out.println("Subject: " + subject);
System.out.println("Name: " + name);

Using JJWT:

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.Claims;

String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

// Split and decode payload only
String[] parts = token.split("\\.");
String payload = new String(Base64.getUrlDecoder().decode(parts[1]));
System.out.println(payload);

Go

Using golang-jwt:

package main

import (
    "fmt"
    "github.com/golang-jwt/jwt/v5"
)

func main() {
    tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

    // Parse without validation
    token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{})
    if err != nil {
        panic(err)
    }

    if claims, ok := token.Claims.(jwt.MapClaims); ok {
        fmt.Println("Subject:", claims["sub"])
        fmt.Println("Name:", claims["name"])
    }
}

PHP

Using firebase/php-jwt:

<?php
use Firebase\JWT\JWT;

$token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

// Decode without verification
$parts = explode('.', $token);
$payload = json_decode(base64_decode(strtr($parts[1], '-_', '+/')), true);

print_r($payload);
// Array ( [sub] => 1234567890 [name] => John Doe [iat] => 1516239022 )

Using Online JWT Decoders

For quick debugging, online tools are invaluable. Our JWT Decoder processes tokens entirely in your browser—your JWT never leaves your device.

When to use online decoders:

  • Debugging authentication issues
  • Inspecting token structure
  • Learning JWT format
  • Quick payload inspection

Security warning: Never paste production tokens containing real user data into untrusted online tools. Use tools that process client-side only, or create test tokens for debugging.

Real-World JWT Decode Use Cases

Understanding when and why to decode JWTs helps you use them effectively in your applications.

1. Displaying User Information in UI

After login, decode the JWT to show personalized content without making additional API calls:

// Client-side: Display user info from JWT
const token = localStorage.getItem('accessToken');
const payload = jwt.decode(token);

// Update UI with user data
document.getElementById('userName').textContent = payload.name;
document.getElementById('userEmail').textContent = payload.email;
document.getElementById('userRole').textContent = payload.role;

Important: This is safe for UI display, but never use decoded claims for authorization decisions on the client. Always validate server-side.

2. Checking Token Expiration Before API Calls

Prevent unnecessary API calls by checking expiration locally:

function isTokenValid(token) {
  try {
    const payload = jwt.decode(token);
    if (!payload || !payload.exp) return false;

    // Add 10-second buffer for clock skew
    const expiresAt = payload.exp * 1000;
    return Date.now() < (expiresAt - 10000);
  } catch {
    return false;
  }
}

// Use before making API requests
async function fetchData() {
  const token = getStoredToken();

  if (!isTokenValid(token)) {
    await refreshToken(); // Get new token first
  }

  return fetch('/api/data', {
    headers: { Authorization: `Bearer ${token}` }
  });
}

3. Debugging Authentication Flows

When authentication fails, decode the JWT to identify issues:

function debugToken(token) {
  const decoded = jwt.decode(token, { complete: true });

  console.log('Algorithm:', decoded.header.alg);
  console.log('Token Type:', decoded.header.typ);
  console.log('Issuer:', decoded.payload.iss);
  console.log('Audience:', decoded.payload.aud);
  console.log('Subject:', decoded.payload.sub);
  console.log('Issued At:', new Date(decoded.payload.iat * 1000));
  console.log('Expires:', new Date(decoded.payload.exp * 1000));
  console.log('Is Expired:', Date.now() > decoded.payload.exp * 1000);
}

4. Implementing Role-Based UI (Client-Side)

Show or hide UI elements based on user roles (while enforcing on the server):

const payload = jwt.decode(token);
const userRoles = payload.roles || [];

// Show admin panel link if user has admin role
if (userRoles.includes('admin')) {
  document.getElementById('adminLink').style.display = 'block';
}

// Show premium features for subscribers
if (payload.subscription === 'premium') {
  enablePremiumFeatures();
}

5. Multi-Tenant Application Routing

In SaaS applications, decode JWTs to route users to their tenant:

const payload = jwt.decode(token);
const tenantId = payload.tenant_id;

// Route to tenant-specific subdomain or path
if (tenantId) {
  window.location.href = `https://${tenantId}.app.example.com/dashboard`;
}

JWT Libraries Comparison

Choosing the right JWT library depends on your language and requirements:

JavaScript/Node.js Libraries

LibraryDecodeVerifySignNotes
jsonwebtokenMost popular, full-featured
joseModern, supports JWE encryption
jwt-decodeLightweight, decode-only
// jsonwebtoken (recommended for Node.js)
const jwt = require('jsonwebtoken');
const decoded = jwt.decode(token);

// jose (recommended for Edge/browser)
import * as jose from 'jose';
const { payload } = jose.decodeJwt(token);

// jwt-decode (minimal, client-side)
import jwt_decode from 'jwt-decode';
const decoded = jwt_decode(token);

Python Libraries

LibraryDecodeVerifySignNotes
PyJWTMost popular, well-maintained
python-joseJOSE standard support
authlibFull OAuth/OIDC framework

Java Libraries

LibraryDecodeVerifySignNotes
java-jwt (Auth0)Simple API, well-documented
jjwtFluent API, extensive features
nimbus-jose-jwtFull JOSE support, enterprise-ready

Choosing a Library

  1. For decode-only (client-side): Use lightweight libraries like jwt-decode
  2. For full JWT handling: Use established libraries like jsonwebtoken or PyJWT
  3. For encryption (JWE): Use JOSE-compliant libraries like jose or python-jose
  4. For OAuth/OIDC flows: Consider framework libraries like authlib

JWT Decode vs JWT Verify

Understanding the difference is critical for security:

ActionWhat It DoesNeeds Secret Key?Use Case
DecodeReads token contentsNoDebugging, displaying user info
VerifyValidates signature + claimsYesAuthentication, authorization

Critical security rule: Always verify JWTs before trusting their contents for authorization decisions. Decoding alone does NOT prove the token is legitimate.

// WRONG - Security vulnerability!
const decoded = jwt.decode(token);
if (decoded.role === 'admin') {
  grantAdminAccess(); // Anyone can create a token with role: admin
}

// CORRECT - Verify first
try {
  const verified = jwt.verify(token, secretKey);
  if (verified.role === 'admin') {
    grantAdminAccess(); // Signature proves token is authentic
  }
} catch (err) {
  denyAccess();
}

Learn more about verification in our guide: How to Verify JWT Signatures.

Common JWT Decoding Errors

"Invalid token format"

Cause: Token doesn't have exactly 3 parts separated by periods.

Solution: Ensure you're copying the complete token without extra whitespace or line breaks.

// Check format before decoding
function isValidJWTFormat(token) {
  const parts = token.split('.');
  return parts.length === 3 && parts.every(part => part.length > 0);
}

"Malformed UTF-8 data"

Cause: Incorrect Base64URL decoding or encoding issues.

Solution: Use proper Base64URL decoding (replace - with + and _ with /):

function base64UrlDecode(str) {
  // Replace URL-safe characters
  str = str.replace(/-/g, '+').replace(/_/g, '/');
  // Add padding
  while (str.length % 4) str += '=';
  return atob(str);
}

"Unexpected token in JSON"

Cause: Decoded string isn't valid JSON (possibly decoding the signature).

Solution: Only decode header (index 0) and payload (index 1), not signature (index 2).

Token shows as expired

Cause: The exp claim timestamp has passed.

Solution: This isn't a decoding error—the token is legitimately expired. Request a new token from your auth server.

function isTokenExpired(token) {
  const decoded = jwt.decode(token);
  if (!decoded.exp) return false;
  return Date.now() >= decoded.exp * 1000;
}

Security Best Practices

What NOT to Store in JWTs

JWTs are encoded, not encrypted. Never include:

  • Passwords or password hashes
  • Credit card numbers
  • Social Security numbers
  • API keys or secrets
  • Sensitive personal information

Safe JWT Handling

  1. Always use HTTPS - Prevents token interception
  2. Set short expiration times - Limit damage if compromised (15-60 minutes typical)
  3. Use httpOnly cookies - Prevents XSS from stealing tokens
  4. Implement token refresh - Use refresh tokens for long sessions
  5. Validate on the server - Never trust client-side validation alone

Detecting Tampered Tokens

If someone modifies a JWT payload, the signature becomes invalid:

// Server-side verification catches tampering
try {
  const verified = jwt.verify(token, process.env.JWT_SECRET);
  // Token is authentic and unmodified
} catch (err) {
  if (err.name === 'JsonWebTokenError') {
    console.log('Token was tampered with or invalid');
  }
}

JWT Decode FAQ

Is it safe to decode JWTs online?

Yes, if the tool processes tokens client-side (in your browser). Our JWT Decoder never sends your token to any server. However, avoid pasting production tokens with real user data into untrusted tools.

Can anyone decode my JWT?

Yes. JWTs are encoded (Base64URL), not encrypted. Anyone with the token can read the header and payload. This is why you should never store sensitive data in JWTs and always use HTTPS.

Why can't I decode the signature?

The signature is a cryptographic hash, not encoded data. It's designed to be verified (compared), not decoded. You need the secret key to verify it matches the expected value.

How do I decode a JWT in the browser console?

JSON.parse(atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')))

What's the difference between Base64 and Base64URL?

Base64URL is URL-safe: it replaces + with - and / with _, and may omit padding (=). JWTs use Base64URL because tokens are often passed in URLs.

Learn more: Base64URL and Base64 Variants Explained

JWT Decode Troubleshooting Checklist

When JWT decoding isn't working as expected, run through this checklist:

  1. Verify token format: Ensure exactly 3 parts separated by periods
  2. Check for whitespace: Trim any leading/trailing spaces or newlines
  3. Confirm encoding: JWTs use Base64URL, not standard Base64
  4. Test with known-good token: Use a sample JWT to verify your code works
  5. Check character encoding: Ensure UTF-8 encoding throughout
  6. Verify JSON parsing: The decoded string must be valid JSON
  7. Handle padding: Add = padding characters if missing (some libraries require this)
// Comprehensive JWT validation before decoding
function validateAndDecode(token) {
  // Remove whitespace
  token = token.trim();

  // Check format
  const parts = token.split('.');
  if (parts.length !== 3) {
    throw new Error('Invalid JWT: must have 3 parts');
  }

  // Validate each part has content
  if (parts.some(p => p.length === 0)) {
    throw new Error('Invalid JWT: empty segment');
  }

  // Decode and parse
  try {
    return JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
  } catch (e) {
    throw new Error(`JWT decode failed: ${e.message}`);
  }
}

Conclusion

JWT decode is a fundamental skill for any developer working with modern authentication. Whether you're debugging auth flows, inspecting token claims, or building integrations, understanding how to properly decode JWTs saves hours of troubleshooting.

Key takeaways:

  1. JWTs have three parts: header, payload, and signature
  2. Decoding reveals contents but doesn't verify authenticity
  3. Always verify signatures before trusting token claims
  4. Never store sensitive data in JWT payloads
  5. Use our JWT Decoder Tool for quick, secure decoding

Need to decode a JWT right now? Try our free JWT Decoder—it works entirely in your browser with no data sent to servers.

Frequently Asked Questions

Find answers to common questions

To decode a JWT token, split it by periods (.) into three parts: header, payload, and signature. The header and payload are Base64URL encoded - decode them using atob() in JavaScript, base64.urlsafe_b64decode() in Python, or our free JWT Decoder tool. The signature cannot be decoded as it's a cryptographic hash. Note: Decoding only reads the token contents - it doesn't verify authenticity.

JWT decode simply reads the token's contents by Base64URL decoding the header and payload - anyone can do this without a secret key. JWT verify validates the signature using the secret key to confirm the token is authentic and unmodified. Always verify JWTs before trusting their claims for authorization decisions. Learn more about what JWT tokens are.

Yes, you can decode a JWT without the secret key. JWTs are encoded (Base64URL), not encrypted. The header and payload are readable by anyone with the token. However, you cannot verify the signature without the secret key. This is why sensitive data like passwords should never be stored in JWT payloads.

In JavaScript, decode a JWT using: JSON.parse(atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'))) for the payload. For Node.js, use the jsonwebtoken library: jwt.decode(token). For browser applications, the lightweight jwt-decode library provides a simple jwt_decode(token) function. Or use our JWT Decoder for quick debugging.

JWT decode returns null when the token format is invalid (doesn't have 3 parts separated by periods), contains whitespace or newlines, has malformed Base64URL encoding, or the decoded string isn't valid JSON. Trim the token, verify it has exactly 3 parts, and check for hidden characters before decoding. Use our Base64 decoder to troubleshoot encoding issues.

Decode the JWT payload and check the 'exp' (expiration) claim, which is a Unix timestamp in seconds. Compare it to the current time: const isExpired = Date.now() >= decoded.exp * 1000. If the current time exceeds the exp value, the token is expired and should be refreshed or the user should re-authenticate.

Building Something Great?

Our development team builds secure, scalable applications. From APIs to full platforms, we turn your ideas into production-ready software.