# libwebauthn is the Rust FIDO2 engine behind the Linux credentials portal

> libwebauthn is a Linux-native implementation of the FIDO2 and FIDO U2F platform APIs written entirely in Rust, covering USB HID, Bluetooth LE, NFC and the hybrid caBLE transports, and it now sits inside the Credentials for Linux project. It is a library rather than a daemon, and it depends on a system Public Suffix List to check relying party IDs.

**linux-credentials/libwebauthn** — FIDO2 (WebAuthn) and FIDO U2F platform library for Linux written in Rust

- Repository: https://github.com/linux-credentials/libwebauthn
- Stars: 586 · Forks: 30
- Language: Rust
- License: LGPL-2.1
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/linux-credentials-libwebauthn

## A platform library, not a credential daemon

libwebauthn is described as a Linux-native implementation of the FIDO2 and FIDO U2F Platform API, fully written in Rust. The word platform matters more than the version numbers: this is the layer that talks to an authenticator and produces the ceremony outputs a relying party verifies, not a service that holds user credentials or serves a web interface.

The project was previously known as xdg-credentials-portal, and it is now part of the Credentials for Linux project, which aims to offer FIDO2 platform functionality, meaning FIDO U2F and WebAuthn, on Linux over a D-Bus Portal interface. The D-Bus API proposal itself lives in a separate repository, credentialsd, which the README points to for anyone looking for that side of the work.

So the layering is deliberate. libwebauthn is the protocol and device layer, credentialsd is where the D-Bus surface would be specified, and the portal project ties them to the desktop. A consumer of this library is normally a portal implementation, a browser's WebAuthn backend, or a tool that needs to drive an authenticator directly.

The language is Rust, the licence is LGPL-2.1, the default branch is master, and the repository is not archived. The last push was on 2026-10-17, and the recent releases are libwebauthn v0.10.0 from 2026-07-14, v0.9.0 from 2026-07-01 and v0.8.0 from 2026-06-15, three releases inside a single month.

The repository layout is small and says something about the project shape. There is a Cargo.toml workspace with two members, libwebauthn and libwebauthn-tests, plus a Cargo.lock, a COPYING file, a SECURITY.md, a .gitmodules file and the usual GitHub configuration directories.

## What the feature matrix actually covers, and what is still planned

The feature list is organised by protocol generation, and the distinction between FIDO U2F and FIDO2 is not cosmetic. FIDO U2F covers registration through U2F_REGISTER, authentication through U2F_AUTHENTICATE and a version call, U2F_VERSION. FIDO2 adds credential creation, assertion verification, biometric user verification and discoverable credentials, plus related origins as specified in WebAuthn L3 section 5.11.

Between the two generations sits a downgrade path, and this is the part of the list that deserves attention. FIDO2 to FIDO U2F downgrade has basic functionality plus support for excludeList and pre-flight requests. A pre-flight request is the mechanism a relying party uses to ask an authenticator whether it already holds a credential for a given RP, so that a registration attempt can fail early instead of silently creating a second credential. Supporting it during a downgrade means an authenticator that cannot speak CTAP can still participate in an exclude-list check.

The PIN/UV layer is implemented in two parts. Both PIN/UV Auth Protocol One and Protocol Two are supported, and the operations cover GetPinToken, GetPinUvAuthTokenUsingPinWithPermissions and GetPinUvAuthTokenUsingUvWithPermissions, plus a persistent pinUvAuthToken for read-only credential management, marked as pcmr and CTAP 2.2 or later.

Passkey authentication is where the list runs longest, with discoverable credentials, the hybrid transport under caBLE v2 for QR-initiated transactions, state-assisted hybrid transactions that remember a phone, and a CTAP 2.3 hybrid variant using a direct BLE L2CAP data channel, QR-initiated and with no tunnel server.

One row is not filled in: TPM 2.0 platform authenticators are listed as planned for both U2F and WebAuthn, and the README links that to issue number 4. That is the single largest functional gap, and it means a host TPM cannot currently act as the authenticator through this library.

## Transports as a pluggable interface, with NFC deliberately left out of the default build

The library supports multiple transports through a pluggable interface, which the README describes as making it easy to add additional backends. The supported set covers USB HID via hidapi for both U2F and WebAuthn, Bluetooth Low Energy via bluez for both, and NFC via either pcsc or libnfc for both.

