WINDOWS NOT SUPPORTED
A lightweight and performant JSON Web Token (JWT) library written in Zig. This library provides a simple, type-safe API for creating, signing, verifying, and parsing JWTs. It's designed to leverage Zigβs compileβtime safety and efficient memory management.
This library is built with simplicity and efficiency in mind. Whether you're building an API server or a command-line tool, our JWT library helps you manage tokenβbased authentication seamlessly with minimal overhead.
Zig >= 0.15.0
-
Token Creation & Signing: Easily create tokens with customizable headers and payloads.
-
Verification: Securely verify token signatures and check expiration dates.
-
Custom Payloads: Support for both standard and custom payload structures.
-
Compile-time Safety: Benefit from Zigβs compile-time type safety.
-
Efficient Memory Management: Uses Zigβs allocator interface for optimal resource handling.
- β | none | No digital signature or MAC value included |
- β | HS256 | HMAC using SHA-256 hash algorithm |
- β | HS384 | HMAC using SHA-384 hash algorithm |
- β | HS512 | HMAC using SHA-512 hash algorithm |
- β | ES256 | ECDSA using P-256 curve and SHA-256 hash algorithm |
- β | EdDsa | Edwards curve digital signature algorithm and SHA-512 hash algorithm |
- β | ES384 | ECDSA using P-384 curve and SHA-384 hash algorithm |
- β | PS256 | RSASSA-PSS using SHA-256 hash algorithm |
- β | ES512 | ECDSA using P-521 curve and SHA-512 hash algorithm |
- β | PS384 | RSASSA-PSS using SHA-384 hash algorithm |
- β | PS512 | RSASSA-PSS using SHA-512 hash algorithm |
- β | RS256 | RSASSA-PKCS1-v1_5 using SHA-256 hash algorithm |
- β | RS384 | RSASSA-PKCS1-v1_5 using SHA-384 hash algorithm |
- β | RS512 | RSASSA-PKCS1-v1_5 using SHA-512 hash algorithm |
// in your build.zig
const jwt = b.dependency("jwt", .{
.target = target,
.optimize = optimize,
});
exe_mod.addImport("jwt", jwt.module("jwt"));
zig fetch --save https://github.com/NikoMalik/jwt-zig/archive/refs/tags/0.5.5.tar.gz
zig fetch --save https://github.com/NikoMalik/jwt-zig/archive/refs/heads/main.tar.gz
.dependencies = .{
.jwt = .{
.url = "https://github.com/NikoMalik/jwt-zig/archive/refs/tags/0.5.5.tar.gz",
//the correct hash will be suggested by zig
}
}
```zig
.dependencies = .{
.jwt = .{
.url = "https://github.com/NikoMalik/jwt-zig/archive/refs/heads/main.tar.gz",
//the correct hash will be suggested by zig
}
}
git clone https://github.com/NikoMalik/jwt-zig.git
mv jwt-zig /path/to/your/project/directory
pub fn main() !void {
var alloc = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true }){};
defer _ = alloc.deinit();
const allocator = alloc.allocator();
// Initialize header with kid value
const header = head.Header.init(allocator, typ.Type.JWT, typ.Algorithm.ES256, .{ .kid = "kid" });
// Create standard payload
const payload = p.Payload{
.allocator = allocator,
.jti = " boy",
.iss = "iss",
.sub = "trump",
};
// Create token from header and payload
var jwtToken = jwt.Token(p.Payload).init(allocator, header, payload);
defer jwtToken.deinit();
// Load keys from PEM files
var privPem = try jwt.keyFromFile(allocator, "private_key.pem");
defer privPem.deinit();
var publicPem = try jwt.keyFromFile(allocator, "public_key.pem");
defer publicPem.deinit();
var privateBytes: [32]u8 = undefined;
@memcpy(&privateBytes, privPem.value.bytes);
var publicBytes: [65]u8 = undefined;
@memcpy(&publicBytes, publicPem.value.bytes);
// Sign and verify token
try jwtToken.sign(privateBytes[0..]); // or signToken to get full copy jwt raw
const verify = try jwtToken.verifyToken(publicBytes[0..]);
assert(verify);
}
const customPayload = struct {
user_type: []const u8,
};
pub fn main() !void {
var alloc = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true }){};
defer _ = alloc.deinit();
const allocator = alloc.allocator();
const leaks = alloc.detectLeaks();
//header init
const header = head.Header.init(allocator, typ.Type.JWT, typ.Algorithm.ES256, .{ .kid = "kid" });
//payload init
const payload = p.CustomPayload(customPayload).init(allocator, .{ .user_type = "admin" });
var jwtToken = jwt.Token(p.CustomPayload(customPayload)).init(allocator, header, payload);
defer jwtToken.deinit();
var privPem = try jwt.keyFromFile(allocator, "private_key.pem");
defer privPem.deinit();
var publicPem = try jwt.keyFromFile(allocator, "public_key.pem");
defer publicPem.deinit();
std.debug.print("private len = {d}\n", .{privPem.value.bytes.len});
std.debug.print("public len = {d}\n", .{publicPem.value.bytes.len});
var privateBytes: [32]u8 = undefined;
@memcpy(&privateBytes, privPem.value.bytes);
var publicBytes: [65]u8 = undefined;
@memcpy(&publicBytes, publicPem.value.bytes);
try jwtToken.sign(privateBytes[0..]);
const verify = try jwtToken.verifyToken(publicBytes[0..]);
std.debug.print("Verification: {any}\n", .{verify});
std.debug.print("Token: {s}\n", .{jwtToken.raw.?});
assert(verify);
}
const std = @import("std");
const jwt = @import("jwt");
const head = jwt.header;
pub fn main() !void {
var alloc = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true }){};
defer _ = alloc.deinit();
const allocator = alloc.allocator();
const header = head.Header.init(allocator, jwt.typ.Type.JWT, jwt.typ.Algorithm.ES256, .{ .kid = "kid" });
const payload = jwt.payload.Payload{
.allocator = allocator,
.jti = " boy",
.iss = "iss",
.sub = "trump",
};
var jwtToken = jwt.jwt.Token(jwt.payload.Payload).init(allocator, header, payload);
defer jwtToken.deinit();
var privPem = try jwt.jwt.keyFromFile(allocator, "private_key.pem");
defer privPem.deinit();
var publicPem = try jwt.jwt.keyFromFile(allocator, "public_key.pem");
defer publicPem.deinit();
var privateBytes: [32]u8 = undefined;
@memcpy(&privateBytes, privPem.value.bytes);
var publicBytes: [65]u8 = undefined;
@memcpy(&publicBytes, publicPem.value.bytes);
const rest = try jwtToken.signToken(privateBytes[0..]);
defer allocator.free(rest);
std.debug.print("res: {s}\n", .{rest});
const verify = try jwtToken.verifyToken(publicBytes[0..]);
std.debug.print("verify: {any}\n", .{verify});
}
const token = "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCIsImN0eSI6ImpqIn0.eyJleHAiOjJ9.UAf2dBrW6aPhw9bOteeGqda9RGlqqKA4l9XRhK3Bg";
const custom = struct {
user_type: []const u8,
};
pub fn main() void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
var token = try parse.parseToken(pl.CustomPayload(customPayload), alloc,token, null);
defer token.deinit();
const heads = try token.header.marshalJSON();
const payloads = try token.payload.marshalJSON_PAYLOAD();
defer alloc.free(payloads);
defer alloc.free(heads);
assert(verify);
}This library supports JWKS-style token verification for server-to-server authentication scenarios. The library includes a reusable client.zig module with helper functions for extracting Key IDs (kids), parsing JWKS JSON, and creating JWKS endpoints.
JwksClient.extractKid(token, allocator)- Extract the Key ID from a JWT headerJwksClient.parseJwks(jwks_json, allocator)- Parse JWKS JSON into a structured formatJwksClient.findKeyByKid(jwks, kid)- Find a specific key by its Key IDJwksClient.deinit(jwks, allocator)- Free JWKS memorycreateJwksJson(n_hex, e_hex, kid, alg, allocator)- Create JWKS JSON from RSA key parameters
const std = @import("std");
const jwt = @import("jwt");
const rsa = @import("rsa.zig");
const time = @import("time.zig");
const client = @import("client.zig");
const base64url = client.base64url;
const JwksClient = client.JwksClient;
const createJwksJson = client.createJwksJson;
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Auth server: Generate key and sign token
const rsa_algo = rsa.RSAAlgorithm(2048, .RSASSA_PKCS1_v1_5, .sha256);
var priv_key = try rsa_algo.generateKey();
defer priv_key.deinit();
var pub_key = try priv_key.publicKey();
defer pub_key.deinit();
// Export public key to PEM
const pub_key_pem = try rsa.exportPublicKey(pub_key.key, allocator);
defer allocator.free(pub_key_pem);
// Sign a token with kid
const header = jwt.header.Header.init(allocator, jwt.typ.Type.JWT, jwt.typ.Algorithm.RS256, .{ .kid = "key-id-1" });
const now = @as(u64, @intCast(std.time.timestamp()));
const payload = jwt.payload.Payload{
.allocator = allocator,
.sub = "service-abc",
.iss = "https://auth.example.com",
.exp = time.NumericDate.init(allocator, now + 3600),
};
var jwt_token = jwt.jwt.Token(jwt.payload.Payload).init(allocator, header, payload);
defer jwt_token.deinit();
var der_buffer: [4096]u8 = undefined;
const private_bytes = try priv_key.toBytes(&der_buffer);
const signed_token = try jwt_token.signToken(private_bytes);
defer allocator.free(signed_token);
// Create JWKS JSON for the public key
const ne = try pub_key.getModulusAndExponent(allocator);
defer allocator.free(ne.n);
defer allocator.free(ne.e);
const jwks_json = try createJwksJson(ne.n, ne.e, "key-id-1", "RS256", allocator);
defer allocator.free(jwks_json);
// Resource server: Extract kid and parse JWKS
const token_kid = try JwksClient.extractKid(signed_token, allocator);
defer if (token_kid) |kid| allocator.free(kid);
const jwks = try JwksClient.parseJwks(allocator, jwks_json);
defer JwksClient.deinit(jwks, allocator);
const jwk = if (token_kid) |kid| JwksClient.findKeyByKid(jwks, kid) else null;
if (jwk == null) return error.KeyNotFound;
// Parse and verify token
var iter = std.mem.splitSequence(u8, signed_token, ".");
_ = iter.first();
_ = iter.next();
const sig_b64 = iter.next() orelse return error.InvalidTokenFormat;
const sig_decoded_size = base64url.Decoder.calcSizeForSlice(sig_b64) catch 0;
var sig_buffer: [512]u8 = undefined;
_ = try base64url.Decoder.decode(sig_buffer[0..sig_decoded_size], sig_b64);
var parsed_token = try jwt.parse.parseToken(
jwt.payload.Payload,
allocator,
signed_token,
sig_buffer[0..sig_decoded_size]
);
defer parsed_token.deinit();
const verification_pub_key = try rsa_algo.PublicKey.fromPem_Der(pub_key_pem);
defer verification_pub_key.deinit();
const pub_key_der = try verification_pub_key.toBytes(&der_buffer);
const is_valid = try parsed_token.verifyToken(pub_key_der);
std.debug.print("Token verified: {}\n", .{is_valid});
}Run the complete example: zig build example
RSA keys can be serialized to DER format for storage in databases and restored later. This is useful for key management across application restarts or for distributing keys between services.
PrivateKey.toBytes(out)- Serialize private key to DER bytesPrivateKey.fromBytes(raw)- Deserialize private key from DER bytesPublicKey.toBytes(out)- Serialize public key to DER bytesPublicKey.fromBytes(raw)- Deserialize public key from DER bytes
const std = @import("std");
const rsa = @import("rsa.zig");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const rsa_algo = rsa.RSAAlgorithm(2048, .RSASSA_PKCS1_v1_5, .sha256);
// Generate key pair
var priv_key = try rsa_algo.generateKey();
defer priv_key.deinit();
var pub_key = try priv_key.publicKey();
defer pub_key.deinit();
// Serialize keys to DER bytes (for database storage)
var priv_buffer: [4096]u8 = undefined;
const priv_bytes = try priv_key.toBytes(&priv_buffer);
var pub_buffer: [4096]u8 = undefined;
const pub_bytes = try pub_key.toBytes(&pub_buffer);
// Store in database:
// INSERT INTO keys (private_key, public_key) VALUES (priv_bytes, pub_bytes);
// Load from database later:
const loaded_priv_key = try rsa_algo.PrivateKey.fromBytes(priv_bytes);
defer loaded_priv_key.deinit();
const loaded_pub_key = try rsa_algo.PublicKey.fromBytes(pub_bytes);
defer loaded_pub_key.deinit();
// Use the loaded keys for signing/verification
const msg = "test message";
var sig: rsa_algo.Signature = undefined;
const sig_len = try loaded_priv_key.sign(msg, &sig);
try rsa_algo.verify(loaded_pub_key, msg, sig[0..sig_len]);
}Run the complete example: zig build example_db
This library uses Zig's allocator interface to manage memory. When using functions like allocator.dupe(), the allocated memory must be freed by calling the corresponding deinitialization method (e.g., deinit()). Always call deinit() on tokens and parsed objects when they're no longer needed to prevent memory leaks.
Contributions are welcome! Please fork the repository, open issues, or submit pull requests with your improvements or bug fixes. Follow the project's coding style and include tests for any changes.
This library is distributed under the MIT License. You are free to use, modify, and distribute this library as per the terms of the license.
- Inspired by various JWT libraries in different languages.
- Built using Zigβs modern, safe, and efficient design principles.