This page walks through common end-to-end tasks with LibJWT, starting from the simplest possible case and gradually layering in more advanced concepts. Each example introduces only one or two new ideas, so they are meant to be read in order.
Almost everything in LibJWT is built around two pairs of objects:
The pattern is always the same: create the object, configure it (a key, some claims, perhaps a callback), then call a single function to produce or consume a token.
- Note
- To keep them readable, the examples below check only the most important return values. Real applications should check every return code (using jwt_builder_error_msg() and friends to report failures) and are encouraged to use the jwt_builder_auto_t / jwt_checker_auto_t / jwe_builder_auto_t / jwe_checker_auto_t / jwk_set_auto_t cleanup types (on GCC and Clang) so objects are freed automatically when they go out of scope.
✏ Signing and Verifying (JWS)
These examples cover creating and consuming signed tokens with the Builder and Checker.
Signing your first token
The simplest token is signed with HS256, an HMAC over a shared secret. The secret is just a JWK with "kty":"oct". Loading a key is always a two-step process: read the JSON into a keyring (jwk_set_t) with one of the jwks_create_* functions, then pull the individual key (jwk_item_t) out of it with jwks_item_get.
#include <stdio.h>
#include <stdlib.h>
#include <jwt.h>
int main(void)
{
char *token = NULL;
if (token == NULL)
else
printf("%s\n", token);
free(token);
return 0;
}
jwk_set_t * jwks_create_fromfile(const char *file_name)
Wrapper around jwks_load_fromfile() that explicitly creates a new keyring.
struct jwk_set jwk_set_t
Opaque JWKS object.
Definition jwt.h:50
void jwks_free(jwk_set_t *jwk_set)
const jwk_item_t * jwks_item_get(const jwk_set_t *jwk_set, size_t index)
Return the index'th jwk_item in the jwk_set.
struct jwk_item jwk_item_t
Object representation of a JWK.
Definition jwt.h:276
@ JWT_ALG_HS256
Definition jwt.h:61
const char * jwt_builder_error_msg(const jwt_builder_t *builder)
Get the error message contained in a builder object.
struct jwt_builder jwt_builder_t
Opaque Builder Object.
Definition jwt.h:369
jwt_builder_t * jwt_builder_new(void)
Function to create a new builder instance.
char * jwt_builder_generate(jwt_builder_t *builder)
Generate a token.
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.
void jwt_builder_free(jwt_builder_t *builder)
Frees a previously created builder object.
By default the builder adds an iat (Issued At) claim. You can turn that off with jwt_builder_enable_iat.
Verifying a token
Verifying mirrors signing: load the same key, set it on a jwt_checker_t with the algorithm you expect, then call jwt_checker_verify. A return of 0 means the signature (and any configured claim checks) passed.
printf("token is valid\n");
else
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.
struct jwt_checker jwt_checker_t
Opaque Checker object.
Definition jwt.h:637
jwt_checker_t * jwt_checker_new(void)
Function to create a new checker instance.
void jwt_checker_free(jwt_checker_t *checker)
Frees a previously created checker object.
const char * jwt_checker_error_msg(const jwt_checker_t *checker)
Get the error message contained in a checker object.
int jwt_checker_verify(jwt_checker_t *checker, const char *token)
Verify a token.
- Note
- Pinning the algorithm with jwt_checker_setkey is a security feature: the checker will reject a token whose alg header does not match the algorithm you configured. This prevents algorithm-confusion attacks.
Adding and validating claims
Claims and headers are set through a small helper struct, jwt_value_t. You fill it in with one of the jwt_set_SET_* macros, then hand it to jwt_builder_claim_set. Anything you set on the builder is copied into every token it generates.
jwt_value_error_t jwt_builder_claim_set(jwt_builder_t *builder, jwt_value_t *value)
Set a claim in a builder object.
#define jwt_set_SET_STR(__v, __n, __x)
Setup a jwt_value_t to set a string value.
Definition jwt.h:985
#define jwt_set_SET_INT(__v, __n, __x)
Setup a jwt_value_t to set an integer value.
Definition jwt.h:972
Data type for get and set actions for JWT headers and claims.
Definition jwt.h:255
On the verifying side, the checker can validate a handful of registered claims for you with a simple comparison. Use jwt_checker_claim_set with the claim you care about; verification then fails if the token's value does not match.
printf("issuer and audience accepted\n");
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:341
@ JWT_CLAIM_ISS
Definition jwt.h:339
The checker validates iss, aud, and sub by string comparison, and exp/nbf by time (see below). For anything more involved — multiple acceptable issuers, custom claims, business rules — use a callback (Dynamic tokens with a callback).
Expiration and not-before
The exp (Expiration) and nbf (Not Before) claims are time-based, so LibJWT manages them as offsets from "now". On the builder, set the offset in seconds with jwt_builder_time_offset.
int jwt_builder_time_offset(jwt_builder_t *builder, jwt_claims_t claim, time_t secs)
Disable, or enable and set the nbf or exp time offsets.
@ JWT_CLAIM_NBF
Definition jwt.h:343
@ JWT_CLAIM_EXP
Definition jwt.h:342
The checker validates exp and nbf automatically whenever they are present. To tolerate small clock differences between machines, give it some leeway (in seconds) with jwt_checker_time_leeway.
int jwt_checker_time_leeway(jwt_checker_t *checker, jwt_claims_t claim, time_t secs)
Setup the exp or nbf claim leeway values.
Critical headers
Sometimes a token carries a custom header that the recipient must understand. RFC-7515 Sec 4.1.11 handles this with the crit (Critical) header, which lists header names that cannot be ignored. On the builder, register each critical name with jwt_builder_setcrit; the header itself is added in a callback (see Dynamic tokens with a callback).
int jwt_builder_setcrit(jwt_builder_t *builder, const char *header)
Mark a header parameter as critical (RFC-7515 Sec 4.1.11).
A checker rejects any token whose crit header lists a name it has not been told to expect. Declare the names your application understands with jwt_checker_understands.
int jwt_checker_understands(jwt_checker_t *checker, const char *header)
Declare a critical header parameter as understood (RFC-7515 Sec 4.1.11).
Dynamic tokens with a callback
Everything above is static — the same configuration applies to every token. When something must change per token (a different sub for each user, a key chosen at runtime, a unique jti), use a callback. The builder invokes it during jwt_builder_generate, after the token object is created but before it is signed, giving you a jwt_t to modify and a jwt_config_t to set the key and algorithm.
{
const char *user = config->
ctx;
config->
key = my_lookup_key(user);
return 0;
}
int jwt_builder_setcb(jwt_builder_t *builder, jwt_callback_t cb, void *ctx)
Set a callback for generating tokens.
jwt_value_error_t jwt_claim_set(jwt_t *jwt, jwt_value_t *value)
Set a value in the claims of a JWT.
struct jwt jwt_t
Opaque JWT object.
Definition jwt.h:39
Structure used to pass state with a user callback.
Definition jwt.h:302
const jwk_item_t * key
Definition jwt.h:303
void * ctx
Definition jwt.h:305
jwt_alg_t alg
Definition jwt.h:304
The checker has the mirror-image hook, jwt_checker_setcb — it runs during verification so you can inspect the header and claims, or select the verification key, before the signature is checked. (A checker callback should only read the jwt_t — changes to it do not affect verification.)
- Note
- For the very common case of a unique token id, the builder offers a dedicated jwt_builder_setjti hook to generate the jti claim, paired with jwt_checker_setjti on the checker for replay protection.
🔒 Encrypting and Decrypting (JWE)
Encrypted tokens use the Builder and Checker. The main difference from JWS is that JWE needs two algorithms: a key management algorithm (the "alg" header, jwe_key_alg_t) that establishes the Content Encryption Key (CEK), and a content encryption algorithm (the "enc" header, jwe_enc_t) that actually encrypts the payload.
Encrypting your first token
The simplest JWE uses dir (Direct Encryption): a shared symmetric key is the CEK, so there is no key wrapping. Here it is paired with A256GCM content encryption. The key is a JWK with "kty":"oct". Because the plaintext can be arbitrary bytes, jwe_builder_generate takes an explicit length.
#include <stdlib.h>
#include <string.h>
#include <jwt.h>
const char *message = "the launch code is 0000";
char *token = NULL;
strlen(message));
if (token == NULL)
else
printf("%s\n", token);
free(token);
int jwe_builder_setkey(jwe_builder_t *builder, jwe_key_alg_t alg, jwe_enc_t enc, const jwk_item_t *key)
Set the key and algorithms for a JWE builder.
char * jwe_builder_generate(jwe_builder_t *builder, const unsigned char *plaintext, size_t plaintext_len)
Encrypt a plaintext into a JWE.
struct jwe_builder jwe_builder_t
Opaque JWE Builder (encryption) object.
Definition jwt.h:1460
const char * jwe_builder_error_msg(const jwe_builder_t *builder)
Get the error message contained in a JWE builder object.
jwe_builder_t * jwe_builder_new(void)
Create a new JWE builder instance.
void jwe_builder_free(jwe_builder_t *builder)
Free a previously created JWE builder object.
@ JWE_ALG_DIR
Definition jwt.h:94
@ JWE_ENC_A256GCM
Definition jwt.h:120
Decrypting a token
To decrypt, configure a jwe_checker_t with the same key and the algorithms you expect, then call jwe_checker_decrypt. It returns a newly allocated buffer and, through its out-parameter, the true plaintext length. The buffer is also nil-terminated for convenience.
unsigned char *plaintext = NULL;
size_t len = 0;
if (plaintext == NULL)
else
printf("recovered %zu bytes: %s\n", len, plaintext);
free(plaintext);
void jwe_checker_free(jwe_checker_t *checker)
Free a previously created JWE checker object.
const char * jwe_checker_error_msg(const jwe_checker_t *checker)
Get the error message contained in a JWE checker object.
int jwe_checker_setkey(jwe_checker_t *checker, jwe_key_alg_t alg, jwe_enc_t enc, const jwk_item_t *key)
Set the key and algorithms for a JWE checker.
jwe_checker_t * jwe_checker_new(void)
Create a new JWE checker instance.
unsigned char * jwe_checker_decrypt(jwe_checker_t *checker, const char *token, size_t *plaintext_len)
Decrypt and authenticate a Compact Serialization JWE.
struct jwe_checker jwe_checker_t
Opaque JWE Checker (decryption) object.
Definition jwt.h:1744
- Note
- Decryption with an authenticated mode like A256GCM also verifies the token. If the ciphertext, IV, or tag has been altered, decryption fails and returns NULL — there is no separate "verify" step.
Encrypting to a recipient's public key
dir requires both sides to share a secret in advance. More often you want to encrypt to someone's public key so that only their private key can decrypt. That is what the asymmetric key management algorithms are for. Here we use RSA-OAEP-256: only the alg changes — the rest of the flow is identical.
strlen(message));
@ JWE_ALG_RSA_OAEP_256
Definition jwt.h:99
The recipient decrypts exactly as in Decrypting a token, but using their RSA private key and the matching JWE_ALG_RSA_OAEP_256.
JSON serialization and AAD
Every example so far produced the RFC-7516 Sec 7.1 Compact Serialization — the five-part a.b.c.d.e string. The RFC-7516 Sec 7.2 JSON Serialization is a JSON object instead, and it can carry things the compact form cannot, such as Additional Authenticated Data (AAD): bytes that are authenticated (bound into the tag) but not encrypted. Select the JSON form with jwe_builder_set_format and attach AAD with jwe_builder_set_aad.
static const unsigned char aad[] = "context-id:42";
strlen(message));
int jwe_builder_set_aad(jwe_builder_t *builder, const unsigned char *aad, size_t aad_len)
Set the JWE Additional Authenticated Data.
int jwe_builder_set_format(jwe_builder_t *builder, jwe_serialization_t format)
Select the serialization jwe_builder_generate produces.
@ JWE_FORMAT_JSON_FLAT
Definition jwt.h:140
A JSON token may be compact or JSON, so on the consuming side use jwe_checker_decrypt_all, which auto-detects the serialization. After a successful decrypt you can recover the authenticated AAD with jwe_checker_get_aad.
size_t aad_len = 0;
const unsigned char *got = NULL;
if (got != NULL)
printf("authenticated AAD: %.*s\n", (int)aad_len, got);
unsigned char * jwe_checker_decrypt_all(jwe_checker_t *checker, const char *token, size_t *plaintext_len)
Decrypt and authenticate a JWE in any serialization.
const unsigned char * jwe_checker_get_aad(const jwe_checker_t *checker, size_t *aad_len)
Get the JWE AAD recovered from a JSON-serialized token.
Multiple recipients
The General JSON Serialization can address several recipients at once: the payload is encrypted a single time with one CEK, and that CEK is then wrapped independently for each recipient. Any one recipient can decrypt the token with their own key. Configure the first recipient with jwe_builder_setkey as usual, then add each additional one with jwe_builder_add_recipient. Adding a second recipient automatically switches output to JWE_FORMAT_JSON_GENERAL.
strlen(message));
jwe_recipient_t * jwe_builder_add_recipient(jwe_builder_t *builder, jwe_key_alg_t alg, const jwk_item_t *key)
Add a recipient to a General JSON Serialization JWE.
@ JWE_ALG_ECDH_ES_A128KW
Definition jwt.h:101
Each recipient decrypts with their own key. The checker is configured with that recipient's key and algorithm, and jwe_checker_decrypt_all finds the matching entry in the recipients array.
- Note
- dir and ECDH-ES Direct (JWE_ALG_ECDH_ES) derive the CEK directly from a single key, so they cannot be combined with other recipients.
ECDH-ES and PartyInfo
ECDH-ES derives the CEK from an (ephemeral) Diffie-Hellman exchange against the recipient's EC key. The derivation (a Concat KDF, RFC-7518 Sec 4.6) can optionally mix in two application-supplied octet strings, PartyUInfo and PartyVInfo, which are also emitted as the apu and apv headers. Set them with jwe_builder_set_partyinfo.
(const unsigned char *)"Alice", 5,
(const unsigned char *)"Bob", 3);
strlen(message));
int jwe_builder_set_partyinfo(jwe_builder_t *builder, const unsigned char *apu, size_t apu_len, const unsigned char *apv, size_t apv_len)
Set the ECDH-ES PartyUInfo / PartyVInfo.
@ JWE_ALG_ECDH_ES
Definition jwt.h:100
The decrypting side sets the same algorithms and uses its EC private key; the apu/apv values travel in the token, so the checker reproduces the same derivation automatically.
- Note
- For per-recipient PartyInfo or per-recipient headers in a General JSON token, use the recipient handle returned by jwe_builder_add_recipient with jwe_recipient_set_partyinfo and jwe_recipient_add_header_json.