Library / SDK
Yubico/java-webauthn-server avatar
Yubico/java-webauthn-server

java-webauthn-server: the relying party half of WebAuthn in Java

Server-side Web Authentication library for Java https://www.w3.org/TR/webauthn/#rp-operations

567 stars168 forksScalaNOASSERTION

At a glance

What is it?
WebAuthn is usually described from the browser's side, where a call to the credentials API produces a signed assertion. The other half is the server, and that half is what this library implements: the ceremony orchestration, the validation, the attestation checks, and a stated boundary about everything it deliberately leaves to you. The readme's first warning is about a vulnerability in the Java runtime itself, which is the right place for a library to put it.
Who is it for?
java-webauthn-server fits a Java service that wants passkeys or security keys without implementing the ceremony choreography, and fits it particularly well if the service is already careful about cryptographic agility, because the library is explicit about the algorithms the runtime can support. It does not fit somebody who expects a login framework, since the readme says plainly that it has no concept of accounts, sessions or permissions.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Scala, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What it does, and what it refuses to do

This is a server-side Web Authentication library for Java, and it provides implementations of the relying party operations that a server needs in order to support Web Authentication, including passkey authentication.

The relying party operations are the specification's own term, and that matters, because it sets the boundary of the library precisely. The specification describes what a server has to do to register a credential and to assert one, and this library implements that and stops there.

The features list says what that means in practice. It generates request objects suitable as parameters to the browser's credential creation and retrieval calls. It performs all the necessary validation logic on the response from the client. It has no mutable state or side effects, so everything except the builders is thread safe. It can optionally integrate with an attestation trust source to verify authenticator attestations. And the release signatures match fresh builds from source, which the readme treats as a feature rather than as an afterthought.

And then there is the section of non-features, which is the most useful paragraph in the document. The library has no concept of accounts, sessions, permissions or identity federation, and it is not an authentication framework. It only deals with executing the WebAuthn mechanism. Sessions, account management and other higher level concepts can make use of it, but the mechanism alone does not make a security system.

That paragraph is doing real work for a reader. A library in this space that is small and specific is a much better thing to depend on than one that grows a session layer, an account store and a permission model, because the specific library can be audited, upgraded and replaced without rewriting your login system. The trade is that somebody has to build the rest, and the readme is honest that the rest is yours.

The repository is worth noting for a different reason: the implementation is written in Scala and the published artefact is a Java library, with configuration files for the Scala formatter and the Scala linter alongside the Java ones. That is a deliberate choice by the vendor, and the practical effect is that the library's public surface is Java, which is what a Java service wants, while the code behind it is written in a language the maintainers prefer.

The advisory that comes first

The first thing in the readme, before the introduction, is a warning, and it is about the Java runtime rather than about the library.

In April 2022 a flaw was disclosed in Oracle's OpenJDK, and in other virtual machines derived from it, that can impact applications using this library. The impact is that for the most common type of WebAuthn credential, invalid signatures are accepted as valid, allowing authentication bypass for users with such a credential.

Read that again, because the severity is easy to skim past. This is not a weakness in the WebAuthn implementation. It is a weakness in the platform's signature verification, and the consequence is that a signature that should be rejected is accepted. For a library whose entire job is to verify signatures, that is the worst possible place for the platform to have a bug, and no amount of care in the library's own validation can compensate.

The readme's instructions are correspondingly direct: read the vendor's own advisory, and make sure you are not using one of the affected runtime versions. If you are, it urges you to upgrade your Java deployment to a version that is safe.

Three things about this are worth noting as a matter of craft rather than of content.

The first is placement. The warning is the first section of the readme, above the table of contents, before the description of what the library does. A reader who skims gets the warning anyway.

The second is that it names the specific consequence rather than pointing at a vulnerability identifier and moving on. Invalid signatures being accepted as valid, and authentication bypass for users with such a credential, is a sentence an engineer can act on. A bug number requires a lookup.

The third is that the library is still maintained, and the recent release history shows a current version with a release candidate in the preceding weeks. The warning is four years old in the readme, and the project has moved on. That combination is the honest shape of a long-lived dependency: an old platform flaw that is now fixed, still recorded, still first.

The shape of the API, and the threading claim

The library is used through a single main class, and the getting started section makes the shape of it clear.

You implement an interface with your database access logic, which is where your application's knowledge of its own credential records lives. Then you instantiate a relying party object with that implementation. And then you call four methods on it: two for registration ceremonies and two for authentication ceremonies, in start and finish pairs.

