Skip to content

passwordless-lib/fido2-net-lib

Repository files navigation

Passkeys - FIDO2 .NET Library (WebAuthn)

A fully working and battle tested library for passkeys (FIDO2 and WebAuthn) on .NET

codecov Financial Contributors on Open Collective NuGet Status

Releases & Change log

đź’ˇ Bitwarden Passwordless API

The quickest way to get started with FIDO2 and WebAuthn is with the Bitwarden Passwordless API. It's free up to 10k users and a faster way to start using passkeys on your website or mobile app.

Bitwarden Passwordless.dev supports .NET Framework as well as the latest .net 8+.

Get started with passwordless.dev

Purpose

Our purpose is to enable passwordless sign in for all .NET apps (asp, core, native).

To provide a developer friendly and well tested .NET FIDO2 Server / WebAuthn relying party library for the easy validation of registration (attestation) and authentication (assertion) of FIDO2 / WebAuthn credentials, in order to increase the adoption of the technology, ultimately defeating phishing attacks.

This project is part of the .NET foundation

Installation

Requirements: .NET 8.0 or later

dotnet add package Fido2

To use the ASP.NET Core helpers:

dotnet add package Fido2.AspNet

For Blazor WebAssembly support:

dotnet add package Fido2.BlazorWebAssembly

⚠️ Breaking Changes: If upgrading from v3.x, see the Upgrade Guide for migration instructions.

Demo

What is FIDO2?

The passwordless web is here. FIDO2 / WebAuthn is a modern, stable and open authentication standard, supported by browsers and many large tech companies such as Microsoft, Google etc. The main driver is to allow a user to login without passwords, creating passwordless flows or strong MFA for user signup/login on websites. The standard is not limited to web applications with support coming to native apps. The technology builds on public/private keys, allowing authentication to happen without sharing a secret between the user & website. This brings many benefits, such as easier and safer logins and makes phishing attempts extremely hard.

Read more:

Supported features

  • âś… Attestation API & verification (Register and verify credentials/authenticators)
  • âś… Assertion API & verification (Authenticate users)
  • âś… 100% pass rate in conformance testing (results)
  • âś… FIDO2 security keys aka roaming authenticators (spec), like SoloKeys Solo, Yubico YubiKey, and Feitian BioPass FIDO2)
  • âś… Device embedded authenticators aka platform authenticators (spec), like Android Key and TPM)
  • âś… Backwards compatibility with FIDO U2F authenticators (spec)
  • âś… Windows Hello
  • âś… Face ID and Touch ID for the Web (aka "Apple Hello")
  • âś… All currently referenced cryptographic algorithms for FIDO2 Server (spec)
  • âś… All current attestation formats: "packed", "tpm", "android-key", "android-safetynet", "fido-u2f", "apple", "apple-appattest", and "none" (spec)
  • âś… FIDO2 Server attestation validation via FIDO Metadata Service V3 (spec)
  • âś… WebAuthn extensions (spec) including PRF, Large Blob, Credential Protection
  • âś… Blazor WebAssembly support for client-side applications
  • âś… Examples & demos (ASP.NET Core and Blazor WebAssembly)
  • âś… Intellisense documentation

Configuration

Only some options are mentioned here, see the Configuration class for all options

  • fido2:MDSCacheDirPath - App Secret / environment variable that sets the cache path for the MDS. Defaults to "current user's temporary folder"/fido2mdscache. Optional when using the default MetadataService provider.

Quick Start

1. Configure Services (ASP.NET Core)

services.AddFido2(options =>
{
    options.ServerDomain = "example.com";
    options.ServerName = "Example App";
    options.Origins = new HashSet<string> { "https://example.com" };
});

2. Inject IFido2 Service

public class AuthController : Controller
{
    private readonly IFido2 _fido2;

    public AuthController(IFido2 fido2)
    {
        _fido2 = fido2;
    }
}

Examples

For integration patterns, see:

Create Attestation Options

To add FIDO2 credentials to an existing user account, start by creating options for the client.

// 1. Get user from DB by username (in our example, auto create missing users)
var user = DemoStorage.GetOrAddUser(username, () => new User
{
    DisplayName = "Display " + username,
    Name = username,
    Id = Encoding.UTF8.GetBytes(username) // byte representation of userID is required
});

// 2. Get user existing keys by username
var existingKeys = DemoStorage.GetCredentialsByUser(user)
    .Select(c => c.Descriptor)
    .ToList();

