Skip to content

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 →