Developer Tools

Decode JWT Tokens Without a Library — 2026 Guide

8 min read

Decode JWT tokens in plain JavaScript without installing any package. Step-by-step guide covering base64url decoding, claim inspection, and signature verification.

Executive Summary

"You don't need npm packages like jwt-decode to read a JWT token. A JWT is simply three base64url strings. You can split it, fix the padding, and decode the payload natively using JavaScript's atob() and JSON.parse()."

Up-to-date Feed

View All
Tools

JSON Formatter vs jq: Which Should You Use in 2026?

Read Now
Security

Calculate Password Entropy Bits — Complete Guide

Read Now
Developer Tools

CSV to JSON With Nested Objects — 2026 Guide

Read Now
Developer Tools

Decode JWT Tokens Without a Library — 2026 Guide

Read Now
Developer Tools

Generate JWT Tokens Free — Offline Tool Guide

Read Now
Developer Tools

JSON to Pydantic Model Generator — Python 2026

Read Now
Developer Tools

JSON to TypeScript Interface — Free Converter Guide

Read Now
Developer Tools

JSON to YAML Converter — Free Offline Tool 2026

Read Now
Developer Tools

JWT Token Expiry Error Fix — Node.js 2026

Read Now
Engineering

JWT vs Session Cookies 2026 — Which to Use?

Read Now
Developer Tools

Validate JSON Format Online — Free Instant Tool

Read Now
SEO & Performance

The Complete Core Web Vitals Guide (2026 Edition)

Read Now
SEO & Performance

The Ultimate Technical SEO Audit Checklist

Read Now
SEO Tools

301 vs 302 vs 307 Redirects: HTTP & SEO Engineering Guide

Read Now
Developer Tools

Cron Syntax Reference: Evaluating Fields and Operators

Read Now
Design Tools

Favicon Sizes in 2026: The Complete Asset Manual

Read Now
Tutorials

JS Regex Cheat Sheet: ECMA-262 Reference & Catastrophic Backtracking

Read Now
Security

Privacy First: The Architecture of Zero-Knowledge Client-Side Web Utilities

Read Now
Research

Achieving a 3ms TTFB: Edge Caching & Core Web Vitals (2026)

Read Now
Engineering

Base64 Encoding Architecture: Binary Data, API Bloat, and the V8 Engine Crash

Read Now
Security

Enterprise Web Security: Zero-Trust Architectures, OWASP Mitigations, and Threat Defense

Read Now
Tutorials

Optimizing Core Web Vitals for Enterprise Next.js Applications

Read Now
Engineering

JSON Serialization Architecture: RFC 8259, V8 Fast-Paths, & The 500MB Node Crash

Read Now
Engineering

The Complete Meta Tags Guide: SEO, Social & AI Directives

Read Now
Engineering

The Unified Diff Format: Parsing Engines, Mathematical Invariants, & Patch Architecture

Read Now
Security

How Secure is My Password? Entropy & GPU Cracking Guide

Read Now
Engineering

URL Slug Optimization 2026: Linux Case-Sensitivity, CTR Truncation & The 404 Incident

Read Now
CSS

Modern CSS Architecture for Enterprise: Component Scoping, Cascade Layers (@layer), and Tailwind Tokenization

Read Now
Tutorials

AI-First Development in 2026: Cursor, Claude, and GitHub Copilot Setup

Read Now
SEO Tools

.htaccess Guide 2026: Security Hardening & Redirect Rules

Read Now

✓ Last tested: May 2026 · Verified against RFC 7519

How to Decode a JWT Token Without Any Library

1. Field Notes: The Phantom Auth Bug

It was 2023, and I was debugging a legacy React application that kept unexpectedly logging users out. The app relied on a third-party authentication service that issued JSON Web Tokens (JWTs).

The console was flooded with 401 Unauthorized errors, but the tokens looked fine. They were present in localStorage, and the network requests were sending them in the Authorization: Bearer header.

My first instinct was to npm install jwt-decode to inspect the token's expiry. But the project had a strict dependency policy, and every new package required a security review that could take days.

I realized: A JWT token is just three base64url strings separated by dots. You don't need jwt-decode, jsonwebtoken, or any npm package to read it. I opened the Chrome DevTools console and wrote a quick snippet to slice the token, decode the base64 payload using atob(), and parse the JSON.

const payload = JSON.parse(atob(token.split('.')[1]));
console.log(new Date(payload.exp * 1000));

The issue became immediately clear: the exp (expiry) claim was set to just 5 minutes, but the frontend was trying to refresh the token every 10 minutes. The fix was a one-line configuration change. This experience taught me a valuable lesson: understand the underlying data structures before reaching for a library.

A JSON Web Token (JWT) is a compact, URL-safe token format defined in RFC 7519. It consists of three parts: Header.Payload.Signature, each base64url-encoded.


2. What Is Inside a JWT Token?

Answer: A JWT contains three parts separated by dots: the Header (metadata about the token), the Payload (the actual data or claims), and the Signature (used to verify the token hasn't been tampered with).

The Header — Algorithm and Type

The header typically consists of two parts: the type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 (HS256) or RSA (RS256). When base64url encoded, this forms the first part of the JWT.

{
  "alg": "HS256",
  "typ": "JWT"
}

The Payload — Claims and Expiry

The second part of the token is the payload, which contains the claims. Claims are statements about an entity (typically, the user) and additional data. There are registered, public, and private claims.

{
  "sub": "1234567890",
  "name": "Abu Sufyan",
  "admin": true,
  "exp": 1717200000
}

The Signature — What It Proves (and What It Doesn't)

To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that. The signature is used to verify the message wasn't changed along the way. It does not encrypt the payload. Anyone who intercepts the token can read the header and payload, which is why you must never put secrets (like passwords) in a JWT.


3. How to Decode a JWT in Plain JavaScript (No Library)

You can decode a JWT natively in any modern browser console or Node.js environment (v16+) without installing any dependencies. Here is the step-by-step process.

Step 1 — Split the Token Into Three Parts

A JWT is delimited by periods (.). We can use the JavaScript String.prototype.split() method to separate the components.

const token = "eyJhbGciOiJIUzI1NiIsInR5cCI... (truncated)";
const [headerB64, payloadB64, signatureB64] = token.split('.');

Step 2 — Base64URL Decode Each Part

JWTs use Base64URL encoding, not standard Base64. This means + is replaced by -, / is replaced by _, and trailing padding = characters are omitted. To decode it using the browser's atob() function, we need to convert it back to standard Base64 by fixing the characters and adding the necessary padding.

function base64UrlDecode(str) {
  // Replace non-url compatible chars with base64 standard chars
  str = str.replace(/-/g, '+').replace(/_/g, '/');
  
  // Pad out with standard base64 required padding characters
  const pad = str.length % 4;
  if (pad) {
    if (pad === 1) {
      throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
    }
    str += new Array(5 - pad).join('=');
  }

  // Decode base64 to string and handle UTF-8 characters properly
  return decodeURIComponent(escape(atob(str)));
}

Note on Node.js: If you are in Node.js, you can simply use Buffer.from(str, 'base64url').toString('utf-8').

Step 3 — Parse the JSON and Read Claims

Once we have the decoded JSON string, we can parse it into a JavaScript object.

const payloadStr = base64UrlDecode(payloadB64);
const payloadObj = JSON.parse(payloadStr);

console.log(payloadObj);
// Output: { sub: "1234567890", name: "Abu Sufyan", admin: true, exp: 1717200000 }

Step 4 — Check the exp Claim for Expiry

One of the most common reasons to decode a JWT on the frontend is to check if it's expired before making an API request. The exp claim is a Unix timestamp in seconds.

function isTokenExpired(payloadObj) {
  if (!payloadObj.exp) return false; // Token doesn't expire
  
  const expiryTimeMs = payloadObj.exp * 1000;
  return Date.now() > expiryTimeMs;
}

console.log("Is token expired?", isTokenExpired(payloadObj));

4. Why You Cannot Verify the Signature Without the Secret

Answer: Verifying a signature requires the cryptographic key used to create it. For symmetric algorithms (HS256), you need the exact same secret. For asymmetric algorithms (RS256), you need the public key corresponding to the private key used for signing. Without the key, you can only *read* the token, not *trust* it.

It is critical to understand the distinction between decoding and verifying.

Feature JWT Decode JWT Verify
Action Translates Base64URL to JSON Re-calculates signature and compares it
Requires Key? No Yes (Secret or Public Key)
Environment Frontend (Browser) or Backend Backend (Server)
Security Risk Safe (Just reading data) Critical (Validates authenticity)
Use Case Extracting UI state, checking expiry Authenticating API requests

Never authorize an action on the backend based solely on a decoded JWT payload. You must always verify the signature first.


5. Common JWT Decode Errors and Fixes

When writing your own JWT decoding logic, you might encounter a few common errors.

"Invalid base64" — Padding Issue Fix

If you pass a raw base64url string to atob(), it will throw a DOMException: The string to be decoded is not correctly encoded. This happens because atob() expects standard Base64 padding (the = characters). Always ensure your base64UrlDecode function appends the necessary padding, as demonstrated in Step 2.

"Unexpected token" — Malformed Payload Fix

If JSON.parse() throws a SyntaxError: Unexpected token, it means the decoded string is not valid JSON. This usually occurs if the token is truncated, corrupted, or if UTF-8 characters were not handled correctly during decoding. Using decodeURIComponent(escape(atob(str))) is a robust way to prevent UTF-8 parsing issues in the browser.

Token Shows Expired But It Shouldn't — Clock Skew Explanation

Sometimes a token appears expired locally but works on the server. This is caused by clock skew — your user's local device time is slightly ahead of or behind the server's time. When checking expiry locally, it's best practice to allow a small buffer (e.g., 60 seconds) for clock skew.

const CLOCK_SKEW_MS = 60000;
const isExpired = Date.now() > (payloadObj.exp * 1000) + CLOCK_SKEW_MS;

6. JWT Decode vs JWT Verify — Key Differences

To summarize the differences and when to use each approach:

Action Environment Needs Library? Needs Key? Purpose
Inspect Offline/Tools No No Debugging token structure and contents.
Decode Frontend No No Reading claims (like User ID or Expiry) for UI logic.
Verify (HS256) Backend Yes Yes (Secret) Ensuring the token was signed by your server.
Verify (RS256) Backend Yes Yes (Public Key) Ensuring the token was signed by a trusted issuer.
Refresh Backend/Frontend N/A N/A Obtaining a new token when the current one expires.

7. Best Practices for Handling JWTs in 2026

When working with JSON Web Tokens, adherence to security best practices is non-negotiable.

  1. Never Store Sensitive Data in the Payload: JWTs are encoded, not encrypted. Anyone can read the payload. Do not include passwords, social security numbers, or sensitive PII.
  2. Keep Tokens Short-Lived: Use an exp claim of 15 minutes or less for access tokens. Use secure refresh tokens to maintain sessions.
  3. Validate on the Backend: Always use a robust library (like jsonwebtoken in Node) on your server to verify the signature before processing requests.
  4. Avoid localStorage: Storing JWTs in localStorage makes them vulnerable to Cross-Site Scripting (XSS) attacks. Store them in memory or in HttpOnly cookies if possible.
  5. Check Expiry Locally: Decode the token on the frontend to check the exp claim and preemptively refresh it before making an API call.
  6. Use Asymmetric Keys (RS256): For distributed systems, use RS256 instead of HS256. It allows other services to verify the token using a public key without exposing the private signing key.

Use our free Offline JWT Decoder to inspect tokens instantly without any server round-trips. Use our free Offline JWT Decoder to decode headers and payloads safely in your browser →

Also, check out our JWT signing guide for backend implementations, our password entropy tester to ensure strong credentials, and our hash generator for cryptographic utilities.


External Sources


Abu Sufyan · Full-stack developer · Founder of WebToolkit Pro Github

Last updated: May 2026

Expert Recommendations

Pro Insights

  • 01.Never trust the decoded payload of a JWT for authorization without first verifying the signature on the backend.
  • 02.Use `decodeURIComponent(escape(atob(str)))` to correctly handle UTF-8 characters when decoding base64 in the browser.
  • 03.Always check the `exp` (expiry) claim locally to prevent unnecessary API calls with expired tokens.

Frequently Asked Questions

Q. Can I decode a JWT without the secret key?

Yes. The header and payload of a JWT are merely base64url encoded, not encrypted. Anyone can decode and read the contents of a JWT without needing the secret key.

Q. Is a decoded JWT safe to display to users?

Yes, provided the JWT doesn't contain sensitive information like passwords or PII. You should never store highly sensitive data in a JWT payload anyway.

Q. What is the difference between HS256 and RS256 in JWTs?

HS256 is a symmetric algorithm using a single secret key to both sign and verify. RS256 is asymmetric, using a private key to sign the token and a public key to verify it.

Q. How do I check if a JWT is expired in JavaScript?

Extract the `exp` claim from the payload and multiply it by 1000 (since it's in seconds). Compare it against `Date.now()`. If `exp * 1000 < Date.now()`, the token is expired.

#JWT#JavaScript#Authentication#Security
AS

Abu Sufyan

Lead Systems Architect & Performance Engineer

Abu Sufyan specializes in V8 execution benchmarking, React architecture, and enterprise-grade technical SEO.

Blog & Journal Archive

All Entries →