The start and finish pattern is the ceremony model from the specification, and the reason it is two calls rather than one is worth understanding. A registration starts with the server generating a challenge and a set of acceptable parameters, and it finishes when the client returns what it signed. Between the two calls, the client does something that can take a long time and can fail: the person has to touch a security key, or the platform authenticator has to do a user verification ceremony. The server has to keep the state of the first call somewhere while it waits, and the interface gives you exactly one place to put it, which is the interface you implement.

So the interface is not a convenience abstraction over a data store. It is the seam where the ceremony's state lives, and that is why the library has no concept of accounts: the account is a record you keep in your own database, keyed however you like, and the library's only requirement is that you can look up a credential by its identifier and store the result of a registration.

The threading claim deserves a second look, because it is stated as a feature and it is unusual for a library of this kind. No mutable state or side effects, and everything except the builders is thread safe.

That is a strong property for a security library, and it is the kind of thing that is expensive to retrofit. A validator that caches a decoded key in a field, or that keeps a counter of attempts, is a validator that cannot be shared between requests without synchronisation, and a synchronised field in a validation path is a contention point on every login. Declaring the whole library stateless except for the object-building step means an application can hold one instance and use it from every thread, and it means the library cannot accumulate state that changes its behaviour over time, which for a validator is a property you would want enforced rather than documented.

The builders are excluded, and that is the correct exception. A builder accumulates configuration before you use it, and it is used once. The exclusion is precise rather than a blanket caveat.

Getting started, on both sides

Using the library comes in two parts, and the readme splits them explicitly, which is the right way to present a protocol with a browser half and a server half.

The server side is five steps. Implement the credential repository interface with your database access logic. Instantiate the relying party class. Use the registration start and finish methods to perform registration ceremonies. Use the assertion start and finish methods to perform authentication ceremonies. And then optionally use the additional features.

Those optional features are named, and they are the ones that matter for a modern deployment: passkeys, passwordless multi-factor authentication, and credential backup state.

The credential backup state feature is the one that deserves a sentence, because it is a capability that did not exist in the first version of the protocol and has changed how the credential ecosystem works. A credential that can be backed up and synced between devices is a credential whose handle is not a fixed piece of hardware, and a relying party that has not thought about that will make the wrong assumptions about what an identifier means. So the library exposing it explicitly, rather than leaving it as an emergent property of the stored record, is a sign of how closely it tracks the current specification.

The client side is short, and it is worth reading because the boundary is clean. You call the browser's credential creation or retrieval call, passing as its argument the result of the corresponding start call on the server. Then you encode the result of the successfully resolved promise and return it to the server. And for that you need some way of encoding the byte array values, which the readme notes and the passage is cut off before it finishes.

That last point is a real one, and it is the kind of thing that trips people up. The browser's result contains binary data, and it has to be carried to the server as something the server can decode, which in practice means base64 or a text encoding of the bytes, and the client side of your application has to do that. The library is server-side, so it cannot do it for you, and the readme is careful to say so.

So the whole flow is: the browser asks your server for a challenge, your server produces a request object, the browser passes it to the platform, the platform does a ceremony, the browser returns a signed assertion, your client encodes it, your server validates it. The library occupies the two ends of that loop and the validation in the middle, and it leaves the transport, the encoding and the account model to you.

The public API contract, and the module boundaries

The versioning section is unusually precise, and it is the part of the readme a maintainer of a dependent service should read carefully.

The library uses semantic versioning. The public API consists of all public classes, methods and fields in the main package and its subpackages, which is to say everything covered by the published documentation, with one exception. Features annotated as deprecated and carrying an experimental tag in the documentation are considered unstable and may receive breaking changes without a major version increase.

That exception is a well known pattern and a good one, provided the tagging is consistent. It gives the maintainers room to iterate on a new part of the API without committing to it, and it gives users a clear signal about which parts of the library they should not build on yet. The cost is that a careless user can depend on an experimental feature and be broken by a minor release, which is why the readme specifies both the annotation and the tag rather than only one.

Then the two exclusions, and the second one is a genuine hazard for a consumer. Package-private classes and methods are not part of the public API. And a separate utility module is explicitly not part of the public API, with breaking changes to it not reflected in the version numbers.

That second exclusion is the kind of thing that catches a project out. If you write code against a class in the utility module, the library's version number tells you nothing about whether it still compiles, because the version is not tracking that module's compatibility. The readme says so in one sentence, and the sentence exists because it has presumably been needed.

The additional modules section is short. There is an attestation module, which integrates with the FIDO Alliance's metadata service for retrieving and selecting trust roots to use when verifying attestation statements.

