How to Decode a JWT Token Safely (JavaScript, Python, and Online)

Every developer working with modern APIs eventually stares at a long string starting with eyJ and wonders what is inside it. That string is a JSON Web Token (JWT), and decoding it is easy — it is just base64url-encoded JSON. But there are two things many tutorials skip: decoding a token is not the same as verifying it, and a JWT is a live credential that should never be pasted into a website that ships it to a server. This guide covers how to decode a JWT in JavaScript and Python, and how to do it safely.

A JWT Is Three Base64url Parts Joined by Dots

A JWT looks like xxxxx.yyyyy.zzzzz — three segments separated by dots:

  • Header — JSON describing the signing algorithm, e.g. {"alg":"HS256","typ":"JWT"}
  • Payload — JSON claims: who the user is, when the token expires, custom data
  • Signature — raw bytes produced by signing the first two parts with a secret or private key

The header and payload are encoded with base64url, a URL-safe variant of base64. It replaces + with -, replaces / with _, and drops the trailing = padding. That is exactly why a naive atob() call sometimes throws an error on a valid token — you have to convert base64url back to standard base64 first.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9        <- header  (base64url JSON)
.eyJzdWIiOiIxMDAxIiwibmFtZSI6IlJhdmkiLCJleHAiOjE3ODM0MjU2MDB9   <- payload
.k5Jf3mZQx9VbW1cAqT0dO4nHc2xLmR8sYp6uE_wG1jU  <- signature (binary, not JSON)

Decoding a JWT in JavaScript (No Library Needed)

In the browser or Node.js 16+, you can decode a JWT with a few lines. The key steps are: split on dots, fix the base64url characters, add padding back, then decode and parse.

function decodeJwtPart(part) {
  // 1. base64url -> base64
  let b64 = part.replace(/-/g, '+').replace(/_/g, '/');
  // 2. restore padding (length must be a multiple of 4)
  while (b64.length % 4 !== 0) b64 += '=';
  // 3. decode and handle UTF-8 correctly
  const json = decodeURIComponent(
    atob(b64)
      .split('')
      .map((c) => '%' + c.charCodeAt(0).toString(16).padStart(2, '0'))
      .join('')
  );
  return JSON.parse(json);
}

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDAxIiwibmFtZSI6IlJhdmkifQ.sig';
const [headerB64, payloadB64] = token.split('.');

console.log(decodeJwtPart(headerB64));   // { alg: 'HS256', typ: 'JWT' }
console.log(decodeJwtPart(payloadB64));  // { sub: '1001', name: 'Ravi' }

In Node.js you can skip atob() entirely, because Buffer understands base64url natively:

const payload = JSON.parse(
  Buffer.from(token.split('.')[1], 'base64url').toString('utf8')
);
console.log(payload);

Decoding a JWT in Python

Python's standard library handles this cleanly with base64.urlsafe_b64decode. The only trick is restoring the stripped padding:

import base64
import json

def decode_jwt_part(part: str) -> dict:
    padded = part + '=' * (-len(part) % 4)   # restore padding
    raw = base64.urlsafe_b64decode(padded)
    return json.loads(raw)

token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDAxIn0.sig"
header_b64, payload_b64, signature_b64 = token.split('.')

print(decode_jwt_part(header_b64))    # {'alg': 'HS256', 'typ': 'JWT'}
print(decode_jwt_part(payload_b64))   # {'sub': '1001'}

If you already use the PyJWT library, you can decode without verification explicitly — note how the API forces you to acknowledge that signature checking is off:

import jwt  # pip install PyJWT

claims = jwt.decode(token, options={"verify_signature": False})
print(claims)  # payload dict — UNVERIFIED, for inspection only

Decoding Is Not Verifying — Never Trust a Decoded Token

This is the single most important thing to understand. Base64url is an encoding, not encryption and not a signature check. Anyone can decode a JWT, and anyone can construct one with any claims they like. What makes a JWT trustworthy is the signature, and checking the signature requires the secret key (HS256) or the public key (RS256).

  • Decoding — reading the JSON inside. Safe for debugging, useless for security decisions.
  • Verifying — recomputing the signature and comparing it, plus checking expiry, issuer, and audience. This is what your server must do on every request.

A classic vulnerability is a backend that calls jwt.decode() (or reads the payload manually) and trusts the role or userId claim without verification. An attacker just edits the payload, re-encodes it, and becomes an admin. Always use jwt.verify() in Node.js or jwt.decode(token, key, algorithms=[...]) in PyJWT on the server side.

Why You Should Not Paste Production Tokens into Random Websites

A JWT is a bearer token: whoever holds it can use it. If you paste a live production token into an online decoder that sends it to a server — for logging, analytics, or worse — that token can be replayed against your API until it expires. Treat a JWT with the same care as a password that auto-expires.

  • Prefer client-side decoders — tools that decode with JavaScript in your browser and make no network request with the token. The Dev Brains AI JWT Decoder works this way.
  • Check the network tab — open DevTools while decoding; you should see zero requests containing the token.
  • Use expired or staging tokens — when sharing a token in a bug report or Slack message, use one from a test environment or one that has already expired.
  • Rotate if leaked — if a live token was pasted somewhere questionable, revoke the session or wait out the expiry, and rotate the signing secret if the token was long-lived.

Quick Reference: Safe JWT Decoding Checklist

  • Split on . — expect exactly three parts
  • Convert base64url to base64 (- to +, _ to /) and re-add = padding
  • Decode header and payload only — the signature is binary
  • Remember: expiry (exp) is in unix seconds, so multiply by 1000 for JavaScript dates
  • Never make authorization decisions from decoded-but-unverified claims
  • Only paste tokens into client-side tools, and prefer non-production tokens

Frequently Asked Questions

How do I decode a JWT token without a library?

Split the token on the dot character, take the first two parts, convert base64url to base64 by replacing - with + and _ with /, then base64-decode and JSON.parse the result. The signature (third part) is binary and cannot be decoded to JSON.

Is it safe to paste a JWT into an online decoder?

Only if the tool decodes entirely in your browser and never sends the token to a server. A JWT is a live credential — anyone who captures it can impersonate you until it expires. Use client-side tools like the Dev Brains AI JWT Decoder, and prefer expired or test tokens when possible.

Does decoding a JWT verify that it is valid?

No. Decoding only reads the base64url-encoded header and payload, which anyone can do. Verification requires checking the cryptographic signature against the secret or public key on the server. Never trust claims from a token you have only decoded.

Try the Free JWT Decoder

Decode any JWT's header, payload, and expiry instantly — 100% in your browser. Your token never leaves your machine. No signup, no cost.

Related articles