Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 the passkey:<credentialId>,<SPKI_DER> line for the users file. FreeIPA stores passkey public keys as base64-encoded SPKI DER (the content of a PEM BEGIN PUBLIC KEY block); 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 to localhost and will not work on any other origin.

Prerequisites

  • ahdapa binary — the script looks in $PATH first (resolved to its full path), then target/release/ahdapa, then target/debug/ahdapa, and falls back to cargo build if none is found.
  • npm — for building the WebUI dist/ 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:

  1. 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.html
    

    Use the host name localhost, not 127.0.0.1: the page registers for RP ID localhost, and WebAuthn only accepts an RP ID that matches the page’s host, so a page opened on 127.0.0.1 cannot use it.

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

  3. 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
    
  4. 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

FileDescription
ahdapa.tomlServer config with passkey_rp_id = "localhost" and GSSAPI disabled
users.toml.inUsers template (alice, bob, carol); passwords substituted at runtime
users.passkeys.toml.exampleTemplate for the passkey overlay; copy and fill in credential
users.passkeys.tomlYour 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.shBuild 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