DEVESSENTIALS

JWT Decoder — Decode JSON Web Tokens Online

Paste a JWT to decode it instantly and inspect its header, payload claims and signature. This JWT token decoder converts exp, iat and nbf to readable dates and tells you whether the token has expired. It works with access tokens and ID tokens from any OAuth or OpenID Connect provider. Your token never leaves the browser (see our privacy policy).

Runs in your browser — nothing is uploaded.Your token is decoded on your device and never sent to a server.

JWT Token
Header
{
  "alg": "HS256",
  "typ": "JWT"
}
Payload
sub

"usr_devessentials"

Subject — who the token refers to

name

"Developer"

Full name

email

"hello@devessentials.dev"

Email address

iss

"devessentials.dev"

Issuer — who issued the token

aud

"api.devessentials.dev"

Audience — who the token is intended for

iat

2025-03-27 00:00:00 UTC(1743033600)

Issued At — when the token was issued

exp

2026-03-31 23:33:20 UTC(1775000000)

Expiration — when the token expires

role

"admin"

User role

plan

"pro"

Raw JSON
{
  "sub": "usr_devessentials",
  "name": "Developer",
  "email": "hello@devessentials.dev",
  "iss": "devessentials.dev",
  "aud": "api.devessentials.dev",
  "iat": 1743033600,
  "exp": 1775000000,
  "role": "admin",
  "plan": "pro"
}
Signature

SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Signature verification requires the secret key and cannot be done client-side securely.

How to decode a JWT token

  1. Copy the token from wherever you found it: an Authorization: Bearer … header in DevTools, a cookie, or your auth library's logs. Copy only the token, without the word Bearer.
  2. Paste it into the box above. The decoder splits it on the two dots and Base64URL-decodes the first two parts.
  3. Read the header for the algorithm (alg) and key ID (kid), and the payload for who the token is for (sub), who issued it (iss), who it is meant for (aud) and when it expires (exp).
  4. Remember the result is unverified. To trust it, your server must check the signature with the right key.

Worked example

Paste this sample token into the decoder (it is signed with the HMAC secret demo-secret and expired long ago):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsIm5hbWUiOiJBZGEgTG92ZWxhY2UiLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE3NjAwMDAwMDAsImV4cCI6MTc2MDAwMzYwMH0.XvPhu__D3lzDSYySuJnlWedHRKjGwHSpeIvnqi2ZYNs

It decodes to:

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

// Payload
{
  "sub": "user_123",
  "name": "Ada Lovelace",
  "role": "admin",
  "iat": 1760000000,   // 2025-10-09 08:53:20 UTC
  "exp": 1760003600    // 2025-10-09 09:53:20 UTC — one hour later
}

Two things are worth noticing. First, you just read role: admin without any key — so a JWT is not a place for secrets. Second, nothing stops someone from changing role to anything they like and re-encoding it. The only thing that would catch it is signature verification on the server, which is why decoding is for debugging and verification is for authorization.

Common JWT errors and what they mean

When a token is rejected, decoding it is usually the fastest way to see why:

ErrorSeen inUsual cause
jwt malformed / Invalid token specifiedjsonwebtoken / jwt-decodeThe string isn't three Base64URL segments. Usually a stray "Bearer " prefix, surrounding quotes, or a truncated copy from a log line.
invalid signaturejsonwebtoken, joseWrong secret or public key, or the token was edited. Check you're using the key for this issuer and environment (staging vs. production).
jwt expired (TokenExpiredError)jsonwebtokenCurrent time is past exp. Decode the token here to see exp as a date; if it was issued seconds ago, look for clock skew and set a small leeway.
invalid algorithmjsonwebtokenThe token's alg isn't in your algorithms list — e.g. an RS256 token checked with an HS256 secret. Fix the config, don't widen the list.
JWTDecodeExceptionJava (auth0 java-jwt)The token couldn't be parsed at all: wrong segment count, invalid Base64URL or invalid JSON in the header/payload.
jwt audience invalid / issuer invalidjsonwebtoken, joseaud or iss in the payload doesn't match what the verifier expects. Decode it to compare the exact strings, including trailing slashes.

Why your JWT decoder should run in the browser

A JWT is not just data — it is a credential. Anyone holding an unexpired access token can call your API as that user, and many tokens live for an hour or more. When you paste one into a decoder that processes it on a server, you have no way of knowing whether it was logged, cached or stored.

Developer tools storing what people paste has already gone wrong at scale: JSONFormatter and CodeBeautify kept over 80,000 saved snippets (about 5 GB) that included API keys, OAuth tokens and cloud credentials. How the JSONFormatter and CodeBeautify leak happened and why pasting JWTs into online tools is risky explain what to look for. This decoder has no backend to send a token to; open DevTools → Network while you paste and you will see no request.