Two hybrid transports exist for WebAuthn only, and neither applies to U2F, which is marked N/A in the table. The first is hybrid caBLE v2, the cloud-assisted model where the phone relays a challenge through a tunnel server. The second is the CTAP 2.3 hybrid variant, which uses a direct BLE L2CAP data channel and is QR-initiated with no tunnel server.

The NFC story has a detail that will bite anyone who builds from the README's table without reading further. USB, BLE and the two hybrid transports build with the default features, but NFC is opt-in: the crate ships with default = [] for the NFC stack. To compile it in you enable the nfc-backend-pcsc feature, described as pure userspace and recommended, or nfc-backend-libnfc, which requires the libnfc system library. The footnote in the table says both can be enabled together and the first FIDO device found by either backend is used.

That default-empty configuration is unusual enough to be worth restating plainly. A consumer who adds libwebauthn and expects USB to work will get it, and a consumer who expects NFC to work will not, and the failure is a missing example binary rather than an error at build time.

The backend choice between pcsc and libnfc has a practical dimension: pcsc talks to the PC/SC stack that most desktop Linux systems already run, while libnfc talks to the reader directly through the libnfc library, which needs to be installed as a system dependency.

## Running the example programs, and where the public suffix list enters

The examples live in libwebauthn/examples and are grouped by purpose: ceremony for register and authenticate flows, features for per-feature demos covering extensions, preflight, PRF and device selection, and management for CTAP2 admin operations. Shared helpers come from examples/common. The basic ceremony examples cover all transports, and the WebAuthn examples consume and emit JSON per the WebAuthn IDL.

A HID ceremony run looks like this:

```bash
cargo run --example u2f_hid
cargo run --example webauthn_hid
```

Bluetooth and NFC variants exist under their own names, and NFC needs the feature flag because it is not in the default build:

```bash
cargo run --features nfc-backend-pcsc --example u2f_nfc
cargo run --features nfc-backend-pcsc --example webauthn_nfc
```

The hybrid transports have their own examples, one for the CTAP 2.3 direct channel and one for the caBLE tunnel server:

```bash
cargo run --example webauthn_cable
cargo run --example webauthn_cable_wss
```

Note what the example names imply about scope. There is no example named for a daemon or a service, because the project is a library, and the examples exist to prove the ceremonies work against real hardware.

There is also a runtime dependency that is easy to miss because it is not a Rust crate. Validating the relying party ID against the calling origin requires the Public Suffix List, and the built-in SystemPublicSuffixList::auto() loader reads it from the standard system path, probing the binary .dafsa format first and falling back to the text .dat format. The packaging story differs per distribution. The publicsuffix package on Debian and Ubuntu ships both formats. On Fedora the binary .dafsa file comes from publicsuffix-list-dafsa, a transitive dependency of libpsl and so usually already installed, while the text .dat file needs the optional publicsuffix-list package. Arch packages only the text .dat format. Callers who wire their own list do not need a system package at all.

That last sentence is the escape hatch, and it is worth knowing before you start debugging a missing-file error on a distribution that ships the format your build does not expect.

## The Trussed patch in the workspace manifest, and what it says about the dependency graph

The workspace Cargo.toml is short, and one of its two stanzas is a maintenance note rather than configuration:

```toml
[workspace]
resolver = "2"
members = ["libwebauthn", "libwebauthn-tests"]

# The trussed ecosystem is currently a bit messy, so we have to do some patching of the versions
[patch.crates-io]
trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "024e0eca5fb7dbd2457831f7c7bffe4341e08775" }
trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging.git", rev = "7922d67e9637a87e5625aaff9e5111f0d4ec0346" }
```

The comment is the important part. The crates.io versions of trussed and trussed-staging are pinned to specific git revisions because the upstream ecosystem is described as currently a bit messy. In practice this means a build of this repository depends on two git repositories at fixed revisions in addition to crates.io, which affects offline builds, reproducible builds and the size of a vendor directory. It also means the dependency graph is not entirely resolvable from crates.io alone, so an air-gapped build has more to mirror than it would for a normal crate.

Trussed is the Trussed hardware abstraction layer used by the FIDO2 reference stack, and pulling it in through a patch rather than a normal version constraint is a signal about how much of the CTAP implementation comes from that stack rather than from this repository's own code. The presence of libwebauthn-tests as a separate workspace member confirms the same split: the tests are a crate of their own rather than a directory of unit tests inside the library.

The .gitmodules file in the repository root suggests at least one submodule, which is consistent with a project that pulls in components from more than one upstream.

## Where libwebauthn is the wrong piece to reach for

Three limits, and the first is the one that decides whether the library applies at all.

