If you've ever had to debug a JWT auth issue in production, you've probably ended up at jwt.io — pasting a potentially sensitive token into a third-party website just to read the payload. There's a better way.
This post covers a free JWT Decoder & Validator API that lets you decode and validate JWT tokens programmatically, directly from your code or CI pipeline — no copy-pasting tokens into unknown sites.
RapidAPI listing: JWT Decoder & Validator API on RapidAPI
What's in a JWT?
A JWT (JSON Web Token) is three base64url-encoded parts separated by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 <- Header
.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTc0... <- Payload
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_... <- Signature
The header and payload are just base64url — anyone can read them. The signature is what you trust. Common mistakes developers make:
-
Accepting
alg: none— means no signature, token is trivially forgeable -
Not checking
exp— expired tokens keep working -
Missing
issvalidation — tokens from other services accepted - Using HS256 with a weak secret — brute-forceable offline
This API surfaces all of these issues automatically.
Endpoints
Base URL:
https://jwt-decoder-api.p.rapidapi.com
| Endpoint | Method | Purpose |
|---|---|---|
/decode |
GET / POST | Decode header + payload |
/validate |
GET / POST | Structure validation + security warnings |
/health |
GET | Service status |
Decode a JWT Token
curl (GET with query param):
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImlhdCI6MTcwOTI1MTIwMCwiZXhwIjoxNzA5MjU0ODAwfQ.abc123"
curl "https://jwt-decoder-api.p.rapidapi.com/decode?token=${TOKEN}" \
-H "X-RapidAPI-Key: YOUR_KEY" \
-H "X-RapidAPI-Host: jwt-decoder-api.p.rapidapi.com"
JavaScript (POST — better for long tokens):
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
const response = await fetch(
"https://jwt-decoder-api.p.rapidapi.com/decode",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY",
"X-RapidAPI-Host": "jwt-decoder-api.p.rapidapi.com",
},
body: JSON.stringify({ token }),
}
);
const data = await response.json();
console.log(data.data.header); // { alg: "HS256", typ: "JWT" }
console.log(data.data.payload); // { sub: "user_123", exp: 1709254800, iat: 1709251200 }
Full response:
{
"success": true,
"data": {
"header": { "alg": "HS256", "typ": "JWT" },
"payload": {
"sub": "user_123",
"iat": 1709251200,
"exp": 1709254800
},
"signature": "SflKxwRJSMeKKF2QT4...",
"has_signature": true,
"parts_count": 3,
"algorithm": "HS256",
"type": "JWT",
"analysis": {
"registered_claims": {
"sub": "user_123",
"iat": 1709251200,
"exp": 1709254800
},
"custom_claims": {},
"expiration": {
"timestamp": 1709254800,
"date": "2024-03-01T01:00:00.000Z",
"is_expired": true,
"seconds_remaining": 0
},
"issued_at": {
"timestamp": 1709251200,
"date": "2024-03-01T00:00:00.000Z"
}
}
}
}
The analysis block does the heavy lifting: it separates registered claims (iss, sub, aud, exp, nbf, iat, jti) from custom claims, and pre-computes expiration state so you don't have to do the timestamp math.
Validate JWT Security
The /validate endpoint goes further — it checks for common security misconfigurations and returns structured warnings.
const response = await fetch(
"https://jwt-decoder-api.p.rapidapi.com/validate",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-RapidAPI-Key": "YOUR_KEY",
"X-RapidAPI-Host": "jwt-decoder-api.p.rapidapi.com",
},
body: JSON.stringify({ token }),
}
);
const result = await response.json();
console.log(result.data.warnings);
Example response for a suspicious token:
{
"success": true,
"data": {
"is_valid_structure": true,
"validations": {
"has_valid_structure": true,
"has_header": true,
"has_payload": true,
"has_signature": false,
"has_algorithm": true,
"has_type": true
},
"warnings": [
"Token has no signature (unsecured JWT)",
"Algorithm is \"none\" — token is unsigned and should not be trusted",
"Missing \"iss\" (issuer) claim",
"Missing \"exp\" (expiration) claim — token never expires"
],
"warning_count": 4,
"algorithm": "none"
}
}
Four warnings for a single token — this is exactly the kind of token that causes CVEs. The API catches:
-
alg: none(unsigned token vulnerability) - Missing signature
- Missing
exp(token lives forever) - Missing
iss(issuer not validated) - Token already expired
- Token
nbfnot yet active - HS256 symmetric key warning
Practical Use Cases
1. Debug auth failures in your backend
// middleware/auth-debug.js
async function debugToken(token) {
const res = await fetch("https://jwt-decoder-api.p.rapidapi.com/decode", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-RapidAPI-Key": process.env.RAPIDAPI_KEY,
"X-RapidAPI-Host": "jwt-decoder-api.p.rapidapi.com",
},
body: JSON.stringify({ token }),
});
const { data } = await res.json();
if (data.analysis.expiration?.is_expired) {
console.error("Token expired at:", data.analysis.expiration.date);
}
if (data.analysis.issued_at) {
console.log("Token issued at:", data.analysis.issued_at.date);
}
console.log("Subject:", data.payload.sub);
console.log("Algorithm:", data.algorithm);
}
2. Security audit in CI pipeline
Run this as part of your test suite to catch misconfigured tokens before they reach production:
// tests/jwt-security.test.js
import { describe, it, expect } from "vitest";
async function validateToken(token) {
const res = await fetch("https://jwt-decoder-api.p.rapidapi.com/validate", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-RapidAPI-Key": process.env.RAPIDAPI_KEY,
"X-RapidAPI-Host": "jwt-decoder-api.p.rapidapi.com",
},
body: JSON.stringify({ token }),
});
return res.json();
}
describe("JWT security audit", () => {
it("should not have alg:none tokens in config", async () => {
const suspectToken = process.env.TEST_JWT_TOKEN;
const result = await validateToken(suspectToken);
const hasNoneAlg = result.data.warnings.some(w => w.includes('"none"'));
expect(hasNoneAlg).toBe(false);
});
it("should have expiration claim", async () => {
const token = process.env.TEST_JWT_TOKEN;
const result = await validateToken(token);
const missingExp = result.data.warnings.some(w => w.includes('"exp"'));
expect(missingExp).toBe(false);
});
});
3. Token expiration monitor
async function checkTokenExpiry(token, warningThresholdSeconds = 300) {
const res = await fetch(
`https://jwt-decoder-api.p.rapidapi.com/decode?token=${encodeURIComponent(token)}`,
{
headers: {
"X-RapidAPI-Key": process.env.RAPIDAPI_KEY,
"X-RapidAPI-Host": "jwt-decoder-api.p.rapidapi.com",
},
}
);
const { data } = await res.json();
const expiry = data.analysis.expiration;
if (!expiry) return { status: "no_expiry" };
if (expiry.is_expired) return { status: "expired", expiredAt: expiry.date };
if (expiry.seconds_remaining < warningThresholdSeconds) {
return { status: "expiring_soon", secondsLeft: expiry.seconds_remaining };
}
return { status: "valid", secondsLeft: expiry.seconds_remaining };
}
Why Not Just Decode It Locally?
You could base64-decode the payload yourself in 3 lines. The value here is:
- Structured analysis — registered vs custom claims separated automatically
-
Security warnings —
alg:none, missingexp, expired tokens flagged consistently - No client-side dependency — works from any language that can make HTTP requests
- Audit trail — useful when you want external validation logged separately from your auth flow
Free Tier & Limits
The free tier on RapidAPI includes 100 requests/day — enough for debugging sessions and CI test runs.
View pricing and upgrade options if you need higher limits for automated monitoring.
Related APIs in the Collection
If you're working on auth and security tooling, these free APIs pair well:
- Hash & Encoding API — MD5, SHA-256, base64 encoding/decoding
- Password Generator API — Cryptographically secure passwords with entropy scoring
- UUID Generator API — v4, v7, ULID, NanoID
All 40+ APIs are free to use, no credit card required. Browse the full collection on RapidAPI.
Top comments (0)