// 3. Create options using new parameter wrapper
var options = _fido2.RequestNewCredential(new RequestNewCredentialParams
{
    User = user,
    ExcludeCredentials = existingKeys,
    AuthenticatorSelection = AuthenticatorSelection.Default,
    AttestationPreference = AttestationConveyancePreference.Parse(attType),
    Extensions = new AuthenticationExtensionsClientInputs
    {
        CredProps = true  // Enable credential properties extension
    }
});

// 4. Temporarily store options, session/in-memory cache/redis/db
HttpContext.Session.SetString("fido2.attestationOptions", options.ToJson());

// 5. Return options to client
return Json(options);

Register Credentials

When the client returns a response, verify and register the credentials.

// 1. Get the options we sent the client and remove from storage
var jsonOptions = HttpContext.Session.GetString("fido2.attestationOptions");
HttpContext.Session.Remove("fido2.attestationOptions");
var options = CredentialCreateOptions.FromJson(jsonOptions);

// 2. Create callback so that lib can verify credential id is unique to this user
IsCredentialIdUniqueToUserAsyncDelegate callback = async (IsCredentialIdUniqueToUserParams args) =>
{
    var users = await DemoStorage.GetUsersByCredentialIdAsync(args.CredentialId);
    return users.Count == 0; // Return true if credential ID is unique
};

// 3. Verify and make the credentials using new parameter wrapper
var result = await _fido2.MakeNewCredentialAsync(new MakeNewCredentialParams
{
    AttestationResponse = attestationResponse,
    OriginalOptions = options,
    IsCredentialIdUniqueToUserCallback = callback
});

// 4. Store the credentials in database
DemoStorage.AddCredentialToUser(options.User, new StoredCredential
{
    Descriptor = new PublicKeyCredentialDescriptor(result.Id),
    PublicKey = result.PublicKey,
    UserHandle = result.User.Id
});

// 5. Return success to client
return Json(result);

Create Assertion Options

For user authentication, create assertion options based on registered credentials.

// 1. Get user from DB
var user = DemoStorage.GetUser(username);
if (user == null) return NotFound("Username was not registered");

// 2. Get registered credentials from database
var existingCredentials = DemoStorage.GetCredentialsByUser(user)
    .Select(c => c.Descriptor)
    .ToList();

// 3. Create options using new parameter wrapper
var options = _fido2.GetAssertionOptions(new GetAssertionOptionsParams
{
    AllowedCredentials = existingCredentials,
    UserVerification = UserVerificationRequirement.Preferred,
    Extensions = new AuthenticationExtensionsClientInputs
    {
        Extensions = true
    }
});

// 4. Temporarily store options, session/in-memory cache/redis/db
HttpContext.Session.SetString("fido2.assertionOptions", options.ToJson());

// 5. Return options to client
return Json(options);

Verify the Assertion Response

When the client returns a response, verify it and accept the login.

// 1. Get the assertion options we sent the client and remove from storage
var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
HttpContext.Session.Remove("fido2.assertionOptions");
var options = AssertionOptions.FromJson(jsonOptions);

// 2. Get registered credential from database
var creds = DemoStorage.GetCredentialById(clientResponse.Id);

// 3. Create callback to check if userhandle owns the credentialId
IsUserHandleOwnerOfCredentialIdAsync callback = async (args) =>
{
    var storedCreds = await DemoStorage.GetCredentialsByUserHandleAsync(args.UserHandle);
    return storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId));
};

// 4. Make the assertion using new parameter wrapper
var result = await _fido2.MakeAssertionAsync(new MakeAssertionParams
{
    AssertionResponse = clientResponse,
    OriginalOptions = options,
    StoredPublicKey = creds.PublicKey,
    StoredSignatureCounter = creds.SignatureCounter,
    IsUserHandleOwnerOfCredentialIdCallback = callback
});

// 5. Store the updated counter
DemoStorage.UpdateCounter(result.CredentialId, result.Counter);

// 6. Return success to client
return Json(result);

Nuget package

https://www.nuget.org/packages/Fido2/ and https://www.nuget.org/packages/Fido2.Models/

Contributing

See Contributing for information about contributing to the project.

This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. For more information see the .NET Foundation Code of Conduct.

For security and penetration testing, please see our Vulnerability Disclosure Program

Contributors

Code Contributors

This project exists thanks to all the people who contribute. [Contribute].

Financial Contributors

Become a financial contributor and help us sustain our community. [Contribute]

Individuals

Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]

.NET Foundation

This project is supported by the .NET Foundation.

Sponsor this project

  •  

Packages

 
 
 

Contributors 45

Languages