Decoding JWTs in Code

JavaScript (Browser) — Manual Decode

// JWTs are Base64URL-encoded — no library needed to read claims
function decodeJwt(token) {
  const [header, payload] = token.split(".");
  const decode = (str) => {
    const b64 = str.replace(/-/g, "+").replace(/_/g, "/");
    const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
    return JSON.parse(new TextDecoder().decode(bytes)); // UTF-8 safe
  };
  return { header: decode(header), payload: decode(payload) };
}

const { header, payload } = decodeJwt("eyJhbGciOiJIUzI1NiJ9...");
console.log(payload.sub);  // user ID
console.log(payload.exp);  // expiry (Unix timestamp)
// ⚠️  This only reads claims — it does NOT verify the signature.

Node.js — jsonwebtoken

// npm install jsonwebtoken
import jwt from "jsonwebtoken";

// jwt.decode: read claims WITHOUT verifying (debugging only)
const payload = jwt.decode(token);
const { header, signature } = jwt.decode(token, { complete: true });

// jwt.verify: check signature + exp/nbf before trusting anything
const claims = jwt.verify(token, process.env.JWT_SECRET, {
  algorithms: ["HS256"],   // always pin the algorithm
  audience: "my-api",
  issuer: "https://auth.example.com/",
  clockTolerance: 30,      // seconds of leeway for clock skew
});

Node.js — jose (verify + decode)

// npm install jose
import { jwtVerify, decodeJwt } from "jose";

// Decode without verification (inspect claims only)
const claims = decodeJwt("eyJhbGciOiJIUzI1NiJ9...");
console.log(claims);

// Verify HS256 token
const secret = new TextEncoder().encode("your-secret-key");
const { payload } = await jwtVerify("eyJhbGciOiJIUzI1NiJ9...", secret);
console.log(payload.sub);

// Verify RS256 token using JWKS endpoint
import { createRemoteJWKSet } from "jose";
const JWKS = createRemoteJWKSet(new URL("https://auth.example.com/.well-known/jwks.json"));
const { payload: p } = await jwtVerify(token, JWKS);

Python — PyJWT

# pip install PyJWT cryptography
import jwt

# Decode without verification (inspect claims only)
claims = jwt.decode(token, options={"verify_signature": False})
print(claims)

# Verify HS256 token
claims = jwt.decode(token, key="your-secret-key", algorithms=["HS256"])
print(claims["sub"])

# Verify RS256 token
with open("public_key.pem") as f:
    public_key = f.read()
claims = jwt.decode(token, public_key, algorithms=["RS256"])

Go — golang-jwt

// go get github.com/golang-jwt/jwt/v5
import "github.com/golang-jwt/jwt/v5"

// Parse and verify HS256
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
        return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
    }
    return []byte("your-secret-key"), nil
})

if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
    fmt.Println(claims["sub"])
    fmt.Println(claims["exp"])
}

Terminal

# Decode payload (no verification) using base64 and jq
TOKEN="eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTcwMDAwMDAwMH0.sig"
PAYLOAD=$(echo $TOKEN | cut -d. -f2)

# Add padding if needed and decode
echo "${PAYLOAD}==" | tr '_-' '/+' | base64 -d | jq .

# Output:
# { "sub": "user_123", "exp": 1700000000 }

JWT Structure Explained

A JWT (JSON Web Token) is three Base64URL-encoded segments separated by dots: header.payload.signature

PartContainsExample
HeaderAlgorithm and token type{ "alg": "HS256", "typ": "JWT" }
PayloadClaims (user data, expiry){ "sub": "user_123", "exp": 1712686400 }
SignatureHMAC or RSA of header+payloadVerified server-side — cannot be faked without the secret

Standard claims in the payload: sub (subject — user ID), iss (issuer), aud (audience), iat (issued at — Unix timestamp), exp (expiry — Unix timestamp), nbf (not before). The time claims are seconds since the Unix epoch — paste one into the Unix timestamp converter to see the exact date. The optional jti (token ID) is usually a random UUID, which you can create with the UUID generator or in code in any language. Any additional claims (role, email, permissions) are custom and defined by the application.

Decoding ≠ verifying. This tool decodes the payload — it reads the claims but does not check the signature. Anyone can decode any JWT. Only your server, using the correct secret or public key, can verify that it hasn't been tampered with. Keep that signing secret out of your codebase by loading it from environment variables or a .env file.

Expired or invalid token? An API should answer with 401 Unauthorized when the token is missing, expired or has a bad signature, and 403 Forbidden when the token is valid but lacks the required role. See choosing HTTP status codes for REST APIs for the full breakdown.

Learn more

Browse all developer guides →

Related Tools

Frequently Asked Questions

Can I decode a JWT without the secret key?▾

