Skip to content
Arian Khademi
All projects

Authentication · SDK design

jwkit

2026

One JWT verification contract, implemented in Go and TypeScript

  • Go
  • TypeScript
  • Gin
  • Express
  • JWKS
One verification contract · Go + TypeScript
  1. 01
    Parse & constrain
    Canonical encoding, size limits, algorithm allow-list.
  2. 02
    Find key & verify
    Cached JWKS, bounded refresh, signature check.
  3. 03
    Validate claims
    Issuer, audience, expiry; the same typed errors.

An API written in Go and one written in TypeScript should agree on whether a token is valid. Their underlying libraries can disagree on malformed JSON, base64 encoding and which error to report first. I built jwkit to make that boundary explicit: one verification policy, two implementations, and a shared suite that checks they behave the same way.

What I built

Go and TypeScript verifiers, Gin and Express middleware, typed errors, and a JWKS cache that handles signing-key rotation. The verifier accepts RS256 and ES256, checks the signature before claims, and validates issuer, audience and time claims in a fixed order.

Technical decisions

  • Own the policy, reuse the cryptography. jwkit handles parsing, validation order and errors; jwx and jose handle signature verification and JWK decoding. This makes behavior consistent without implementing cryptographic primitives.
  • Bind each key to an algorithm. A token cannot choose a different verification algorithm for its key. An explicit allow-list and strict encoding checks reject ambiguous inputs early.
  • Bound key refreshes. Concurrent callers share one fetch. Unknown key IDs can trigger a refresh, subject to a cooldown. An expired cache waits for refresh, so a withdrawn key is not silently served while an update runs.
  • Distinguish infrastructure failure. Unavailable signing keys produce a service-unavailable response rather than telling a client that a potentially valid session is unauthorized.

Quick walkthrough

  1. A service configures its trusted issuer, audience and JWKS endpoint, then attaches the Gin or Express middleware.
  2. A request passes through parsing, key lookup and signature verification before any claims are trusted.
  3. A valid token exposes claims to the handler. An expired token returns ErrExpired; missing scope returns a forbidden response. Both language implementations follow the same contract.

The example servers run against an in-repository issuer. The conformance suite compares acceptance and typed errors across implementations. The repository also includes fuzzing, property tests and cache-rotation tests.

Scope and limits

jwkit verifies compact signed JWTs; it does not provide login, encrypted tokens or every signing algorithm. Tokens must include a key ID and expiry. The TypeScript package targets Node.js; edge runtimes have not been validated. Rigforge provides a concrete integration of the Go middleware.