The first is that this is a library, not a user-facing service. There is no daemon to install, no policy configuration, no account management and no UI. If the requirement is for end users to store and use passkeys on a desktop, what you need is the D-Bus portal interface that the Credentials for Linux project is building, with credentialsd holding the API proposal, and libwebauthn is a component underneath that rather than a substitute for it.

The second is the TPM 2.0 gap. Platform authenticators backed by a host TPM are listed as planned, so a deployment that wants the machine's own TPM to act as the FIDO2 authenticator cannot get that from this library today. The supported set is external devices over USB, Bluetooth, NFC and hybrid. A product that promised a laptop with no external key would be promising something the code does not do.

The third is the system-level coupling that the Public Suffix List introduces. Because the built-in loader reads a system file, the behaviour of relying party ID validation depends on the distribution's packaging, and the two formats are not interchangeable: a build that probes .dafsa first will fall back to .dat on a system that only ships .dat, and will fail outright on a system that ships neither. The stated escape is to wire your own list, which means implementing the loader's interface yourself and owning the update cadence of that list, since an out-of-date Public Suffix List produces an incorrect accept or reject decision rather than a crash.

The alternative approach is to build on a CTAP implementation that already exists in another language and expose it behind the same interface. The difference in approach is where the authenticator logic lives: libwebauthn implements the ceremonies and the transports in Rust and pulls in the Trussed stack for the hardware abstraction, so a team already committed to Rust gets a single-language stack, while a team that already has a working CTAP stack has no reason to port it and would be spending effort to reach the same ceremonies.

## Conclusion

Adopt libwebauthn if you are writing a Linux-side authenticator or a credentials portal component, because it implements the CTAP and WebAuthn ceremonies in Rust across four transports and the two PIN/UV auth protocols rather than wrapping a C library. Do not adopt it as a standalone credential store for end users, because it is a library with no service to run, and note that TPM 2.0 platform authenticators are still marked planned. Verify first that the system Public Suffix List is present in a format the loader can read, since the Debian and Fedora packages differ and Arch ships only the text .dat, that you enable the NFC backend feature you actually need because the crate ships with default = [], and that the release you pin, currently v0.10.0, matches the CTAP version your authenticator firmware expects.

## FAQ

### What is libwebauthn and what does it implement?

It is a Linux-native implementation of the FIDO2 and FIDO U2F Platform API written entirely in Rust. It covers FIDO U2F registration, authentication and version calls, and FIDO2 credential creation, assertion verification, biometric user verification, discoverable credentials, related origins and both PIN/UV auth protocols.

### How do I build libwebauthn with NFC support?

NFC is not in the default build because the crate ships with default = [] for the NFC stack. Enable the nfc-backend-pcsc feature, which is pure userspace and recommended, or nfc-backend-libnfc, which requires the libnfc system library, then run the nfc examples with the same feature flag.

### What does libwebauthn need from the host system at runtime?

Validating the relying party ID against the calling origin requires the Public Suffix List. The built-in SystemPublicSuffixList::auto() loader reads it from the standard system path, probing the binary .dafsa format first and falling back to the text .dat format, and callers that wire their own list do not need a system package.

### Does libwebauthn support TPM 2.0 platform authenticators?

No. TPM 2.0 is listed as planned for both FIDO U2F and WebAuthn in the transports table, with the README linking that row to issue number 4. The supported transports are USB HID, Bluetooth Low Energy, NFC and the two hybrid variants.

### How is libwebauthn related to the Credentials for Linux project?

The repository was previously known as xdg-credentials-portal and is now part of the Credentials for Linux project, which aims to offer FIDO2 platform functionality on Linux over a D-Bus Portal interface. The D-Bus API proposal itself is in a separate repository, credentialsd.

### Can I run the libwebauthn examples against a real security key?

Yes. The examples are grouped into ceremony for register and authenticate flows, features for per-feature demos and management for CTAP2 admin operations, and the basic ceremony examples cover all transports, with HID, Bluetooth, NFC and hybrid variants each having their own example binary.

## Sources

- [Issues](https://github.com/linux-credentials/libwebauthn/issues)
- [License: LGPL-2.1](https://github.com/linux-credentials/libwebauthn/blob/master/LICENSE)
- [linux-credentials/libwebauthn on GitHub](https://github.com/linux-credentials/libwebauthn)
- [README](https://github.com/linux-credentials/libwebauthn/blob/master/README.md)
- [Releases](https://github.com/linux-credentials/libwebauthn/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/linux-credentials-libwebauthn
