This post helps you find the right WebAuthn server library to offer passkeys. 9 libraries are compared and a strategy to find the right library is given.
Nicolai
Created: December 15, 2023
Updated: October 1, 2024
Our mission is to make the Internet a safer place, and the new login standard passkeys provides a superior solution to achieve that. That's why we want to help you understand passkeys and its characteristics better.
1. Introduction: WebAuthn Server Implementation
2. How Can I Implement Passkeys On My Own?
3. WebAuthn Server Backend Libraries: A Comparison
3.1 TypeScript: SimpleWebAuthn
3.6 Java: java-webauthn-server
4. Recommendations: Navigating the WebAuthn Server Library Landscape
4.1 Step 1: Choose Your WebAuthn Library
4.2 Step 2: Define Your WebAuthn Server Options
4.3 Step 3: Define the Database Structure
4.4 Step 4: Test On Your Users' Devices
Providing secure and simple user authentication is a must-have for digital companies in 2024. Passkeys, as the new login standard, are the ideal solution to meet these needs. However, the enhanced user experience and security of passkeys for the user comes at a price when implementing them as a developer. The implementation difficulty stems from the fact that passkeys are relatively new - to users, but also to developers and that their implementation can be quite challenging compared to password- based authentication. In fact, you need at least four API endpoints for passkey authentication compared to one API endpoint for password authentication.
One of core components on server side for providing passkey authentication is the WebAuthn server (green library part).
Source: Yubico
In this blog post, we compare several WebAuthn server libraries / packages / SDKs, analyze differences and provide a recommendation for developers who are new to passkey implementation.
Recent Articles
⚙️
The 30 Best Passkey Tutorials for 14 Languages / Frameworks
⚙️
The 34 Best Passkey SDKs and Libraries for Your Framework
📖
WebAuthn User ID, User Handle, User Name and Credential ID
♟️
CAPTCHA vs. Passkeys: Everyone hates CAPTCHAs - are passkeys the solution?
♟️
How Invisible MFA with Passkeys Solves the MFA Problem
To better understand, why a WebAuthn server library is needed in the first place, lets have a look at how passkeys can be implemented. In principle, there are two ways to integrate passkeys into websites and apps:
While a third-party passkey solution is easy to integrate and usually saves a lot of engineering time (especially for edge cases, maintenance, recovery, fallbacks and improved passkey UX), some developers just favor to implement everything themselves.
Lets have a look how the do-it-yourself passkey implementation works. In a very basic setup, a mechanism to register (sign-up) and authenticate (login) is needed. Both processes, also called WebAuthn ceremonies, are handled differently, even though the overall flow follows a similar schema:
Since every sign-up / login process involves these steps, the backend needs to keep track of users, passkeys and sign-up / login requests.
Ben Gould
Head of Engineering
I’ve built hundreds of integrations in my time, including quite a few with identity providers and I’ve never been so impressed with a developer experience as I have been with Corbado.
10,000+ devs trust Corbado & make the Internet safer with passkeys. Got questions? We’ve written 150+ blog posts on passkeys.
Join Passkeys CommunityIf you want to get more in-depth knowledge about the way passkeys work and how a simple implementation looks like (without using a third-party passkey solution), you can investigate our blog article here.
In real-life scenarios, when implementing passkeys yourself, keep in mind that its not just about providing the necessary API endpoints and basic implementation to sign up and login. On top of that, you need to address the following topics and use cases:
Though, for the basic passkey implementation, you only need to adhere to the WebAuthn standard. Implementing a well-known and supported WebAuthn server library is usually enough. The library generates the WebAuthn server parameters and verifies login challenges, essentially taking over the cryptographic and most complex part for you.
All of the analyzed WebAuthn server libraries provide the necessary functionalities to offer passkey authentication. Therefore, we paid special attention to the following criteria:
The following WebAuthn server libraries were analyzed (ordered by descending number of GitHub stars in Decemer 2023):
type UserModel = { id: string; username: string; currentChallenge?: string; }; /** * It is strongly advised that authenticators get their own DB * table, ideally with a foreign key to a specific UserModel. * * "SQL" tags below are suggestions for column data types and * how best to store data received during registration for use * in subsequent authentications. */ type Authenticator = { // SQL: Encode to base64url then store as `TEXT`. Index this column credentialID: Uint8Array; // SQL: Store raw bytes as `BYTEA`/`BLOB`/etc... credentialPublicKey: Uint8Array; // SQL: Consider `BIGINT` since some authenticators return atomic timestamps as counters counter: number; // SQL: `VARCHAR(32)` or similar, longest possible value is currently 12 characters // Ex: 'singleDevice' | 'multiDevice' credentialDeviceType: CredentialDeviceType; // SQL: `BOOL` or whatever similar type is supported credentialBackedUp: boolean; // SQL: `VARCHAR(255)` and store string array as a CSV string // Ex: ['usb' | 'ble' | 'nfc' | 'internal'] transports?: AuthenticatorTransport[]; };
public class StoredCredential { /// <summary> /// The Credential ID of the public key credential source. /// </summary> public byte[] Id { get; set; } /// <summary> /// The credential public key of the public key credential source. /// </summary> public byte[] PublicKey { get; set; } /// <summary> /// The latest value of the signature counter in the authenticator data from any ceremony using the public key credential source. /// </summary> public uint SignCount { get; set; } /// <summary> /// The value returned from getTransports() when the public key credential source was registered. /// </summary> public AuthenticatorTransport[] Transports { get; set; } /// <summary> /// The value of the BE flag when the public key credential source was created. /// </summary> public bool IsBackupEligible { get; set; } /// <summary> /// The latest value of the BS flag in the authenticator data from any ceremony using the public key credential source. /// </summary> public bool IsBackedUp { get; set; } /// <summary> /// The value of the attestationObject attribute when the public key credential source was registered. /// Storing this enables the Relying Party to reference the credent’al's attestation statement at a later time. /// </summary> public byte[] AttestationObject { get; set; } /// <summary> /// The value of the clientDataJSON attribute when the public key credential source was registered. /// Storing this in combination with the above attestationObject item enables the Relying Party to re-verify the attestation signature at a later time. /// </summary> public byte[] AttestationClientDataJson { get; set; } public List<byte[]> DevicePublicKeys { get; set; } public byte[] UserId { get; set; } public PublicKeyCredentialDescriptor Descriptor { get; set; } public byte[] UserHandle { get; set; } public string AttestationFormat { get; set; } public DateTimeOffset RegDate { get; set; } public Guid AaGuid { get; set; } }
<?php declare(strict_types=1); namespace App\Entity; use App\Repository\PublicKeyCredentialSourceRepository; use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Uid\AbstractUid; use Symfony\Component\Uid\Uuid; use Webauthn\PublicKeyCredentialSource as BasePublicKeyCredentialSource; use Webauthn\TrustPath\TrustPath; #[ORM\Table(name: 'pk_credential_sources')] #[ORM\Entity(repositoryClass: PublicKeyCredentialSourceRepository::class)] class PublicKeyCredentialSource extends BasePublicKeyCredentialSource { #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] public readonly DateTimeImmutable $createdAt; #[ORM\Id] #[ORM\Column(type: Types::STRING, length: 255)] #[ORM\GeneratedValue(strategy: 'NONE')] private string $id; public function __construct( string $publicKeyCredentialId, string $type, array $transports, string $attestationType, TrustPath $trustPath, AbstractUid $aaguid, string $credentialPublicKey, string $userHandle, int $counter ) { $this->id = Uuid::v4()->toRfc4122(); $this->createdAt = new DateTimeImmutable(); parent::__construct($publicKeyCredentialId, $type, $transports, $attestationType, $trustPath, $aaguid, $credentialPublicKey, $userHandle, $counter); } public function getId(): string { return $this->id; } }
The following table provides an overview of the WebAuthn server libraries:
Since most of the libraries are equally powerful and implement the WebAuthn standard, we recommend the following decision-tree:
If you just want to learn more about WebAuthn servers in general without already having a specific project we can make some recommendations since there are some differences between the libraries and their supplementary material like docs and example implementations. So, for software developers eager to kickstart their passkey implementation journey, we advise to choose the following implementations:
For an even deeper understanding of how WebAuthn works on the server side, you can read the very detailed section WebAuthn Relying Party Operations in the WebAuthn RFC which details every step that needs to be implemented for registering a new credential (7.1) and verifying an authentication assertion assertion (7.2).
Evaluate the specific passkey and WebAuthn requirements you have. In this blog post, we assumed you only want to support passkeys as discoverable credentials. Read about the PublicKeyCredentialCreationOptions and PublicKeyCredentialRequestOptions together with the client-side navigator.credentials.create() and navigator.credentials.get() WebAuthn API calls to set the parameters in the WebAuthn server SDK config correctly for your use case.
For all the WebAuthn server libraries, you will need to provide the appropriate database structure to persist / access the following information:
For some libraries, there are specific recommendations and examples (if we found them useful, we have provided them above). Its essential to fully understand which WebAuthn fields need to be stored where. Pay special attention to identify which value you want to use for the User ID (user.id). We have a more detailed explanation here. Also take into consideration what happens when a user might delete a passkey. Besides that, you can optionally constrain the usage of certain authenticators. A list of valid authenticators related to passkeys can be found here. In case you also want to support and check the attestations of security keys, this is a whole different story. You find more information here.
Identify on which devices your users will use passkeys and fallback authentication methods. In case you are not sure which devices, browsers and operating systems your users use, we provide you with free passkey analyzer tool, that can be added to any website or web app in a matter of minutes (you dont need to use Corbado as auth solution to use the passkey analyzer). If you have specific questions on the passkey adoption and passkey-readiness share of certain devices, feel free to reach out to us. We are happy to provide you with further insights and help you on this topic (see also our latest blog post regarding passkey- readiness). Moreover, you should keep in mind that for Windows 10 and Linux you will need to come up with dedicated solutions as these operating system provide the least (if at all) passkey support.
For almost every language or framework out there exists a well-established WebAuthn server library by now. Comparing libraries of different languages shows no clear superiority of certain implementations. Rather you should use the framework / programming language you are most familiar with. Alternatively, if you dont want to implement WebAuthn yourself and take care of all the stuff that comes along, you can try a dedicated, pre-built passkey-authentication solution like Corbado. Posing a passkey centered all- in-one authentication solution, it comes with great passkey intelligence, session management as well as fallback authentication methods, so you can focus on developing your product and let go of authentication. You can try it out for free with unlimited users here.
Table of Contents
Enjoyed this read?
🤝 Join our Passkeys Community
Share passkeys implementation tips and get support to free the world from passwords.
🚀 Subscribe to Substack
Get the latest news, strategies, and insights about passkeys sent straight to your inbox.
We provide UI components, SDKs and guides to help you add passkeys to your app in <1 hour
Start for free
Recent Articles
WebAuthn User ID, User Handle, User Name and Credential ID
Vincent - December 14, 2023
WebAuthn Conditional UI (Passkeys Autofill) Technical Explanation
Vincent - October 20, 2023