JWT Debugger — C source
Decode a JSON Web Token and inspect its header and payload as pretty JSON, with exp/iat claim awareness and an expiry badge. Runs entirely in your browser.
This is the C implementation — the same logic the interactive tool runs, in a shareable, citable form.
/* JWT base64url decoding helper — C (C11) port of the jwt tool. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Reverse standard-base64 table; '=' (padding) is handled by the caller. */
static int b64_index(unsigned char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
if (c == '+') return 62;
if (c == '/') return 63;
return -1;
}
/* Decode a base64url segment (JWT header/payload) to bytes. Returns a
* malloc'd buffer and stores its length in *out_len, or NULL on input
* outside the base64url alphabet / bad padding — the TS throw analogue.
* ISO C has no stdlib base64, so the decoder is carried here. */
unsigned char *b64url_decode(const char *s, size_t *out_len) {
/* 1. URL-safe alphabet -> standard base64 alphabet (on a copy). */
size_t n = strlen(s);
char *std_b64 = malloc(n + 4); /* room for up to 3 '=' plus NUL */
if (std_b64 == NULL) return NULL;
for (size_t i = 0; i < n; i++) {
char c = s[i];
std_b64[i] = (c == '-') ? '+' : (c == '_') ? '/' : c;
}
/* 2. Restore stripped '=' padding so the length is a multiple of 4. */
size_t len = n;
while (len % 4 != 0) std_b64[len++] = '=';
std_b64[len] = '\0';
/* 3. Strict decode: 4-char blocks -> 3 bytes, validating every char. */
size_t quads = len / 4;
unsigned char *out = malloc(quads * 3 + 1);
if (out == NULL) { free(std_b64); return NULL; }
size_t o = 0;
for (size_t q = 0; q < quads; q++) {
int v[4] = {0, 0, 0, 0};
int pad = 0;
for (int k = 0; k < 4; k++) {
char c = std_b64[q * 4 + k];
if (c == '=') { pad++; continue; }
if (pad != 0) goto invalid; /* data after '=' */
v[k] = b64_index((unsigned char)c);
if (v[k] < 0) goto invalid; /* non-alphabet character */
}
if (pad > 2) goto invalid; /* "A==="-style over-padding */
if (pad != 0 && q + 1 != quads) goto invalid; /* '=' before the end */
unsigned triple = ((unsigned)v[0] << 18) | ((unsigned)v[1] << 12) |
((unsigned)v[2] << 6) | (unsigned)v[3];
out[o++] = (unsigned char)(triple >> 16);
if (pad < 2) out[o++] = (unsigned char)(triple >> 8);
if (pad < 1) out[o++] = (unsigned char)(triple);
}
free(std_b64);
*out_len = o;
return out; /* 4. raw bytes — read byte-faithful (Latin-1), like JS atob */
invalid:
free(std_b64);
free(out);
return NULL;
}
int main(void) {
/* eyJhbGciOiJIUzI1NiJ9 decodes to {"alg":"HS256"} */
size_t len = 0;
unsigned char *text = b64url_decode("eyJhbGciOiJIUzI1NiJ9", &len);
if (text == NULL) {
fprintf(stderr, "invalid base64url input\n");
return 1;
}
printf("%.*s\n", (int)len, text);
free(text);
return 0;
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →