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.
{
"alg": "HS256",
"typ": "JWT"
}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
iat2025-03-27 00:00:00 UTC(1743033600)
Issued At — when the token was issued
exp2026-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"
}SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Signature verification requires the secret key and cannot be done client-side securely.
How to decode a JWT token
- 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 wordBearer. - Paste it into the box above. The decoder splits it on the two dots and Base64URL-decodes the first two parts.
- 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). - 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:
| Error | Seen in | Usual cause |
|---|---|---|
| jwt malformed / Invalid token specified | jsonwebtoken / jwt-decode | The string isn't three Base64URL segments. Usually a stray "Bearer " prefix, surrounding quotes, or a truncated copy from a log line. |
| invalid signature | jsonwebtoken, jose | Wrong 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) | jsonwebtoken | Current 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 algorithm | jsonwebtoken | The 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. |
| JWTDecodeException | Java (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 invalid | jsonwebtoken, jose | aud 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
| Part | Contains | Example |
|---|---|---|
| Header | Algorithm and token type | { "alg": "HS256", "typ": "JWT" } |
| Payload | Claims (user data, expiry) | { "sub": "user_123", "exp": 1712686400 } |
| Signature | HMAC or RSA of header+payload | Verified 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
How JWT Authentication Works: A Developer's Guide
JWTs are used in virtually every modern authentication system. Learn how they're structured, how signing and verification work, common security mistakes, and when to use JWTs vs sessions.
Why You Should Never Paste Sensitive Data Into Online Tools
API keys, JWT tokens, database URLs, passwords — pasting them into server-side tools is a real security risk. Learn what happens to your data and what client-side alternatives to use.
CORS Explained: Why It Happens and How to Fix It
CORS errors happen when a browser blocks a request to a different origin. Learn how the Same-Origin Policy works, what preflight requests are, and how to configure CORS correctly in Express, FastAPI, and Nginx.
Related Tools
Unix Timestamp Converter
Turn exp and iat claims into readable dates.
Base64 Encoder/Decoder
JWT uses Base64URL encoding internally.
Unix Timestamp Converter
Convert exp and iat claims to dates.
Hash Generator
SHA-256, HMAC and more.
Password Generator
Generate secure signing secrets.
Why You Should Never Paste Sensitive Data Into Online Tools
What server-side decoders can do with a JWT.
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.