Demo: passkey (WebAuthn) login
Location: contrib/demo/passkey/
Starts a single ahdapa instance configured for WebAuthn passkey authentication using static users — no FreeIPA or Kerberos infrastructure is required. On first run only password login is available; a registration helper page lets you create a passkey credential and add it to the users file.
What it shows
- WebAuthn credential registration — a helper page (HTML + CSS + JS) served by the
demo instance walks through the
navigator.credentials.create()call and generates thepasskey:<credentialId>,<SPKI_DER>line for the users file. FreeIPA stores passkey public keys as base64-encoded SPKI DER (the content of a PEMBEGIN PUBLIC KEYblock); the static users file uses the same format. - Passkey login — after adding the credential to the overlay file, entering
a username on the login page triggers the
navigator.credentials.get()prompt instead of (or before) the password field. - Overlay-based credential management — a gitignored overlay file is merged
with the base users file at startup. The overlay takes precedence over same-
username entries in the base file, so passkey credentials can be added without
modifying the tracked
users.toml.in. - RP ID scoping — the demo uses RP ID
localhost. Passkeys registered with this configuration are tied tolocalhostand will not work on any other origin.
Prerequisites
ahdapabinary — the script looks in$PATHfirst (resolved to its full path), thentarget/release/ahdapa, thentarget/debug/ahdapa, and falls back tocargo buildif none is found.npm— for building the WebUIdist/directory on first run.- A browser that supports WebAuthn (all modern browsers do).
- Port 8080 free.
- For hardware security key testing: a FIDO2-capable key (YubiKey, SoloKey, etc.)
- For virtual authenticator testing: Chrome DevTools → Application → Web Authentication → Enable virtual authenticator.
Running
contrib/demo/passkey/run.sh
On first run (when no webui/dist/ directory and no pre-built binary exist)
the script builds the WebUI and the binary, then starts ahdapa. If a binary
is already installed or built, it is used directly and the build step is
skipped. The selected binary path is printed before launch:
==> Building WebUI (first run)…
Using binary: /home/user/ahdapa/target/release/ahdapa
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PASSKEY DEMO
Server → http://localhost:8080
WebUI → http://localhost:8080/ui/
OIDC discovery → http://localhost:8080/.well-known/openid-configuration
Register page → http://localhost:8080/ui/passkey-register.html
PQC probe → http://localhost:8080/ui/pqc-probe.html
Password logins (generated at startup):
alice / <password> (admins, editors)
bob / <password> (editors)
carol / <password>
Passkey login: no
RP ID: localhost
DB: /tmp/ahdapa-passkey-demo.db (delete to reset)
Press Ctrl-C to stop.
Enabling passkey login
Step 1 — start the demo
contrib/demo/passkey/run.sh
Step 2 — register a passkey for alice
Open http://localhost:8080/ui/passkey-register.html in a browser. Enter
alice and click Register passkey. The browser prompts for authentication
via platform authenticator, a hardware key, or a virtual authenticator in
DevTools.
After the ceremony completes, the page displays a line in the form:
passkey:<base64-credentialId>,<base64-SPKI-DER>
Copy this line.
Step 3 — create the overlay file
cp contrib/demo/passkey/users.passkeys.toml.example \
contrib/demo/passkey/users.passkeys.toml
Edit users.passkeys.toml and paste the copied line into alice’s passkeys
array. Ensure the password field is also present so password login continues
to work:
[[user]]
username = "alice"
password = "<password printed at startup>"
name = "Alice Admin"
given_name = "Alice"
family_name = "Admin"
email = "alice@dev.local"
groups = ["admins", "editors"]
passkeys = [
"passkey:AAAA...base64...,BBBB...base64...",
]
Step 4 — restart the demo
Stop the running server (Ctrl-C), then:
contrib/demo/passkey/run.sh
The startup output now shows Passkey login: yes (alice). On the login page,
entering alice triggers the passkey prompt from the browser instead of the
password field.
Probing post-quantum passkeys
http://localhost:8080/ui/pqc-probe.html checks whether the browser and
authenticator can create and use an ML-DSA passkey (COSE algorithms -48,
-49 and -50, key type AKP). It runs a registration and an assertion
against RP ID localhost without involving the server, and shows what the
authenticator returned and what the browser understood: the COSE key, the
getPublicKey() SPKI, the attestation format, algorithm and certificates,
and the assertion signature length. Copy JSON exports the raw ceremony
data so it can be verified offline.
Offering only ML-DSA algorithms makes Chrome log a console warning about
missing ES256 and RS256; it does not affect the ceremony. Firefox
(154–156) cannot register with ML-DSA alone and reports a generic
NotAllowedError; check ES256 as well and it falls back to it
(bug 2064183).
Recording a probe for the test suite
A probe export can be replayed through ahdapa’s own registration and assertion verification, which checks a real authenticator end to end:
-
Open the probe page on
localhost, either through the running demo or with any static server — the page needs no ahdapa endpoint:cd contrib/demo/passkey && python3 -m http.server 8000 --bind 127.0.0.1 # then open http://localhost:8000/pqc-probe.htmlUse the host name
localhost, not127.0.0.1: the page registers for RP IDlocalhost, and WebAuthn only accepts an RP ID that matches the page’s host, so a page opened on127.0.0.1cannot use it. -
Pick the algorithms, click 1. Register, then 2. Sign in, touching the authenticator each time. The export needs both ceremonies of the same run — each Register creates a new credential and Sign in uses the latest one — so wait for the Assertion table to fill.
-
Click Copy JSON and save the clipboard to a file outside the repository:
wl-paste > ~/passkey-probes/yubikey-mldsa65.json # Wayland xclip -o -selection clipboard > ~/passkey-probes/yubikey-mldsa65.json # X11 -
Run the ignored test against it:
AHDAPA_PASSKEY_PROBE_JSON=~/passkey-probes/yubikey-mldsa65.json \ cargo test --bin ahdapa probe_export -- --ignored
The test takes the RP ID and origin from the export, so any port works, and it is not limited to ML-DSA: an ES256 or EdDSA probe verifies the same way.
Keep exports private and out of the repository. With direct
attestation they contain the authenticator’s attestation certificate,
which may name its serial number (YubiKeys use
CN=Yubico FIDO EE Serial <n>).
The page keeps its scripts and styles in separate files because the /ui/
Content Security Policy rejects inline ones.
Configuration notes
| File | Description |
|---|---|
ahdapa.toml | Server config with passkey_rp_id = "localhost" and GSSAPI disabled |
users.toml.in | Users template (alice, bob, carol); passwords substituted at runtime |
users.passkeys.toml.example | Template for the passkey overlay; copy and fill in credential |
users.passkeys.toml | Your local overlay (gitignored); merged with users.toml at startup |
passkey-register.{html,css,js} | Registration helper page; served from webui/dist/ |
pqc-probe.{html,css,js} | ML-DSA (post-quantum) passkey probe; served from webui/dist/; exports test vectors for probe_export_from_real_authenticator_verifies |
run.sh | Build and launch script |
The run.sh script merges the overlay and base files before starting:
# If overlay exists, prepend it (overlay entries take precedence).
cat users.passkeys.toml users.toml > /tmp/ahdapa-passkey-demo-users.toml
The merged file path is set in ahdapa.toml:
[users]
file = "/tmp/ahdapa-passkey-demo-users.toml"
RP ID
The passkey_rp_id must match the origin of the page that registered the
credential. For localhost development it is always "localhost". For a
production FreeIPA deployment, set it to the IPA domain name (e.g.
"ipa.example.com"), which is also what FreeIPA uses when managing passkeys
via ipa user-add-passkey.
[ipa]
passkey_rp_id = "localhost"
Resetting
Delete the demo database to discard all CRDT state (OAuth2 clients, keys) and start fresh:
rm /tmp/ahdapa-passkey-demo.db
The passkey credential registered in the browser is separate from the database: it is stored in the operating system’s credential store or on the hardware key. You can re-register any time using the registration page.
See also
- Authentication Methods — passkey and WebAuthn configuration reference.
- FreeIPA Co-deployment — passkey management
integrated with FreeIPA
ipa user-add-passkey.