Skip to content

4. Wrangling The FIDO2.Net library into EF Core

Matt Goldman edited this page Jul 22, 2023 · 8 revisions

🏗️ WIP

The WebAuthN ceremonies have a client-side component and a server-side component. The server side in dotnetflix is IdentityServer, and while it doesn't include FIDO2 or WebAuthN support out of the box, the single greatest benefit of IdentityServer (over say a cloud based IDP or closed-source solution) is that it's just a NuGet package that you include in your code. You can extend or modify its behavior as much as you need to.

In dotnetflix, FIDO2 support is provided via the Fido2 library. This library takes care of things like issuing challenges and validating attestations and assertions. However, the demo code provided uses an in-memory store which means that an authenticator registered for passwordless authentication could not be used after a reboot (or any other loss of memory state). Additionally, it doesn't interact with the user store in any way, just using its own Fido2User type instead, which is not persisted (e.g. with ASP.NET Core Identity).

While this is ok for a demo, I wanted to do something a little more production ready. I wanted to be able to persist the FIDO credentials to enable authentication after a reboot, and I wanted to be able to associate them with my IdentityUser type. The Fido2 library doesn't play particularly well with AspNetCore.Identity or EF Core out of the box, but with the following changes I was able to get it to work.

Adding derived types and configurating relationships

The biggest hurdle is that the Fido2 library is designed for use in any context, not just with EF Core. This meant that there are no relationships defined between the entities, and that some of the properties are not particularly database friendly (for example, the cryptographic blocks used for challenges). The first problem is relatively straightforward to solve - we can use EF Core conventions to add navigation properties and specify relationships.

The first type I added is FidoUser which inherits the Fido2User class that comes from the Fido2.Net library. I then added a navigation property for this type to the ApplicationUser class (that comes from the IdentityServer template):

public class ApplicationUser : IdentityUser
{
    public FidoUser FidoUser { get; set; }
}

I also added a navigation property and foreign key to the FidoUser class which enables EF Core to understand the parent/child nature of the relationship:

public string ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }

This adds a relationship between the ApplicationUser type, which is what represents a user who signs up or signs in, and the FidoUser type, which is used for attestations and assertions.

The Fido2.Net library has a StoredCredential type, which represents a credential registered in an attestation and validated in an assertion. I have added a type called FidoStoredCredential which inherits this, and adds a navigation property for the FidoUser type.

public class FidoStoredCredential : StoredCredential
{
    public int CredentialId { get; set; }
    
    public int FidoUserId { get; set; }
    public FidoUser FidoUser { get; set; }

    public new FidoPublicKeyDescriptor Descriptor { get; set; }
}

A user can register many credentials, so the FidoUser type defines a collection:

public ICollection<FidoStoredCredential> StoredCredentials { get; set; } = new List<FidoStoredCredential>();

As you can see from the definition of the FidoStoredCredential type, it also has a relationship to a FidoPublicKeyDescriptor. This inherits the PublicKeyCredentialDescriptor type from the Fido2.Net library and is used to store the public key created during the attestation.

public class FidoPublicKeyDescriptor : PublicKeyCredentialDescriptor
{
    public int DescriptorId { get; set; }

    public int CredentialId { get; set; }
    public FidoStoredCredential Credential { get; set; }

    public FidoPublicKeyDescriptor(byte[] CredentialId)
    {
        Id = CredentialId;
    }

    public FidoPublicKeyDescriptor()
    {
        
    }
}

This is all the entities required to support FIDO2, but they also need some additional configuration.

Entity Configuration

The first step is to add the new entities to the ApplicationDbContext.

public DbSet<FidoUser> FidoUsers { get; set; }
public DbSet<FidoStoredCredential> FidoStoredCredentials { get; set; }
public DbSet<FidoPublicKeyDescriptor> FidoPublicKeyDescriptors { get; set; }