A tiny, dependency-free, cross-platform library that runs the operating system's native passkey flow from Java.
A passkey assertion proves possession of a credential a relying party registered by signing a server-issued
challenge. Every desktop platform ships a credential service that can reach a passkey stored on the machine,
synced from the user's phone, or reached over the cross-device hybrid transport, and surface the platform's
own confirmation UI. Warden drives those services, Windows Hello, macOS AuthenticationServices, and Linux
libfido2, behind one small contract, so a desktop application can satisfy a WebAuthn get ceremony with a
real platform prompt and no embedded browser.
It does the WebAuthn plumbing for you: it builds the clientDataJSON, hands the challenge to the platform,
and returns the authenticatorData, signature, credential id, user handle, and optional PRF output your
server needs to verify the assertion.
import com.github.auties00.warden.PasskeyRequest;
import com.github.auties00.warden.WardenAuthenticator;
// 1. Get an authenticator for the host platform.
var authenticator = WardenAuthenticator.create();
// 2. Build a request. The short constructor fills sensible defaults
// (no allow list, "preferred" user verification, a 2-minute timeout).
var request = new PasskeyRequest("example.com", challengeBytes, "https://example.com");
// 3. Run the ceremony, anchoring the prompt to your window. This opens the platform
// prompt and blocks until the user completes or declines it, so call it off any UI
// thread. Pass your window's native handle (a Win32 HWND, an NSWindow* on macOS), or
// WardenAuthenticator.NO_WINDOW to let the backend pick a platform default.
//
// PasskeyException is a sealed hierarchy, so the outcomes can be handled exhaustively.
try {
var assertion = authenticator.getAssertion(request, myWindowHandle);
send(assertion.authenticatorData(), assertion.clientDataJson(),
assertion.signature(), assertion.credentialId(), assertion.userHandle());
} catch (PasskeyException failure) {
// See the outcomes section
}For full control, use the canonical constructor (allow list, user verification, timeout, PRF input):
var request = new PasskeyRequest(
"example.com", challengeBytes,
new byte[][] { credentialId }, // allow list (empty for a discoverable request)
UserVerification.REQUIRED,
Duration.ofMinutes(1),
prfEvalFirst, // or null for no PRF
"https://example.com");PasskeyRequest.toWebAuthnGetJson() serialises the request into the { "publicKey": ... } JSON a browser's
navigator.credentials.get accepts, for when you'd rather relay the ceremony to a device you control.
getAssertion returns the PasskeyAssertion on success and otherwise throws one of a sealed
PasskeyException hierarchy, so every case can be handled exhaustively:
| Exception | Meaning |
|---|---|
PasskeyNotAllowedException |
The user cancelled or dismissed the prompt, the ceremony timed out, or no usable credential was available. WebAuthn deliberately makes these indistinguishable, so Windows throws this base type; Linux and macOS throw a leaf where they can: PasskeyCancelled/TimedOut/NoCredentialException. |
PasskeyConstraintException |
The requested user verification could not be satisfied, for example UserVerification.REQUIRED on an authenticator with no PIN or biometric. PREFERRED and DISCOURAGED never raise it. |
PasskeyFailedException |
A genuine platform failure: a malformed request, an authenticator or transport error, anything not modelled above. |
PasskeyUnavailableException |
The native backend could not be initialised (also thrown by create() when the platform service is present but cannot come up). |
For handling the result, prefer a switch over the caught exception, as in the snippet above. Because the
hierarchy is sealed the switch is exhaustive, so the compiler accepts it with no default clause (Java 21's
pattern matching for switch). The draft
exception handling in switch proposal extends this further, letting a
switch catch the selector's exceptions directly with case throws, which the sealed hierarchy again makes
exhaustive.
Maven:
<dependency>
<groupId>com.github.auties00</groupId>
<artifactId>warden</artifactId>
<version>1.0.0</version>
</dependency>Gradle:
implementation("com.github.auties00:warden:1.0.0")The backends issue restricted FFM calls into the platform credential service. On Java 24+ this prints a warning unless native access is granted. Grant it to the module (or the unnamed module on the classpath):
# modular
java --enable-native-access=com.github.auties00.warden -m your.app/...
# classpath
java --enable-native-access=ALL-UNNAMED -cp ... YourApp| Platform | Backend | Mechanism |
|---|---|---|
| Windows (10 1903+) | webauthn.dll |
WebAuthNAuthenticatorGetAssertion (Windows Hello) |
| macOS (experimental) | AuthenticationServices |
ASAuthorizationController driven through the Objective-C runtime |
| Linux | libfido2 |
fido_dev_get_assert against a roaming/hybrid FIDO2 authenticator |
| other | none | unsupported (create() throws UnsupportedOperationException) |
WardenAuthenticator.create() picks the backend for the host and resolves its native library eagerly, so a
missing service surfaces immediately as a PasskeyUnavailableException. WardenAuthenticator.isSupported()
reports whether the host platform family has a backend at all, and authenticator.platform() reports which one
you got.
macOS is experimental. AuthenticationServices delivers its result asynchronously and demands a
presentation anchor (a key window) and an app bound to the relying party by an associated-domains entitlement.
A library cannot synthesise those, so the macOS backend works only when Warden runs inside a Cocoa application
that supplies them; in a headless process the ceremony fails cleanly rather than returning a wrong result. The
macOS backend also returns the credential's own rawClientDataJSON, because AuthenticationServices builds the
clientDataJSON itself rather than from the request's origin.
A non-null PasskeyRequest.prfEvalFirst() requests the WebAuthn PRF (hmac-secret) extension. Warden maps the
evaluation input to the salt the same way a browser does (SHA-256("WebAuthn PRF" || 0x00 || input)) and
returns the result in PasskeyAssertion.prfOutput(). PRF is wired on Windows (assertion-options version 5+)
and Linux (libfido2); it is not wired on the macOS backend.
MIT © Alessandro Autiero