How it works
- 1
Paste your JWT
Drop a token from your app, an API response, or a cookie. The header, payload, and signature are colour-coded immediately.
- 2
Inspect claims and expiry
See the decoded JSON header and payload, every claim in a table, and a live badge showing whether the token is valid or expired.
- 3
Verify the signature (optional)
For HMAC-SHA256 tokens, enter your secret to verify the signature locally using the Web Crypto API — your secret never leaves the tab.
🛡️ Verify zero uploads — open DevTools → Network tab
Open your browser's DevTools (F12), go to the Network tab, and use this tool. You will see zero outbound requests — all processing runs inside your browser sandbox via WebAssembly or pure JavaScript. Nothing you paste or upload is ever sent anywhere.
⚠️ Security considerations
Never paste production JWTs into any online tool
A JWT for a live user session carries real privileges. Even if the tool is client-side, build the habit of only debugging with test tokens or already-invalidated tokens. A compromised JWT lets an attacker act as the user until it expires.
HS256 secret strength determines your entire security posture
With HMAC-SHA256 (HS256), anyone who knows the secret can forge arbitrary tokens. Use a cryptographically random 256-bit secret (32 bytes from crypto.randomBytes). Never use a short or dictionary-based string — HS256 secrets are brute-forceable offline.
Always set exp — JWTs without expiry are permanent credentials
A JWT with no exp claim never expires. If it leaks, it works forever. Short-lived tokens (15 minutes for access tokens, 7 days for refresh tokens) limit the blast radius of a token leak. Use a refresh token flow for long sessions.
Validate iss and aud in your verification code, not just the signature
A valid signature only proves the token wasn't tampered with. Your server must also check that iss matches your expected issuer and aud matches your service. Without these checks, a token from a different service with the same secret passes signature verification.
Prefer RS256 (asymmetric) over HS256 for multi-service architectures
With RS256, only the auth server holds the private key; all other services verify with the public key. This eliminates the need to share a shared secret across services. HS256 requires every verifying service to know the secret — a larger attack surface.
Use cases
Debug auth flows during development
Paste the token your backend issues and immediately see which claims are present, whether the expiry is set correctly, and that the audience (aud) is right.
Verify JWT signature without a server
Confirm that a token was signed with your secret locally, without sending it through a third-party debugger like jwt.io.
Audit third-party API tokens
Inspect JWTs from external providers (Auth0, Supabase, Firebase, Clerk) to understand the included claims, scopes, and expiry windows.
Common mistakes
5 pitfalls to avoid
▾
Common mistakes
5 pitfalls to avoid
1Not validating the alg header
✗ Wrong: Accepting any algorithm the token claims in its header — allows the 'alg:none' attack where attacker strips the signature
✓ Fix: Always pin the expected algorithm in your verification code: jwt.verify(token, secret, { algorithms: ['HS256'] }). Reject tokens that claim alg:none.
2Storing JWTs in localStorage
✗ Wrong: Storing tokens in localStorage because it's easy — exposes them to XSS attacks from any script on the page
✓ Fix: Store JWTs in httpOnly cookies (not accessible to JavaScript). If localStorage is required, harden against XSS with a strict Content-Security-Policy.
3Using a weak or guessable HS256 secret
✗ Wrong: JWT_SECRET=mysecret or JWT_SECRET=password — brute-forceable offline in seconds with hashcat
✓ Fix: Generate a 256-bit random secret: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))". Store it in a secrets manager, not in .env files.
4Skipping audience (aud) validation
✗ Wrong: Only checking the signature — a token issued for service A is accepted by service B
✓ Fix: Set aud to the specific service identifier when issuing tokens, and verify it matches on every decode: jwt.verify(token, secret, { audience: 'my-api' }).
5Treating JWT verification as authentication
✗ Wrong: Accepting any valid JWT as proof of identity — doesn't account for revoked tokens or logged-out sessions
✓ Fix: For logout/revocation, maintain a token denylist (Redis set of jti values) and check it on every request, or use very short exp + refresh token rotation.
Code snippets
Copy-ready integration examples
▾
Code snippets
Copy-ready integration examples
import jwt from "jsonwebtoken" // npm install jsonwebtoken
const secret = process.env.JWT_SECRET! // must be 256-bit random
// Sign a token (access token, 15-minute expiry):
const token = jwt.sign(
{ sub: userId, role: "user", aud: "my-api" },
secret,
{ algorithm: "HS256", expiresIn: "15m", issuer: "my-auth-service" }
)
// Verify — throws if invalid, expired, wrong alg, wrong aud:
try {
const payload = jwt.verify(token, secret, {
algorithms: ["HS256"], // pin algorithm — never omit
audience: "my-api",
issuer: "my-auth-service",
})
console.log(payload.sub) // user ID
} catch (err) {
// TokenExpiredError, JsonWebTokenError, NotBeforeError
return Response.json({ error: "Unauthorized" }, { status: 401 })
}// middleware.ts
import { NextRequest, NextResponse } from "next/server"
import { jwtVerify } from "jose" // npm install jose
const secret = new TextEncoder().encode(process.env.JWT_SECRET)
export async function middleware(req: NextRequest) {
const token = req.cookies.get("access_token")?.value
if (!token) return NextResponse.redirect(new URL("/login", req.url))
try {
const { payload } = await jwtVerify(token, secret, {
algorithms: ["HS256"],
audience: "my-api",
issuer: "my-auth-service",
})
// Forward user ID to route handlers via header:
const res = NextResponse.next()
res.headers.set("x-user-id", payload.sub as string)
return res
} catch {
return NextResponse.redirect(new URL("/login", req.url))
}
}
export const config = { matcher: ["/dashboard/:path*", "/api/user/:path*"] }# pip install PyJWT cryptography
import jwt
import os
secret = os.environ["JWT_SECRET"]
# Sign:
token = jwt.encode(
{"sub": user_id, "aud": "my-api", "exp": datetime.utcnow() + timedelta(minutes=15)},
secret,
algorithm="HS256"
)
# Verify — raises exceptions on failure:
try:
payload = jwt.decode(
token,
secret,
algorithms=["HS256"], # pin — never pass algorithms=jwt.get_unverified_header(token)
audience="my-api",
)
user_id = payload["sub"]
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")// go get github.com/golang-jwt/jwt/v5
import (
"github.com/golang-jwt/jwt/v5"
"os"
)
secret := []byte(os.Getenv("JWT_SECRET"))
// Parse and verify:
token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
// Pin algorithm — reject tokens claiming a different alg:
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
}
return secret, nil
}, jwt.WithAudience("my-api"), jwt.WithIssuer("my-auth-service"))
if err != nil || !token.Valid {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
claims := token.Claims.(jwt.MapClaims)
userID := claims["sub"].(string)Frequently Asked Questions
Is it safe to paste my JWT here?
Yes. This tool decodes entirely in your browser using JavaScript — no data is sent to any server. Open DevTools → Network while using it and you'll see zero requests. That said, avoid pasting production tokens for live users; use test tokens for debugging.
What does the signature verification check?
For HMAC-SHA256 (HS256) signed tokens, the tool re-computes the expected signature from the header and payload using your secret, then compares it to the signature in the token. If they match, the token is authentic and hasn't been tampered with. RSA and ECDSA (RS256, ES256) require the public key — public-key verification is coming soon.
What are the most common JWT claims?
Standard claims: iss (issuer), sub (subject / user ID), aud (audience), exp (expiry timestamp), iat (issued at), nbf (not before), jti (unique token ID). Claims in blue in the table are standard RFC 7519 claims; others are custom claims added by your application.
Why is the expiry time showing the wrong timezone?
JWT exp, iat, and nbf values are Unix timestamps (seconds since epoch). This tool converts them to your browser's local timezone for display. The raw Unix timestamp in the table is always timezone-independent.
How is this different from jwt.io?
jwt.io sends your token to its servers to decode. This tool decodes entirely client-side — your token never leaves the browser tab. It also runs offline once cached, and includes an expiry status badge and claims table without requiring you to scroll.
Want unlimited access + saved history?
Pro is $6/month · 30-day money-back guarantee.
Official specifications & references
RFC 7519 — JSON Web Token (JWT)
The IETF standard defining JWT structure, registered claims (iss, sub, aud, exp, iat, nbf, jti), and validation rules.
RFC 7515 — JSON Web Signature (JWS)
Defines how JWT signatures are computed and verified, including the base64url encoding scheme.
RFC 7518 — JSON Web Algorithms (JWA)
Specifies HS256, RS256, ES256, and other algorithms used in JWT header alg claims.
OWASP — JWT Security Cheat Sheet
OWASP guidance on JWT pitfalls: alg:none attacks, weak secrets, missing claim validation.
PortSwigger — JWT attacks
Practical explanation of JWT attack vectors: algorithm confusion, jwk injection, and key confusion attacks.
Embed or share this toollink · markdown · html · iframe
https://aarunyaapps.com/jwt-debugger
Related Tools
From the blog
How to Debug JWTs Without Pasting Them Into jwt.io
jwt.io is convenient but every token you paste contains your authentication state. This guide shows safer ways to decode and debug JWTs — in your browser console, with curl, and with a privacy-first tool.
8 min read
How to Safely Share Your .env File With a Contractor
Step-by-step guide to sharing environment variables without exposing production secrets. Includes sanitization workflow, .gitignore rules, and a pre-commit hook recipe.
6 min read
Regex for Beginners: 10 Patterns Every Developer Should Know
Practical regular expressions explained in plain English — email validation, URL matching, phone numbers, ISO dates, and more. Includes live examples for JavaScript, Python, and Go.
10 min read