How to Decode a JWT Token Online

Last updated:

A JWT has 3 dot-separated base64url-encoded parts: header, payload, and signature. Decoding the payload requires no library — split on ., replace -+ and _/, then call atob() and JSON.parse() in 4 lines. Decoding is not verification — anyone with the token can read the payload; the 256-byte RS256 signature only proves the token was not tampered with. The 5 standard payload claims to check when debugging a 401 are sub, exp (Unix seconds), iat, aud, and iss. This guide covers 5 signing algorithms (HS256, RS256, ES256, EdDSA, none), decode patterns in the browser and Node.js, expiry checking, and when to verify instead of decode.

What is a JWT?

A JSON Web Token (JWT) is a compact, URL-safe string used to represent claims between two parties. In practice, you'll encounter them as authentication tokens: your app logs in, the server returns a JWT, and you send that token in the Authorization header on every subsequent request.

A JWT looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsIm5hbWUiOiJBbGljZSIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcwNTMxMjIwMCwiZXhwIjoxNzA1Mzk4NjAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Three base64url-encoded parts, separated by dots. They are not encrypted — they are signed. Anyone can decode and read the contents. The signature only proves the token hasn't been tampered with.

The three parts of a JWT

Part 1: Header

The first segment contains metadata about the token itself — specifically, which signing algorithm was used.

// Encoded:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

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

Common values for alg: HS256 (HMAC-SHA256, symmetric),RS256 (RSA-SHA256, asymmetric), ES256 (ECDSA). If you're integrating with an OAuth provider, you'll often see RS256.

Part 2: Payload

The payload contains the actual claims — data the token is asserting. There are registered claim names with well-known meanings, plus custom claims your app defines.

// Encoded:
eyJzdWIiOiJ1c2VyXzEyMyIsIm5hbWUiOiJBbGljZSIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTcwNTMxMjIwMCwiZXhwIjoxNzA1Mzk4NjAwfQ

// Decoded:
{
  "sub": "user_123",
  "name": "Alice",
  "role": "admin",
  "iat": 1705312200,
  "exp": 1705398600
}

Standard registered claims:

  • sub — subject: the user or entity the token refers to
  • iat — issued at: Unix timestamp when the token was created
  • exp — expiration: Unix timestamp after which the token is invalid
  • iss — issuer: who created the token
  • aud — audience: who the token is intended for
  • jti — JWT ID: a unique identifier for the token (used to prevent replay)

Part 3: Signature

The signature is computed by hashing the encoded header + encoded payload with a secret key (for HS256) or a private key (for RS256/ES256). The server validates the signature on every request to confirm the token is authentic and unmodified.

Decoding a JWT does not verify the signature — that requires the secret or public key. Decoding only unpacks the base64url encoding to reveal the JSON contents.

JWT signing algorithms compared

The alg claim in the JWT header names the signing algorithm. Five algorithms cover almost all production tokens; the choice between them is a tradeoff of key type, signature size, and rotation cost.

algFamilyKey typeSignature sizeBest for
HS256HMAC + SHA-256Shared secret32 bytesSame-service tokens, internal APIs
RS256RSA + SHA-256RSA keypair (2048-bit)256 bytesThird-party verification (JWKS)
ES256ECDSA + SHA-256 (P-256)EC keypair64 bytesMobile, low bandwidth
EdDSAEd25519EC keypair64 bytesFastest verification, modern stacks
noneNone0 bytesNever in production — reject server-side

When to decode a JWT

Debugging authentication errors

Getting a 401 on an authenticated request? Decode the token and check:

  • Is exp in the past? The token expired.
  • Is aud set to the wrong service? The token is for a different API.
  • Is sub what you expect? You might be using a token from the wrong user.
  • Is the role or permission claim missing? Your auth middleware might not have included it.

Checking expiry

The exp claim is a Unix timestamp (seconds since epoch). To convert it manually:

// exp: 1705398600
new Date(1705398600 * 1000).toISOString()
// → "2024-01-16T10:30:00.000Z"

A good JWT decoder shows this as a human-readable date automatically, saving you the conversion.

Inspecting claims from OAuth providers

When integrating Google, Auth0, Okta, or another OAuth provider, the ID token or access token contains claims your app needs (email, name, roles, scopes). Decoding lets you verify the provider is sending exactly what you expect before writing the code to consume it.

Security: never paste production tokens into untrusted tools

This is important. A JWT issued in production is a valid credential. Pasting it into a random online tool means:

  • The tool's server might log it
  • The payload (including user data) is now on someone else's system
  • If the token hasn't expired, it can be replayed

For production tokens, either decode them locally in your browser's DevTools console —atob(token.split('.')[1]) — or use a tool that explicitly processes everything client-side with no server uploads.

Jsonic's JWT decoder runs entirely in your browser. Nothing is sent to any server. That makes it safe to use for inspecting tokens during development and staging — though for sensitive production tokens, local decoding is always the right default.

Decoding a JWT manually

You can decode a JWT in any browser console without a tool:

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyJ9.signature'
const [header, payload] = token.split('.')
JSON.parse(atob(header))   // → { alg: 'HS256', typ: 'JWT' }
JSON.parse(atob(payload))  // → { sub: 'user_123' }

Note: atob expects standard base64, but JWTs use base64url (uses -and _ instead of + and /). For most tokens this doesn't matter, but strictly you should replace those characters first:

function decodeJwtPart(part) {
  const base64 = part.replace(/-/g, '+').replace(/_/g, '/')
  return JSON.parse(atob(base64))
}

Decode a JWT now

Paste any JWT token into Jsonic's JWT Decoder to instantly see the header, payload, and expiry time formatted as human-readable JSON and dates. No server. No uploads.

Open JWT Decoder

Frequently asked questions

How do I decode a JWT token without a library?

Split on dots, base64url-decode the first two parts, and parse as JSON:JSON.parse(atob(token.split('.')[1].replace(/-/g,'+').replace(/_/g,'/'))). The third part (signature) is binary, not JSON.

What is the difference between decoding and verifying a JWT?

Decoding unpacks the base64url encoding to read the payload — anyone can do this. Verifying checks the signature using the secret or public key to confirm the token is authentic and unmodified. Never trust claims without server-side verification.

What are the three parts of a JWT?

Header (algorithm and token type), Payload (claims: sub, exp, iat, custom fields), and Signature (computed by the server). All three are base64url-encoded and separated by dots.

Is the JWT payload encrypted or just encoded?

Just encoded — anyone with the token can read the payload. JWTs are signed (to detect tampering) but not encrypted by default. Never put sensitive data like passwords in a JWT payload.

How do I check if a JWT has expired?

Read the exp claim and compare to Date.now() / 1000:Date.now() / 1000 {'>'} payload.exp means expired. Always validate expiry server-side during signature verification for security.

What is the exp claim in a JWT?

The exp (expiration time) claim is a Unix timestamp in seconds after which the token must be rejected. Common lifetimes are 1 hour for access tokens and 7–30 days for refresh tokens. Related claims: iat (issued at) and nbf (not before).

Further reading and primary sources