This site is not affiliated with or endorsed by Cloudflare, Inc. It showcases deployable experiments across most Cloudflare products.
Edge Platform

JWT Inspector

Decode, verify, and issue JWTs with Web Crypto on Workers - JWT debugging and experimentation toolkit at the edge

Inspect JSON Web Tokens at the edge: decode header and payload without verification, verify HS256/RS256 signatures, and issue test HS256 tokens for experimentation.

Features

  • POST /decode - Parse JWT header and payload (no crypto verification)
  • POST /verify - Verify HS256 (secret) or RS256 (public key PEM)
  • POST /issue - Mint test HS256 tokens with configurable expiry
  • Web Crypto - No external JWT libraries required

API Reference

POST /decode

Decode a JWT without verifying the signature.

Prop

Type

Example Request

curl -X POST "https://your-worker.workers.dev/decode" \
  -H "Content-Type: application/json" \
  -d '{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}'

Success Response

{
  "header": { "alg": "HS256", "typ": "JWT" },
  "payload": { "sub": "user-1", "exp": 1710000000 }
}

Error Codes

  • 400 - Invalid JSON (INVALID_BODY), token (INVALID_TOKEN), or decode error (DECODE_ERROR)

POST /verify

Verify a JWT signature.

Prop

Type

Provide secret (HS256) or publicKey (RS256), not both.

Example Request

curl -X POST "https://your-worker.workers.dev/verify" \
  -H "Content-Type: application/json" \
  -d '{"token":"eyJ...","secret":"demo-secret-key"}'

Success Response

{
  "valid": true,
  "algorithm": "HS256",
  "payload": { "sub": "alice", "exp": 1710000000 }
}

Error Codes

  • 400 - Invalid body/token (INVALID_BODY, INVALID_TOKEN, MISSING_KEY, VERIFY_ERROR)

POST /issue

Issue a test HS256 JWT.

Prop

Type

Example Request

curl -X POST "https://your-worker.workers.dev/issue" \
  -H "Content-Type: application/json" \
  -d '{"secret":"demo-secret-key","subject":"alice","expiresInSeconds":3600}'

Success Response

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "payload": {
    "sub": "alice",
    "iat": 1710000000,
    "exp": 1710003600,
    "iss": "jwt-inspector-demo"
  }
}

Error Codes

  • 400 - Invalid body or secret (INVALID_BODY, INVALID_SECRET)
  • 502 - Signing failed (ISSUE_ERROR)

Use Cases

  • Debug JWT payloads during API integration
  • Test HS256/RS256 verification logic before production auth
  • Generate short-lived test tokens for local development

Limitations

  • Demo tool only; not a production identity provider
  • RS256 verification requires PEM public key in request body
  • No support for JWKS fetching or exotic algorithms

Use in your project

Copy these files into an existing Worker. Prefer Deployment to try the full experiment first. Source: apps/experiments/jwt-inspector.

DependenciesNoneBindingsNonePlatformWeb Crypto
function base64UrlDecode(input: string): string {  const padded = input.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((input.length + 3) % 4);  return atob(padded);}function base64UrlEncode(input: string): string {  return btoa(input).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");}export function decodeJwt(token: string): {  header: Record<string, unknown>;  payload: Record<string, unknown>;} {  const parts = token.split(".");  if (parts.length !== 3) {    throw new Error("JWT must have three segments");  }  const header = JSON.parse(base64UrlDecode(parts[0])) as Record<string, unknown>;  const payload = JSON.parse(base64UrlDecode(parts[1])) as Record<string, unknown>;  return { header, payload };}function pemToArrayBuffer(pem: string): ArrayBuffer {  const cleaned = pem    .replace(/-----BEGIN [^-]+-----/g, "")    .replace(/-----END [^-]+-----/g, "")    .replace(/\s+/g, "");  const binary = atob(cleaned);  const bytes = new Uint8Array(binary.length);  for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);  return bytes.buffer;}async function importHmacKey(secret: string): Promise<CryptoKey> {  return crypto.subtle.importKey(    "raw",    new TextEncoder().encode(secret),    { name: "HMAC", hash: "SHA-256" },    false,    ["sign", "verify"]  );}async function importRsaPublicKey(pem: string): Promise<CryptoKey> {  return crypto.subtle.importKey(    "spki",    pemToArrayBuffer(pem),    { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },    false,    ["verify"]  );}export async function verifyJwt(  token: string,  options: { secret?: string; publicKey?: string }): Promise<{ valid: boolean; algorithm: string; payload: Record<string, unknown> }> {  const parts = token.split(".");  if (parts.length !== 3) throw new Error("JWT must have three segments");  const { header, payload } = decodeJwt(token);  const alg = String(header.alg ?? "");  const data = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);  const signature = Uint8Array.from(base64UrlDecode(parts[2]), (c) => c.charCodeAt(0));  if (alg === "HS256") {    if (!options.secret) throw new Error("HS256 verification requires secret");    const key = await importHmacKey(options.secret);    const valid = await crypto.subtle.verify("HMAC", key, signature, data);    return { valid, algorithm: alg, payload };  }  if (alg === "RS256") {    if (!options.publicKey) throw new Error("RS256 verification requires publicKey PEM");    const key = await importRsaPublicKey(options.publicKey);    const valid = await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key, signature, data);    return { valid, algorithm: alg, payload };  }  throw new Error(`Unsupported algorithm: ${alg}`);}export async function issueHs256Token(  payload: Record<string, unknown>,  secret: string): Promise<string> {  const header = { alg: "HS256", typ: "JWT" };  const encodedHeader = base64UrlEncode(JSON.stringify(header));  const encodedPayload = base64UrlEncode(JSON.stringify(payload));  const data = `${encodedHeader}.${encodedPayload}`;  const key = await importHmacKey(secret);  const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(data));  const encodedSignature = base64UrlEncode(String.fromCharCode(...new Uint8Array(signature)));  return `${data}.${encodedSignature}`;}export function validateToken(input: unknown): string | null {  if (typeof input !== "string") return null;  const trimmed = input.trim();  if (!trimmed || trimmed.split(".").length !== 3) return null;  return trimmed;}

Deployment

Click the deploy button

Deploy to Cloudflare Workers

Test your deployment

curl -X POST "https://your-worker.workers.dev/issue" \
  -H "Content-Type: application/json" \
  -d '{"secret":"demo-secret-key","subject":"alice"}'

Local Development

cd apps/experiments/jwt-inspector
npm install
npm run dev

Cloudflare Features Used

On this page