A simple and secure WebAuthn/Passkey implementation for Rust.
- ✨ Simple API - Easy-to-use interface for passkey registration and authentication
- 🔐 Multiple Algorithms - Support for EdDSA (Ed25519), ES256/ES384 (P-256/P-384), and RS256/RS384 (RSA)
- 🛡️ Security First - Built-in replay attack protection via signature counters
- 📦 Framework Agnostic - No web framework lock-in, works with any HTTP server
- 📨 Spec JSON Shapes - Both ceremonies take
credential.toJSON()as the browser produces it, so the front end remaps nothing - 🔑 Extensions - Support for
credProps(discoverable credential reporting), PRF (key derivation / E2E encryption),largeBlob(blob storage on the authenticator),credProtect(user verification policy on security keys) andminPinLength(PIN policy on managed keys) - 💡 Hints - Steer the browser's UI toward a security key, this device or a phone, without ruling any of them out
- 🌐 Related Origins - One passkey across several domains, with a helper for
the
.well-known/webauthnfile - 🖼️ Cross-origin Iframes - Refused by default, with an opt-in allowlist of embedding origins
checked against
topOrigin - 📡 Signal API - Payloads that tell the browser when a passkey or a username changed, so stale ones stop being offered
- 📜 Attestation - Statement verification for
packed,tpm,android-keyandfido-u2f, with opt-in trust path validation against your own roots - 🦀 Pure Rust - Memory-safe implementation with no unsafe code
Add this to your Cargo.toml:
[dependencies]
passki = "0.4"use passki::{AuthenticationOptions, Passki, RegistrationOptions, StoredPasskey};
let passki = Passki::new(
"example.com", // relying party ID (the domain)
&["https://example.com"], // accepted origins
"Example Corp" // name shown in the browser prompt
);
// Registration step 1: issue a challenge
let user_id = b"unique_user_identifier_12345"; // at least 16 bytes
let (registration_challenge, registration_state) = passki.start_passkey_registration(
user_id,
"alice@example.com", // username
"Alice Smith", // display name
RegistrationOptions::default(),
).expect("user_id must be at least 16 bytes");
// Send registration_challenge to the client as JSON, keep registration_state.
// Registration step 2: verify the credential the client created
let mut stored_passkey = passki.finish_passkey_registration(
®istration_credential,
®istration_state,
)?;
// Save stored_passkey in your database, associated with the user.
// Authentication step 1: issue a challenge
let (authentication_challenge, authentication_state) = passki.start_passkey_authentication(
&user_passkeys,
AuthenticationOptions::default(),
)?;
// Authentication step 2: verify the signature
let result = passki.finish_passkey_authentication(
&authentication_credential,
&authentication_state,
&stored_passkey,
)?;
// Persist the new counter, or replay detection has nothing to compare against.
stored_passkey.counter = result.counter;- 🔒 Always use HTTPS in production - browsers refuse WebAuthn on insecure origins
- 🔄 Store the counter returned by each authentication, or cloned authenticators go undetected
- 🔐 Require user verification for sensitive operations
- ⏱️ Keep ceremony timeouts short; the state stored between the two steps expires with them
- 🖼️ Allow only the iframe embedders you trust with
with_embedding_origins
- Rust 1.85 or later (Edition 2024)
The examples/ directory has complete registration and authentication flows for several
web frameworks: Actix-web | Axum
| Poem
| Rocket | Warp
Each one also demonstrates credProps, PRF key derivation and the Signal API.
cargo run --example axum # or actix-web, poem, rocket, warpThen visit http://localhost:3000 in your browser.
WebAuthn has three specification levels published by the W3C, plus extensions that other specifications define. Checkboxes mark features currently implemented in passki.
The initial recommendation. Defined the core protocol:
- Registration ceremony (
create) and authentication ceremony (get) - Challenge generation and binding
- Client data JSON origin verification
- Authenticator data parsing
- COSE public key extraction
- Signature verification (EdDSA/Ed25519, ES256/P-256, ES384/P-384, RS256, RS384)
- Signature counter tracking and replay detection
- Credential exclusion (
excludeCredentials) -
AttestationConveyancePreference(none/indirect/direct) - Attestation object CBOR parsing
- Attestation statement verification (
packed,tpm,android-key,fido-u2f) - rpId hash verification in authenticator data
- UP (user present) flag enforcement
- UV (user verified) flag enforcement
- AAGUID exposure
-
authenticatorAttachment(platform/cross-platform) - Attestation trust path validation
A substantial expansion, still the most widely implemented level today:
- Discoverable credentials / usernameless flows (empty
allowCredentials) -
ResidentKeyRequirement(discouraged/preferred/required) -
enterpriseattestation conveyance preference - Zero-counter authenticator support
-
credPropsextension -
largeBlobextension -
userHandlein authentication response -
transportson credential descriptors
A W3C Recommendation since 25 August 2026, though browser support for its newer parts is still filling in:
- PRF extension (
prf) - BE/BS flags (backup eligibility/state)
- Related origin requests
-
RegistrationResponseJSONandAuthenticationResponseJSONrequest shapes - Signal API
-
hints(security-key/client-device/hybrid) -
attestationFormats -
evalByCredentialin theprfextension -
compoundattestation statement format - Cross-origin ceremonies in iframes, verifying
topOrigin
These extensions are registered in the IANA WebAuthn extension identifiers registry but specified elsewhere, so they are not tied to a WebAuthn level:
-
credProtectextension (CTAP 2.1 §12.1) -
minPinLengthextension (CTAP 2.1 §12.4) -
paymentextension (Secure Payment Confirmation §5)
This project is licensed under the Apache License, Version 2.0 (LICENSE).
Contributions are welcome! Please feel free to submit a Pull Request.