How to decode a JWT safely in 2026 (no signature verification)

Learn how to read JWT header and payload without needing the signing secret, when this is safe, and when it isn't.

J
Jan Stepien·

A JSON Web Token (JWT) is a compact, URL-safe way to represent claims. It has three base64url-encoded parts — header, payload, signature — separated by dots. Understanding how to decode the first two parts safely (without having the signing secret) is a fundamental skill for any developer working with modern APIs.

What's in a JWT?

The header is a JSON object describing the algorithm (alg) and token type (typ). Common algorithms include HS256 (HMAC with SHA-256), RS256 (RSA with SHA-256), and ES256 (ECDSA with P-256 and SHA-256).

The payload contains claims — key-value pairs about the subject. Registered claims include sub (subject), iss (issuer),exp (expiration time in Unix epoch seconds), iat (issued at), andaud (audience). Custom claims can be anything your application needs.

The signature ensures the token hasn't been tampered with. For HS256 it's HMAC(header + "." + payload, secret). Without the secret (or public key for RS*/ES*), you can read the claims but cannot confirm authenticity.

When is decoding without verification safe?

Decoding without verification is safe when you're inspecting a token for debugging purposes, reading non-sensitive claims like a username to display in a UI (after the token has already been verified server-side), or building developer tools that need to introspect arbitrary tokens.

It is not safe for access control decisions. Never grant permissions based on a decoded-but-unverified JWT. An attacker can craft a token with any claims they like — only the signature prevents this, and only after verification.

Decoding in JavaScript

Base64url decoding in the browser is straightforward:

function decodeJwtPart(part) {
  // Base64url → Base64 → JSON
  const b64 = part.replace(/-/g, '+').replace(/_/g, '/');
  return JSON.parse(atob(b64));
}

const [headerB64, payloadB64] = token.split('.');
const header = decodeJwtPart(headerB64);
const payload = decodeJwtPart(payloadB64);

Or just use quickhelp.dev's JWT Decoder — paste any JWT and the header + payload appear instantly, with optional signature verification.

Common pitfalls

  • Padding: Base64url omits trailing =. Add them back: part + '===').slice(0, len + (4 - len % 4) % 4) or useatob with a replace.
  • Unicode: atob expects ASCII; for Unicode payloads, decode to bytes first: decodeURIComponent(escape(atob(b64))) or use the TextDecoder API.
  • Expired tokens: Check exp manually — payload.exp * 1000 > Date.now(). Decoding doesn't enforce expiry.
  • Algorithm confusion: If you verify, always specify the expected algorithm explicitly. Never allow the alg header to dictate the verification method.

Try it now

JWT Decoder on quickhelp.dev decodes any token instantly in your browser — no network call for the decode step. The optional signature verification sends the token and key to our API (via HTTPS), verifies, and returns the result. Nothing is logged or stored.

We use cookies to serve ads and measure traffic. Cookie policy · Privacy policy