Skip to content

uzyn/passcay

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

32 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Passcay

Tests

Passcay Logo

Minimal, fast and secure Passkey (WebAuthn) relying party (RP) library for Zig.

Zig version support:

  1. v3.x - Supports Zig v0.16. First version with no system dependencies (OpenSSL is no longer required).
  2. v2.x - Supports Zig v0.15
  3. v1.x - Supports Zig v0.14

Features

  • Passkey WebAuthn registration
  • Passkey WebAuthn authentication/verification (login)
  • Attestation-less passkey usage (privacy-preserving, does not affect security)
  • Cryptographic signature verification. Supports both ES256 & RS256, covering 100% of all Passkey authenticators today.
  • Secure challenge generation

Dependencies

No system dependencies. Cryptographic verification uses Zig's standard library (std.crypto), so Passcay builds and cross-compiles anywhere Zig runs, including Windows and macOS.

Installation

Add passcay to your Zig project (build.zig.zon) dependencies:

zig fetch --save git+https://github.com/uzyn/passcay.git

# or load a specific version
zig fetch --save git+https://github.com/uzyn/passcay.git#3.0.0

And update your build.zig to load passcay:

const passcay = b.dependency("passcay", .{
    .optimize = optimize,
    .target = target,
});
exe.root_module.addImport("passcay", passcay.module("passcay"));

Build & Test

zig build
zig build test --summary all

Usage

Registration

const passcay = @import("passcay");

const input = passcay.register.RegVerifyInput{
     .attestation_object = attestation_object,
     .client_data_json = client_data_json,
};

const expectations = passcay.register.RegVerifyExpectations{
     .challenge = challenge,
     .origin = "https://example.com",
     .rp_id = "example.com",
     .require_user_verification = true,
};

const reg = try passcay.register.verify(allocator, input, expectations);

// Save reg.credential_id, reg.public_key, and reg.sign_count
// to database for authentication

Store the following in database for authentication:

  • reg.credential_id
  • reg.public_key
  • reg.sign_count (usually starts at 0)

Authentication

const challenge = try passcay.challenge.generate(io, allocator);
// Pass challenge to client-side for authentication

const input = passcay.auth.AuthVerifyInput{
    .authenticator_data = authenticator_data,
    .client_data_json = client_data_json,
    .signature = signature,
};

const expectations = passcay.auth.AuthVerifyExpectations{
    .public_key = user_public_key, // Retrieve public_key from database, given credential_id from navigator.credentials.get
    .challenge = challenge,
    .origin = "https://example.com",
    .rp_id = "example.com",
    .require_user_verification = true,
    .enable_sign_count_check = true,
    .known_sign_count = stored_sign_count,
};

const auth = try passcay.auth.verify(allocator, input, expectations);

Update the stored sign count with auth.recommended_sign_count:

Client-Side (JavaScript)

// Registration
const regOptions = {
    challenge: base64UrlDecode(challenge),
    rp: {
        name: "Example",
        id: "example.com", // Must match your domain without protocol/port
    },
    user: { name: username },
    pubKeyCredParams: [
        { type: "public-key", alg: -7 },   // ES256 (Most widely supported)
        { type: "public-key", alg: -257 }, // RS256
    ],
    authenticatorSelection: {
        authenticatorAttachment: "platform",
        userVerification: "required", // or "preferred"
    },
    attestation: "none", // Fast & privacy-preserving auth without security compromise
};

const credential = await navigator.credentials.create({ publicKey: regOptions });
console.log('Credential details:', credential);
// Pass credential to server for verification: passcay.register.verify

// Authentication
const authOptions = {
  challenge: base64UrlDecode(challenge),
  rpId: 'example.com',
  userVerification: 'preferred',
};
const assertion = await navigator.credentials.get({ publicKey: authOptions });
console.log('Assertion details:', assertion);
// Retrieve public_key from assertion_id that's returned
// Pass assertion to server for verification: passcay.auth.verify
JavaScript utils for base64url <-> ArrayBuffer
// Convert base64url <-> ArrayBuffer
function base64UrlToBuffer(b64url) {
  const pad = '='.repeat((4 - (b64url.length % 4)) % 4);
  const b64 = (b64url + pad).replace(/-/g, '+').replace(/_/g, '/');
  const bin = atob(b64);
  const arr = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
  return arr.buffer;
}
function bufferToBase64Url(buf) {
  const bytes = new Uint8Array(buf);
  let bin = '';
  for (const b of bytes) bin += String.fromCharCode(b);
  return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

Docs

Reference implementations for integrating Passcay into your application:

  • docs/register.md - Registration flow with challenge generation
  • docs/login.md - Authentication flow with verification

See also

For passkey authenticator implementations and library for Zig, check out Zig-Sec/keylib.

Spec references

License

This project is licensed under the MIT License. See the LICENSE file for details.

Copyright (c) 2025 U-Zyn Chua.

About

πŸ¦ŽπŸ”‘ Secure & fast Passkey (WebAuthn) library for Zig

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages