WebAuthn.Net

repository·main·Indexed 42 days ago

https://github.com/dodobrands/webauthn.net

A production-ready implementation of the WebAuthn Level 3 standard for .NET 8, .NET 9, and .NET 10. It provides server-side logic for passkeys, biometric authentication, and hardware security keys, featuring Attestation and Assertion APIs. Includes storage providers for Microsoft SQL Server, PostgreSQL, MySQL, and InMemory, as well as OpenTelemetry integration for metrics.

Tokens
7.9K
Snippets
14
Records
26
Agent score
55%

What's inside WebAuthn.Net

  1. Overview of WebAuthn.Net

    main

    WebAuthn.Net is a production-ready, extensible implementation of the WebAuthn Level 3 standard for .NET 8, .NET 9, and .NET 10 applications. It is designed to handle the server-side logic for WebAuthn, passing the FIDO conformance tests.

    Key capabilities include:

    • Passkey support out of the box.
    • Attestation API: Registering and verifying credentials/authenticators.
    • Assertion API: Authenticating users.
    • Authenticator Support: Works with embedded authenticators (FaceID, TouchID, Windows Hello) and roaming authenticators (USB/NFC/BLE keys like Yubico).
    • Cryptographic Support: Implements all algorithms required for FIDO conformance, including RS, PS, ES, and EdDSA (via libsodium).
    • Extensibility: Designed to allow overriding almost any library component.
  2. Understanding the WebAuthn Context and Transactions

    main

    To ensure all operations within a ceremony (from beginning to completion) are executed within a single database transaction, the library uses a Context object implementing IWebAuthnContext.

    • Lifecycle: The context is created via IWebAuthnContextFactory.CreateAsync(HttpContext). It manages the database connection and transaction.
    • Usage: The context is passed to all database-interacting methods.
    • Commitment: The CommitAsync method must be called at the end of the operation to finalize the transaction.
    • Disposal: IWebAuthnContext inherits from IAsyncDisposable to ensure proper cleanup of resources.
  3. How Registration and Authentication Ceremonies work

    main

    WebAuthn.Net abstracts the WebAuthn specification into two main processes:

    Registration Ceremony

    Goal: Associate a public key with a user account.

    1. Backend generates a challenge and retrieves the userHandle.
    2. Frontend receives these via navigator.credentials.create().
    3. Frontend returns the result to the backend.
    4. Backend validates the result and stores the association between userHandle and credentialId.

    Authentication Ceremony

    Goal: Verify the user using previously registered credentials.

    1. Backend generates a challenge and optionally retrieves existing public keys (for Passkeys/Discoverable Credentials).
    2. Frontend receives these via navigator.credentials.get().
    3. Frontend returns the result to the backend.
    4. Backend validates the result by comparing the credentialId and userHandle against stored data.
  4. Understand FIDO Metadata management

    main

    WebAuthn.Net uses metadata from the FIDO Metadata Service to validate requests and ensure trustworthiness.

    By default, the library uses a background service called FidoMetadataBackgroundIngestHostedService (implementing IHostedService and IDisposable) which downloads, verifies, and periodically updates metadata in the background.

    Important Considerations:

    • Startup Dependency: The default implementation downloads the metadata blob at application startup. If the FIDO Metadata Service is unavailable during startup, your application may fail to launch.
    • Storage: The default implementation uses in-memory storage.
    • Customization: To avoid startup dependencies on the FIDO Metadata Service, you should implement your own storage and update mechanism using persistent storage.
  5. Integrate WebAuthn.Net using Dependency Injection

    main

    WebAuthn.Net provides extension methods for Microsoft.Extensions.DependencyInjection.

    All components are intended to be registered with a Singleton lifetime. The library uses Services.TryAddSingleton for internal services (and Services.AddOptions for options), which allows you to override default implementations easily.

    To override a service: Register your custom implementation with a Singleton lifetime BEFORE calling the WebAuthn.Net extension methods. This prevents the default implementations from being registered.

  6. Configure the MySQL database schema for WebAuthn.Net

    main

    The WebAuthn.Net.Storage.MySql package requires a specific database schema to store credential data. This implementation is intended for MySQL 8.0+; MySQL 5.7 is not supported.

    Because this library is designed to be integrated into existing applications, you must decide how to apply these migrations (e.g., using Entity Framework, FluentMigrator, or manual scripts).

    Critical Requirement: You must ensure a unique index exists on the combination of RpId, UserHandle, and CredentialId, as this combination uniquely identifies a public key.

    CREATE TABLE `CredentialRecords` 
    (
        `Id`                        binary(16)      NOT NULL,
        `RpId`                      varchar(256)    NOT NULL,
        `UserHandle`                varbinary(128)  NOT NULL,
        `CredentialId`              varbinary(1024) NOT NULL,
        `Type`                      int             NOT NULL,
        `Kty`                       int             NOT NULL,
        `Alg`                       int             NOT NULL,
        `Ec2Crv`                    int             NULL,
        `Ec2X`                       varbinary(256)  NULL,
        `Ec2Y`                       varbinary(256)  NULL,
        `RsaModulusN`               varbinary(1024) NULL,
        `RsaExponentE`               varbinary(32)   NULL,
        `OkpCrv`                    int             NULL,
        `OkpX`                       varbinary(32)   NULL,
        `SignCount`                 int unsigned    NOT NULL,
        `Transports`                json            NOT NULL,
        `UvInitialized`             tinyint(1)      NOT NULL,
        `BackupEligible`            tinyint(1)      NOT NULL,
        `BackupState`               tinyint(1)      NOT NULL,
        `AttestationObject`         longblob        NULL,
        `AttestationClientDataJson` longblob        NULL,
        `Description`               varchar(200)    NULL,
        `CreatedAtUnixTime`         bigint          NOT NULL,
        `UpdatedAtUnixTime`         bigint          NOT NULL,
        CONSTRAINT `PK_CredentialRecords` PRIMARY KEY (`Id`)
    ) CHARACTER SET = utf8mb4
      COLLATE = utf8mb4_0900_ai_ci;
    
    CREATE UNIQUE INDEX `IX_CredentialRecords_UserHandle_CredentialId_RpId` ON `CredentialRecords` 
    (
        `UserHandle`,
        `CredentialId`,
        `RpId`
    );
    
    CREATE UNIQUE INDEX `IX_CredentialRecords_CredentialId_RpId` ON `CredentialRecords` 
    (
         `CredentialId`,
         `RpId`
    );
  7. Configure SQL Server schema for WebAuthn.Net

    main

    When using WebAuthn.Net.Storage.SqlServer, you must implement a schema migration strategy (e.g., Entity Framework, FluentMigrator) to create the required tables.

    Critical Requirements

    1. Unique Indexes: You must create unique indexes on the combination of RpId, UserHandle, and CredentialId. This combination acts as the unique descriptor for the public key.
    2. Data Types: The identifier generator produces sequential identifiers compatible only with the uniqueidentifier data type (passed as System.Guid in C#).
      • Warning: Do not use binary(16) for the primary key instead of uniqueidentifier, as this will cause significant performance degradation.
    3. JSON Constraint: The Transports column must be valid JSON.
    CREATE TABLE [CredentialRecords]
    (
        [Id]                        uniqueidentifier NOT NULL,
        [RpId]                      nvarchar(256)    NOT NULL,
        [UserHandle]                varbinary(128)   NOT NULL,
        [CredentialId]              varbinary(1024)  NOT NULL,
        [Type]                      int              NOT NULL,
        [Kty]                       int              NOT NULL,
        [Alg]                       int              NOT NULL,
        [Ec2Crv]                    int              NULL,
        [Ec2X]                      varbinary(256)   NULL,
        [Ec2Y]                      varbinary(256)   NULL,
        [RsaModulusN]               varbinary(1024)  NULL,
        [RsaExponentE]              varbinary(32)    NULL,
        [OkpCrv]                    int              NULL,
        [OkpX]                      varbinary(32)    NULL,
        [SignCount]                 bigint           NOT NULL,
        [Transports]                nvarchar(max)    NOT NULL,
        [UvInitialized]             bit              NOT NULL,
        [BackupEligible]            bit              NOT NULL,
        [BackupState]               bit              NOT NULL,
        [AttestationObject]         varbinary(max)   NULL,
        [AttestationClientDataJson] varbinary(max)   NULL,
        [Description]               nvarchar(200)    NULL,
        [CreatedAtUnixTime]         bigint           NOT NULL,
        [UpdatedAtUnixTime]         bigint           NOT NULL,
        CONSTRAINT [PK_CredentialRecords] PRIMARY KEY ([Id])
    );
    ALTER TABLE [CredentialRecords]
        ADD CONSTRAINT [Transports should be formatted as JSON] CHECK (ISJSON(Transports) = 1);
    CREATE UNIQUE INDEX [IX_CredentialRecords_UserHandle_CredentialId_RpId] ON [CredentialRecords] ([UserHandle], [CredentialId], [RpId]);
    CREATE UNIQUE INDEX [IX_CredentialRecords_CredentialId_RpId] ON [CredentialRecords] ([CredentialId], [RpId]);
  8. Run the FIDO Conformance Test

    main

    Follow these steps to execute the FIDO2 MDS3 tests using the conformance tools and the demo application:

    1. Configure the Conformance Tools

    1. Launch the FIDO Conformance Tools.
    2. Select FIDO2 Server - MDS3 Tests from the test options.
    3. In SELECT TESTS TO RUN, enable all available options.
    4. In TESTS CONFIGURATION, enable all available options and specify your application URL (default: https://localhost:5000).
    5. Click Download test metadata.
    6. Extract the metadataStatements folder from the downloaded metadata.zip and move its contents to: demo/WebAuthn.Net.Demo.FidoConformance/ConformanceMetadata/MetadataStatements.

    2. Set up the MDS3 Test Server

    1. Click the MDS3 test server button.
    2. In the Server endpoint field, enter your application address (default: https://localhost:5000).
    3. Click Download root certificate. Place the resulting MDS3ROOT.crt in: demo/WebAuthn.Net.Demo.FidoConformance/ConformanceMetadata/RootCertificate.
    4. Click SUBMIT. Download the 5 resulting file links and place them in: demo/WebAuthn.Net.Demo.FidoConformance/ConformanceMetadata/Jwt.

    3. Execute the Test

    1. Start the test application (see Starting the test application).
    2. Once the application is running, return to the tools and press RUN.
    3. Verify that all tests complete successfully.
  9. Set up a local SQL Server development environment

    main

    You can run a local Microsoft SQL Server container for testing using Docker.

    1. Start the Container

    Run the following command to pull and start the SQL Server 2019 image:

    2. Connection String

    Use the following connection string to connect to your local container.

    Note: Ensure you update the Initial Catalog parameter to match your specific database name.

    docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=WebAuthn!1337" -p 1433:1433 -d mcr.microsoft.com/mssql/server:2019-latest
    Data Source=localhost;Initial Catalog=webauthn;User ID=sa;Password=WebAuthn!1337;Pooling=True;Trust Server Certificate=True