Yes. The header and payload of a signed JWT (JWS) are only Base64URL-encoded, not encrypted, so anyone can read them without a key — that is all a JWT decoder does. The secret or public key is only needed to verify the signature, which proves the token was issued by a trusted party and has not been changed. This is also why you should never put secrets such as passwords or API keys inside a JWT payload.

What is the difference between decoding a JWT and verifying it?▾

Decoding a JWT just Base64URL-decodes the header and payload — it reads the claims but performs zero cryptographic checks. Verification confirms the signature using the correct algorithm and key, ensuring the token was issued by a trusted party and hasn't been tampered with. This tool is for debugging and inspection only — your server code must always verify the signature before trusting any claim inside a JWT.

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

Only into a decoder that runs in your browser. An access token is a bearer credential: until it expires, whoever holds it can call your API as that user. A server-side decoder receives the full token and may log it. This JWT decoder parses the token with JavaScript on your device and makes no network request with it — you can confirm that in DevTools → Network. For production tokens, the safest habit is still to decode tokens that have already expired, or to revoke a token after debugging it.

What does jsonwebtoken.decode() do, and is it safe to use?▾

In the Node.js jsonwebtoken library, jwt.decode(token) returns the payload without checking the signature, and jwt.decode(token, { complete: true }) also returns the header and signature. It is the same thing this page does. It is fine for reading claims you will not trust — logging, debugging, choosing which key to verify with — but never for authorization decisions. For those, use jwt.verify(token, key, { algorithms: ['RS256'] }) with an explicit algorithm list.

How do I decode a JWT token in JavaScript?▾

Split the token on '.', take the second segment, convert Base64URL to Base64 (replace - with + and _ with /), decode it with atob() and parse the JSON. For payloads with non-ASCII characters, decode the bytes as UTF-8 with TextDecoder instead of using the atob() output directly. In Node.js, Buffer.from(segment, 'base64url').toString() does it in one step. Libraries such as jwt-decode (browser) or jose's decodeJwt() wrap this for you. The code samples on this page show each approach.

Can you decrypt a JWT token?▾

Most JWTs are not encrypted, so there is nothing to decrypt — they are signed (JWS) and their contents are readable by anyone. A JWE (JSON Web Encryption) token is actually encrypted: it has five dot-separated segments instead of three, and its payload cannot be read without the decryption key. This decoder handles the common three-part signed tokens (HS256, RS256, ES256 and others); it does not decrypt JWE.

Can I decode access tokens, ID tokens and refresh tokens here?▾

Access tokens and ID tokens from OAuth 2.0 / OpenID Connect providers (Auth0, Okta, Microsoft Entra ID, Amazon Cognito, Keycloak, Firebase) are usually JWTs and decode normally. Refresh tokens are often opaque random strings rather than JWTs — if the token has no dots, it is not a JWT and there is nothing to decode; only the issuing server can look it up.

What is the 'alg: none' JWT vulnerability and how does it work?▾

The JWT spec originally allowed "alg": "none" to indicate an unsigned token. Several JWT libraries had bugs where passing alg: none in the header caused them to skip signature verification entirely, accepting any forged token as valid. A 2015 disclosure showed this affected multiple popular libraries across languages. Always configure your JWT library to explicitly whitelist the expected algorithm(s) and reject none — never trust the algorithm specified in the token header without validation.

What is the difference between HS256 and RS256, and when should I use each?▾

HS256 (HMAC-SHA256) uses a single shared secret — the same key signs and verifies the token, so every service that needs to verify tokens must possess the secret. RS256 (RSA-SHA256) uses a private key to sign and a public key to verify — you can distribute the public key freely via a JWKS endpoint so any service can verify tokens without ever seeing the signing key. Use RS256 (or ES256) when multiple independent services need to verify tokens, or when integrating with third-party identity systems.

What causes 'token expired' errors on a freshly issued JWT?▾

The exp claim is a Unix timestamp (seconds since epoch) checked against the validating server's current time. A common cause of unexpected expiry errors is clock skew between the issuing and validating servers — even a 1–2 minute difference will cause a freshly issued token to appear expired. Most JWT libraries allow configuring a 'leeway' (e.g., 60 seconds) to tolerate minor clock drift. A decoded token that looks valid in a browser debugger may still be rejected server-side due to this timing mismatch.

Why do the three parts of a JWT use Base64URL encoding instead of standard Base64?▾

Standard Base64 uses + and / characters, which have special meaning in URLs (+ = space, / = path separator) and would require percent-encoding if the JWT were passed as a URL parameter or cookie. Base64URL (RFC 4648 §5) substitutes - for + and _ for /, and omits the = padding characters, producing a compact token that is safe in HTTP headers, query strings, and cookies without any further encoding. This is why the three dot-separated segments of a JWT never contain those characters.