LibJWT 3.5.0
The C JSON Web Token Library +JWK +JWKS
🆔 Application Profiles

Many real-world specifications are not new cryptography — they are application profiles: an ordinary signed JWT with a particular media type ("typ"), a required set of claims, and a key-binding convention. LibJWT's job is to expose the primitives; this page shows how to compose them into each profile. Every recipe below builds and verifies a token with the public API only, and is mirrored by a test in tests/jwt_profiles.c.

The reusable pieces are:

Primitive Function(s) Used by
Media-type pinning jwt_checker_expect_typ, jwt_builder_settyp all typ-bearing profiles
Algorithm allowlist jwt_checker_setalgs all (blocks alg confusion)
Required claims jwt_checker_require at+jwt, VAPID, PASSporT
Confirmation (cnf) jwt_builder_setcnf_jkt, jwt_builder_setcnf, jwt_get_cnf DPoP, mTLS
Embedded-key verify jwt_checker_enable_embedded_jwk DPoP, OpenID4VCI
Token hash jwt_token_hash DPoP ath
JWK Thumbprint jwks_item_thumbprint DPoP, OpenID4VCI
X.509 (x5c/x5t#S256) jwks_item_x5c, jwks_item_x5t_s256 PASSporT, JAdES, mTLS
Detached payload jwt_builder_set_detached, jwt_checker_verify_detached JAdES
Note
These profiles layer on a signed JWT only. LibJWT is offline: it does not implement the HTTP/TLS exchanges of DPoP, mTLS, or OpenID4VCI — only the JWT touchpoints. Pair these with the BCP 240 / RFC 8725 hardening guidance.

🎫 OAuth 2.0 access tokens — at+jwt (RFC 9068)

📄 RFC-9068 profiles a JWT access token: the header "typ" is "at+jwt" and a fixed set of claims (iss, exp, aud, sub, client_id, iat, jti) is mandatory. Pin the type and algorithm, and use jwt_checker_require to assert the mandatory claims are present (LibJWT otherwise validates only the claims you ask it to compare).

/* Issue */
jwt_builder_settyp(builder, "at+jwt");
jwt_builder_setkey(builder, JWT_ALG_ES256, signing_key);
/* ...set iss/sub/aud/client_id/jti and an exp offset... */
/* Verify */
const char *must[] = { "iss","sub","aud","exp","iat","jti","client_id" };
const jwt_alg_t algs[] = { JWT_ALG_ES256 };
jwt_checker_expect_typ(checker, "at+jwt");
jwt_checker_setalgs(checker, algs, 1);
jwt_checker_require(checker, must, 7);
jwt_checker_claim_set(checker, JWT_CLAIM_AUD, "https://rs.example");
if (jwt_checker_verify(checker, token) == 0)
/* a well-formed RFC 9068 access token */;
jwt_alg_t
JWT algorithm types.
Definition jwt.h:71
@ JWT_ALG_ES256
Definition jwt.h:79
int jwt_builder_settyp(jwt_builder_t *builder, const char *typ)
Set the token media type ("typ" header).
int jwt_builder_setkey(jwt_builder_t *builder, const jwt_alg_t alg, const jwk_item_t *key)
Sets a key and algorithm for a builder.
int jwt_checker_require(jwt_checker_t *checker, const char **claims, unsigned int count)
Require that a set of claims is present.
int jwt_checker_setalgs(jwt_checker_t *checker, const jwt_alg_t *algs, size_t n)
Set an allowlist of acceptable algorithms.
int jwt_checker_setkey(jwt_checker_t *checker, const jwt_alg_t alg, const jwk_item_t *key)
Sets a key and algorithm for a checker.
int jwt_checker_expect_typ(jwt_checker_t *checker, const char *typ)
Require a specific token media type ("typ" header).
int jwt_checker_verify(jwt_checker_t *checker, const char *token)
Verify a token.
int jwt_checker_claim_set(jwt_checker_t *checker, jwt_claims_t type, const char *value)
Set the value of a validation claim.
@ JWT_CLAIM_AUD
Definition jwt.h:395

📥 Web Push — VAPID (RFC 8292)

📄 RFC-8292 (VAPID) is the most widely deployed plain JWS-over-P-256 profile on the web: an ES256 token whose aud is the push service origin, sub a contact URI, and exp at most 24 hours out. There is no special "typ"; the discipline is the fixed algorithm and the required claims.

const char *must[] = { "aud", "exp", "sub" };
const jwt_alg_t es256[] = { JWT_ALG_ES256 };
jwt_checker_setalgs(checker, es256, 1); /* ES256 only */
jwt_checker_require(checker, must, 3);
jwt_checker_setkey(checker, JWT_ALG_ES256, app_server_pubkey);
jwt_checker_verify(checker, token);

📞 Caller ID — PASSporT (RFC 8225)

📄 RFC-8225 PASSporT (the token behind STIR/SHAKEN) signs caller-identity claims (orig, dest, iat, attest) with "typ":"passport", typically ES256, and carries the signing certificate via "x5u" or "x5c". Validate the type, algorithm, and required claims here; read the certificate chain with jwks_item_x5c (chain/trust validation against the SHAKEN CA is the caller's PKI policy — see 🖊 Detached signatures — JAdES (ETSI 119 182-1) for reading x5c).

const char *must[] = { "iat", "orig", "dest" };
const jwt_alg_t es256[] = { JWT_ALG_ES256 };
jwt_checker_expect_typ(checker, "passport");
jwt_checker_setalgs(checker, es256, 1);
jwt_checker_require(checker, must, 3);
jwt_checker_setkey(checker, JWT_ALG_ES256, shaken_leaf_key);
jwt_checker_verify(checker, token);

💳 OpenID4VCI key proof

An OpenID4VCI key proof ("typ":"openid4vci-proof+jwt") is self-contained: the signing key travels in the protected-header "jwk" (RFC-7515 Sec 4.1.3) and the issuer binds the credential to that key. Because the header key is supplied by the presenter, it must be confirmed — here against the holder-key thumbprint the issuer recorded for the request — with jwt_checker_enable_embedded_jwk, which refuses to trust an embedded key without a pin.

/* jkt is the thumbprint the issuer bound the credential request to. */
char *jkt = jwks_item_thumbprint(expected_holder_key, JWK_THUMBPRINT_SHA256);
const jwt_alg_t algs[] = { JWT_ALG_ES256 };
jwt_checker_expect_typ(checker, "openid4vci-proof+jwt");
jwt_checker_setalgs(checker, algs, 1);
jwt_checker_verify(checker, proof); /* key is the confirmed header "jwk" */
free(jkt);
char * jwks_item_thumbprint(const jwk_item_t *item, jwk_thumbprint_alg_t alg)
Compute the JWK Thumbprint of a key.
@ JWK_THUMBPRINT_SHA256
Definition jwt.h:105
int jwt_checker_enable_embedded_jwk(jwt_checker_t *checker, jwk_thumbprint_alg_t alg, const char *expected_jkt)
Verify using the key embedded in the header, pinned by thumbprint.

Proof-of-possession — DPoP (RFC 9449)

📄 RFC-9449 DPoP binds an access token to a client key. The access token carries a cnf.jkt (set with jwt_builder_setcnf_jkt); each request is accompanied by a "dpop+jwt" proof that carries the client's public key in its header "jwk" and an "ath" claim hashing the access token. To verify a proof: pin the embedded key to the access token's cnf.jkt, then check that ath matches jwt_token_hash of the presented access token (and that htm/htu match the request).

/* The access token was bound to the client key: */
jwt_builder_setcnf_jkt(at_builder, client_key); /* -> cnf.jkt */
/* The proof carries the client key in its header and hashes the AT: */
char *ath = jwt_token_hash(access_token, JWK_THUMBPRINT_SHA256);
/* ...set proof header "jwk", claims htm/htu/jti/ath, sign with client_key... */
/* Verify the proof at the resource server: */
char *jkt = jwt_get_cnf(verified_access_token, "jkt"); /* the binding */
jwt_checker_expect_typ(checker, "dpop+jwt");
jwt_checker_verify(checker, proof);
/* In a jwt_checker_setcb() callback, compare the proof's "ath" to
* jwt_token_hash(access_token, JWK_THUMBPRINT_SHA256) and check htm/htu. */
free(ath); free(jkt);
int jwt_builder_setcnf_jkt(jwt_builder_t *builder, const jwk_item_t *key)
Set the "cnf" (confirmation) claim to a key thumbprint.
char * jwt_get_cnf(const jwt_t *jwt, const char *member)
Read a string-valued "cnf" (confirmation) member from a token.
char * jwt_token_hash(const char *value, jwk_thumbprint_alg_t alg)
Compute a base64url token hash (full digest).
Note
The confirmation is not a shortcut around the signature: even when the embedded key's thumbprint matches the pin, the proof's signature must still verify against that key, so a proof that embeds the victim's key but is signed by another is rejected.

🔏 mTLS-bound tokens (RFC 8705)

📄 RFC-8705 binds an access token to a client TLS certificate via a cnf "x5t#S256" member (the certificate's SHA-256 thumbprint). Set it with jwt_builder_setcnf; at the resource server, read it with jwt_get_cnf and compare against the thumbprint of the certificate presented in the TLS handshake (which your TLS terminator provides).

/* Issue: bind to the client certificate thumbprint. */
jwt_builder_setcnf(builder, "x5t#S256", client_cert_sha256_b64url);
/* Verify (inside a jwt_checker_setcb() callback): */
char *bound = jwt_get_cnf(jwt, "x5t#S256");
if (bound && !strcmp(bound, presented_cert_thumbprint))
/* the token is being used over the bound mTLS connection */;
free(bound);
int jwt_builder_setcnf(jwt_builder_t *builder, const char *member, const char *value)
Set the "cnf" (confirmation) claim to a single string member.

🖊 Detached signatures — JAdES (ETSI 119 182-1)

JAdES (the eIDAS JWS profile) commonly signs a detached document — the payload is conveyed out of band, not inside the token — with the signing certificate chain in the "x5c" header. Sign opaque bytes unencoded (📄 RFC-7797) and detached, then verify with jwt_checker_verify_detached, supplying the document. Read the chain with jwks_item_x5c (parsed from a JWK's x5c); certificate-path validation is the caller's PKI policy.

/* Sign a detached, unencoded document with the cert chain in x5c. */
jwt_builder_setpayload(builder, document, document_len);
jwt_builder_setb64(builder, 0); /* opaque bytes, not JSON claims */
jwt_builder_set_detached(builder, 1); /* payload conveyed out of band */
/* ...set the "x5c" header to the certificate chain array... */
token = jwt_builder_generate(builder);
/* Verify with the document supplied out of band. */
jwt_checker_setkey(checker, JWT_ALG_ES256, signer_key);
jwt_checker_verify_detached(checker, token, document, document_len);
char * jwt_builder_generate(jwt_builder_t *builder)
Generate a token.
int jwt_builder_setpayload(jwt_builder_t *builder, const unsigned char *data, size_t len)
Sign an opaque (non-claims) payload.
int jwt_builder_set_detached(jwt_builder_t *builder, int detached)
Detach the payload from the output.
int jwt_builder_setb64(jwt_builder_t *builder, int b64)
Select base64url or unencoded payload (RFC 7797).
int jwt_checker_verify_detached(jwt_checker_t *checker, const char *token, const unsigned char *payload, size_t len)
Verify a token whose payload was detached.