How to Decode a JWT Token

Learn how to decode JWT tokens step by step. Complete tutorial with JavaScript code examples for parsing, validating, and reading JWT claims.

Building a RAG system? Diagnose failures automatically at rag-debugger.pages.dev →

How to Decode a JWT Token

JWT (JSON Web Token) decoding is a fundamental skill for modern web development. Whether you're debugging authentication issues or inspecting token contents, knowing how to decode JWT tokens is essential. This tutorial covers manual decoding, JavaScript libraries, and security considerations.

Understanding JWT Structure

A JWT consists of three parts separated by dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

| Part | Description | Encoding | |------|-------------|----------| | Header | Algorithm and token type | Base64url | | Payload | Claims and user data | Base64url | | Signature | Verification hash | Base64url |

Method 1: Manual Decoding (No Libraries)

You can decode JWT tokens manually using built-in browser functions:

function decodeJWT(token) {
  try {
    const [header, payload, signature] = token.split('.');

// Base64url decode const decodedHeader = JSON.parse(atob(header.replace(/-/g, '+').replace(/_/g, '/'))); const decodedPayload = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));

return { header: decodedHeader, payload: decodedPayload, signature: signature }; } catch (error) { console.error('Invalid JWT:', error.message); return null; } }

// Usage const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; const decoded = decodeJWT(token); console.log(decoded);

Output:

{
  header: { alg: "HS256", typ: "JWT" },
  payload: { sub: "1234567890", name: "John Doe", iat: 1516239022 },
  signature: "SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}

Understanding Base64url

JWT uses Base64url encoding (RFC 4648), not standard Base64:

| Standard Base64 | Base64url | |-----------------|-----------| | + | - | | / | _ | | = (padding) | (omitted) |

That's why we replace - with + and _ with / before decoding.

Method 2: Using jwt-decode Library

For production code, use a dedicated library:

npm install jwt-decode
import jwtDecode from 'jwt-decode';

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; const decoded = jwtDecode(token);

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

###jwt-decode Features

  • Handles edge cases automatically
  • Works in browser and Node.js
  • Small bundle size (~5KB)
  • No dependencies

Method 3: Using jsonwebtoken (Node.js)

For server-side verification:

npm install jsonwebtoken
const jwt = require('jsonwebtoken');

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'; const secret = 'your-secret-key';

// Decode without verification (just reading payload) const decoded = jwt.decode(token);

// Decode AND verify try { const verified = jwt.verify(token, secret); console.log(verified); } catch (error) { console.error('Invalid token:', error.message); }

Common JWT Claims

When decoding JWTs, you'll encounter these standard claims:

| Claim | Name | Description | |-------|------|-------------| | iss | Issuer | Who issued the token | | sub | Subject | User identifier | | aud | Audience | Intended recipient | | exp | Expiration | When token expires (Unix timestamp) | | iat | Issued At | When token was created | | nbf | Not Before | Token becomes valid at | | jti | JWT ID | Unique token identifier |

Custom Claims

{
  "sub": "user-123",
  "email": "[email protected]",
  "role": "admin",
  "permissions": ["read", "write", "delete"],
  "iat": 1710345600,
  "exp": 1710432000
}

Checking Token Expiration

A common task is checking if a JWT has expired:

function isTokenExpired(token) {
  try {
    const decoded = jwtDecode(token);
    if (!decoded.exp) return false; // No expiration set

const now = Math.floor(Date.now() / 1000); return decoded.exp < now; } catch { return true; // Invalid token = expired } }

// Usage if (isTokenExpired(token)) { console.log('Token expired, please refresh'); }

Human-Readable Expiration

function formatExpiration(token) {
  const decoded = jwtDecode(token);
  if (!decoded.exp) return 'No expiration';

const expDate = new Date(decoded.exp 1000); const now = new Date(); const diff = expDate - now;

if (diff < 0) { return Expired ${Math.abs(Math.floor(diff / 60000))} minutes ago; }

const hours = Math.floor(diff / 3600000); const minutes = Math.floor((diff % 3600000) / 60000);

return Expires in ${hours}h ${minutes}m; }

Complete JWT Decoder Utility

class JwtDecoder {
  static decode(token) {
    try {
      const [headerB64, payloadB64, signature] = token.split('.');

const header = JSON.parse( atob(headerB64.replace(/-/g, '+').replace(/_/g, '/')) );

const payload = JSON.parse( atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/')) );

return { header, payload, signature, isExpired: payload.exp ? payload.exp < Date.now() / 1000 : false, expiresIn: payload.exp ? payload.exp 1000 - Date.now() : null }; } catch (error) { throw new Error(Invalid JWT: ${error.message}); } }

static getClaim(token, claim) { const { payload } = this.decode(token); return payload[claim]; }

static getTimeUntilExpiry(token) { const { payload } = this.decode(token); if (!payload.exp) return Infinity; return payload.exp * 1000 - Date.now(); } }

// Usage const decoded = JwtDecoder.decode(token); console.log(User: ${decoded.payload.sub}); console.log(Expires: ${decoded.expiresIn}ms);

Security Considerations

⚠️ Important: Decoding a JWT is NOT the same as verifying it.

  • Decoding just reads the payload (anyone can do this)
  • Verifying checks the signature (requires secret key)
// ⚠️ This does NOT verify the token!
const decoded = jwtDecode(token); // Payload could be forged

// ✅ This verifies the signature const verified = jwt.verify(token, secret); // Throws if invalid

Never trust decoded payload data without verification.

Try It Online

Need to quickly inspect a JWT? Use our JWT Decoder for instant decoding with expiration countdown and syntax highlighting.

Conclusion

Decoding JWT tokens is straightforward with jwt-decode library or manual Base64url decoding. Remember to always verify tokens before trusting their contents.

For related tutorials, see How to Encode Base64 in JavaScript which explains the encoding used in JWTs.

---

Related Tools:

🚀 Deploy Your Own Tools — Recommended Hosting

Want to self-host or build your own developer tools? These are the platforms we use:

🌐
Hostinger
Web Hosting from $2.99/mo
💧
DigitalOcean
$200 Free Credit for New Users
🔑
Namecheap
Domains from $0.99/yr

* Affiliate links — we may earn a commission at no extra cost to you.

😓 Tired of Online Tools That Don't Work Offline?

Get the complete offline toolkit. Perfect for flights, remote work, or privacy-focused development. No internet? No problem.

✈️

Offline Pack

82 tools, zero internet needed

🎒 Perfect for 100+ remote devs

$9 Get Offline →
📦

Content Pack

100 article templates ready

🎒 Perfect for 100+ remote devs

$19 Get Pack →
💰

Complete Bundle

All 3 products, save $18

🔥 Best value

$33 $15
Get All →

🔒 Works on flights, trains, remote cabins. Privacy-first, zero tracking.