Skip to content

NikoMalik/jwt-zig

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

JWT Zig Library πŸš€

Github Repo Issues GitHub Repo stars

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.


Overview ✨

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 Version

Zig >= 0.15.0

Features πŸ”₯

  • 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.


Algorithms πŸ”‘

  • βœ… | 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 |

Installation πŸ“¦

// 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

or

.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
    }
}



Installation/2 πŸ“¦

git clone https://github.com/NikoMalik/jwt-zig.git
mv jwt-zig /path/to/your/project/directory

Usage πŸ› 

Registered Payload

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);
}

Custom Payload

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);
}

Another example

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});
}



Parsing a Token

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);


}

JWKS-Style Token Verification πŸ”

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.

Available Client Functions

  • JwksClient.extractKid(token, allocator) - Extract the Key ID from a JWT header
  • JwksClient.parseJwks(jwks_json, allocator) - Parse JWKS JSON into a structured format
  • JwksClient.findKeyByKid(jwks, kid) - Find a specific key by its Key ID
  • JwksClient.deinit(jwks, allocator) - Free JWKS memory
  • createJwksJson(n_hex, e_hex, kid, alg, allocator) - Create JWKS JSON from RSA key parameters

Complete Example

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

Database Storage of Keys πŸ’Ύ

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.

Available Methods

  • PrivateKey.toBytes(out) - Serialize private key to DER bytes
  • PrivateKey.fromBytes(raw) - Deserialize private key from DER bytes
  • PublicKey.toBytes(out) - Serialize public key to DER bytes
  • PublicKey.fromBytes(raw) - Deserialize public key from DER bytes

Example

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

Memory Management 🧠

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.

Contributing 🀝

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.

License πŸ“œ

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.

Acknowledgements πŸ™Œ

  • Inspired by various JWT libraries in different languages.
  • Built using Zig’s modern, safe, and efficient design principles.

About

JWT impl in zig

Resources

License

Stars

Watchers

Forks

Sponsor this project

Packages

No packages published

Contributors 3

  •  
  •  
  •