That module is the interesting one, because attestation is where the trust model gets complicated. Without it, a relying party verifies that a credential signed a challenge, which is the core of WebAuthn. With it, the relying party can additionally check the provenance of the authenticator, using a signed statement from the manufacturer and a metadata document that says which keys to trust. That is a different requirement from the rest of the library, it depends on an external service, and it is a separate module for that reason.

The repository layout matches: the core module, the attestation module, a demo module, and the two utility modules, one of them the Scala one that the rest of the build depends on.

The build, the tests, and reproducible releases

The build is Gradle, with the wrapper checked in so that everybody builds with the same version, and a build source directory for the custom logic. The configuration files show the mixed nature of the codebase: a Lombok configuration for the Java sources, and separate configuration files for the Scala formatter and the Scala linter.

The test layout is the part worth looking at, because the directory names tell you what the project considers worth testing separately. There is a directory for dependent projects and a directory for a test platform, and the fact that they are separate suggests that the test suite is run against more than one target environment, which for a library that has to work across runtimes and providers is the only way to know.

And the badge row is unusual in a good way. There is the ordinary build status. There is a mutation test coverage badge, which is a stronger claim than ordinary coverage: mutation testing deliberately breaks your code and checks that your tests notice, so a suite that passes the mutation check is a suite that actually asserts something rather than merely executing the lines. For a library whose job is rejecting bad input, that is exactly the right property to measure, and publishing it as a badge is a signal about the project's priorities.

The third badge is the one I would point at first. There is a workflow that verifies binary reproducibility, and the feature list says what it means: release signatures match fresh builds from source.

That is a strong claim with a specific meaning. It means that if you take the published source, build it yourself with the same toolchain, you get an artefact that matches the released one, byte for byte, and therefore the signature verifies. The practical consequences are two. A user who distrusts the vendor can check the release without trusting the vendor's build, and an auditor can check the release without trusting the vendor's process. And a user who is worried that the published artefact is not the published source has a way to find out.

The rest of the repository is the standard set for a project with a security policy, a licence file, a news file, the documentation in two formats, a gitmodules file for the submodules, and a build properties file. A library that verifies its own binary reproducibility is one that assumes people will check.

Editorial conclusion

java-webauthn-server fits a Java service that wants passkeys or security keys without implementing the ceremony choreography, and fits it particularly well if the service is already careful about cryptographic agility, because the library is explicit about the algorithms the runtime can support. It does not fit somebody who expects a login framework, since the readme says plainly that it has no concept of accounts, sessions or permissions. Verify first that your Java runtime is not one of the versions affected by the signature validation flaw the readme warns about, that the algorithms you want to accept have providers available in the runtime you deploy, that you read the attestation module separately if you use attestation, and that you treat the versioned public API as the contract and everything else as yours to pin by hand.

Frequently asked questions

What does the java-webauthn-server library do?

It implements the relying party operations a server needs to support Web Authentication, including passkey authentication. It generates the request objects you pass to the browser's credential calls, performs all the validation logic on the response, can optionally verify authenticator attestations against a trust source, and has no mutable state so it is safe to share between threads.

What does the java-webauthn-server library not do?

It has no concept of accounts, sessions, permissions or identity federation, and it is not an authentication framework. The readme says the mechanism alone does not make a security system, so you have to build the account and session layer on top of it yourself.

What is the psychic signatures warning in the java-webauthn-server readme?

It refers to a flaw disclosed in 2022 in the Java runtime that could cause invalid signatures to be accepted as valid for the most common type of WebAuthn credential, allowing authentication bypass. It is a platform flaw rather than a library flaw, so the fix is to upgrade the Java deployment to a version that is not affected, and the readme points at the vendor advisory.

How do I use the java-webauthn-server library?

Implement the credential repository interface with your own database access logic, instantiate the relying party class with it, then call the start and finish methods for registration and for assertion ceremonies. On the client side, pass the server's request object to the browser's credential call and encode the result of the resolved promise before returning it to the server.

What is not covered by the java-webauthn-server version guarantees?

Package-private classes and methods are not part of the public API, and a separate utility module is explicitly excluded, with breaking changes to it not reflected in the version numbers. Features marked as deprecated and tagged as experimental in the documentation may also change without a major version increase.

Official sources

  1. Issues
  2. README
  3. Releases
  4. Yubico/java-webauthn-server on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/yubico-java-webauthn-server.svg)](https://hysenlabs.com/projects/yubico-java-webauthn-server)

Was this useful?