DEV Community

miccho27
miccho27

Posted on

Debug JWT Tokens Instantly — Free JWT Decoder & Security Validator API

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
Enter fullscreen mode Exit fullscreen mode

The header and payload are just base64url — anyone can read them. The signature is what you trust. Common mistakes developers make:

  1. Accepting alg: none — means no signature, token is trivially forgeable
  2. Not checking exp — expired tokens keep working
  3. Missing iss validation — tokens from other services accepted
  4. 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
Enter fullscreen mode Exit fullscreen mode
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"
Enter fullscreen mode Exit fullscreen mode

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 }
Enter fullscreen mode Exit fullscreen mode

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"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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 nbf not 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);
}
Enter fullscreen mode Exit fullscreen mode

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);
  });
});
Enter fullscreen mode Exit fullscreen mode

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 };
}
Enter fullscreen mode Exit fullscreen mode

Why Not Just Decode It Locally?

You could base64-decode the payload yourself in 3 lines. The value here is:

  1. Structured analysis — registered vs custom claims separated automatically
  2. Security warningsalg:none, missing exp, expired tokens flagged consistently
  3. No client-side dependency — works from any language that can make HTTP requests
  4. 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:

All 40+ APIs are free to use, no credit card required. Browse the full collection on RapidAPI.

Top comments (0)