ASP.NET Core 5.0 - FIDO2 Challenge Options

Download KH Authenticator

KH Authenticator App Icon
.NET MAUI App for Windows and Android
Online Registration and Authentication
No Password Or Email Address Required!
Certified Providers
KenHaggerty.Com Users Without Passwords Users With Passwords Users Without Identity

This article will describe the options used to implement authentication with a FIDO2 device. I will assume you have downloaded the ASP.NET Core 5.0 - Users Without Passwords Project.

Users Without Passwords Project and Article Series

I have developed two separate projects in the Users Without Passwords Project (UWPP) solution. The Users Without Passwords v4 project supports Bootstrap v4 and the new Users Without Passwords project supports Bootstrap v5. The new version is published at Fido.KenHaggerty.Com. You can register a new user with Windows Hello or a FIDO2 security key. Details, screenshots, and related articles can be found at ASP.NET Core 5.0 - Users Without Passwords Project. The details page includes the version change log.

The ceremonies to create or authenticate a FIDO2 authenticator implement options which must match property names and types. The CredentialCreationOptions are used with the navigator. credentials. create({ publicKey: createOptions }) method to register a new device.

5.4. Options for Credential Creation
dictionary PublicKeyCredentialCreationOptions {
    required PublicKeyCredentialRpEntity              rp;
    required PublicKeyCredentialUserEntity            user;

    required BufferSource                             challenge;
    required sequence<PublicKeyCredentialParameters>  pubKeyCredParams;

    unsigned long                                     timeout;
    sequence<PublicKeyCredentialDescriptor>           excludeCredentials = [];
    AuthenticatorSelectionCriteria                    authenticatorSelection;
    DOMString                                         attestation = "none";
    AuthenticationExtensionsClientInputs              extensions;

The CredentialRequestOptions are used with the navigator. credentials. get({ publicKey: getOptions }) method to authenticate a device.

5.5. Options for Assertion Generation
dictionary PublicKeyCredentialRequestOptions {
    required BufferSource                             challenge;
    unsigned long                                     timeout;
    USVString                                         rpId;
    sequence<PublicKeyCredentialDescriptor>           allowCredentials = [];
    DOMString                                         userVerification = "preferred";
    AuthenticationExtensionsClientInputs              extensions;

The UWPP implements ChallengeOptions and AuthenticatorSelection classes and a UserVerificationRequirement enum. The ChallengeOptions are configured by appsettings.json and added to the service container in Startup > ConfigureServices.

public class ChallengeOptions
    public string CredentialType { get; set; }
    public string RelyingPartyName { get; set; }
    public List<int> PubKeyCredParamsAlgs { get; set; }
    public AuthenticatorSelection AuthenticatorSelection { get; set; }
    public bool UvmExtension { get; set; }
    public bool CredPropsExtension { get; set; }
    public List<string> AllowCredentialTransports { get; set; }
    public string Attestation { get; set; }
    public uint AttestationTimeout { get; set; }
    public uint AssertionTimeout { get; set; }
public class AuthenticatorSelection
    public string AuthenticatorAttachment { get; set; }
    public bool RequireResidentKey { get; set; }
    public UserVerificationRequirement UserVerification { get; set; }
public enum UserVerificationRequirement
    required = 0,
    preferred = 1,
    discouraged = 2
"ChallengeOptions": {
  "CredentialType": "public-key",
  "RelyingPartyName": "Users Without Passwords",
  "PubKeyCredParamsAlgs": [
    -7, // ECDSA w/ SHA-256 - External authenticators implements "ES256"
    -35, // ECDSA w/ SHA-384
    -36, // ECDSA w/ SHA-512
    -257, // RSASSA-PKCS1-v1_5 w/ SHA-256 - Windows Hello implements "RS256"
    -258, // RSASSA-PKCS1-v1_5 w/ SHA-384
    -259 // RSASSA-PKCS1-v1_5 w/ SHA-512
  "AuthenticatorSelection": {
    "AuthenticatorAttachment": "cross-platform", // platform, cross-platform
    "RequireResidentKey": false, // username-less flows = true
    "UserVerification": "preferred" // required, preferred, discouraged
  "UvmExtension": true,
  "CredPropsExtension": true,
  "AllowCredentialTransports": [ "usb", "nfc", "ble", "internal" ],
  "Attestation": "direct", // none, indirect, direct, enterprise
  "AttestationTimeout": 90000, // 1.5 minutes
  "AssertionTimeout": 120000 // 2 minutes
Startup > ConfigureServices:

The UWPP implements the complete ceremony on a single page. The RegisterChallenge page injects the ChallengeOptions. The OnGet() method generates and serializes the CredentialCreationOptions.

public class RegisterChallengeModel : PageModel
    private readonly IUserService _userService;
    private readonly ICredentialService _credentialService;
    private readonly ChallengeOptions _challengeOptions;
    public RegisterChallengeModel(IUserService userService,
        ICredentialService credentialService,
        IOptions<ChallengeOptions> challengeOptions)
        _userService = userService;
        _credentialService = credentialService;
        _challengeOptions = challengeOptions?.Value;

    public string LoginName { get; set; }
    public Guid ChallengeGuid { get; set; }

    public string CreateOptions { get; set; }
    /// <summary>
    /// 7.1
    /// </summary>
    public IActionResult OnGet()
        if (User.Identity.IsAuthenticated) return RedirectToPage("/Account/Manage/Credentials");

        if (TempData.Peek("LoginName") == null)
            return RedirectToPage("./Register");

        // Generate challenge
        ChallengeGuid = Guid.NewGuid();
        var challenge = ChallengeGuid.ToByteArray(); // 16 bytes

        var currentHost = HttpContext.Request.Host.ToString();
        var relyingPartyId = currentHost.Split(":")[0];
        var rp = new Rp
            Name = _challengeOptions.RelyingPartyName,
            Id = relyingPartyId

        var user = new
            name = LoginName.ToUpper(),
            displayName = LoginName,
            id = challenge // new UserHandle

        var pubKeyCredParams = new List<PubKeyCredParams>();
        var pubKeyCredParamsAlgs = _challengeOptions.PubKeyCredParamsAlgs;
        foreach (var alg in pubKeyCredParamsAlgs)
            pubKeyCredParams.Add(new PubKeyCredParams()
                Type = _challengeOptions.CredentialType,
                Alg = alg

        var authenticatorSelection = _challengeOptions.AuthenticatorSelection;
        var attestation = _challengeOptions.Attestation;
        var timeout = _challengeOptions.AttestationTimeout;

        var extensions = new
            uvm = _challengeOptions.UvmExtension,
            credProps = _challengeOptions.CredPropsExtension

        // 7.1.1 Let options be a new PublicKeyCredentialCreationOptions structure configured to the Relying Party's
        // needs for the ceremony.
        var createOptions = new
        var jsonSerializerOptions = new JsonSerializerOptions
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) },
            WriteIndented = true

        CreateOptions = JsonSerializer.Serialize(createOptions, jsonSerializerOptions);

        return Page();


The FIDO2 authenticators expect the Challenge, UserHandle, and CredentialId properties to be a JavaScript Uint8Array type. The UWPP generates and compiles these properties as Base64 string. The UWPP implements a conversion function in site.js.

function getUint8Array(byteString) {
    let binaryValue = window.atob(byteString);
    let len = binaryValue.length;
    let uint8Array = new Uint8Array(len);
    let i;
    for (i = 0; i < len; i++)
        uint8Array[i] = binaryValue.charCodeAt(i);
    return uint8Array;

The RegisterChallenge page's JavaScript updates the appropriate properties to Uint8Array in a createCredential() function.

RegisterChallenge.cshtml - JavaScript:
let createOptions = @Html.Raw(Model.CreateOptions);
// Update byte string to expected Uint8Array
try {
    if (typeof (createOptions.challenge) != Uint8Array)
        createOptions.challenge = getUint8Array(createOptions.challenge);

    if (typeof ( != Uint8Array) = getUint8Array(;

} catch (e) {
    showMessageModal(e, 'alert-warning');
RegisterChallenge Page.
RegisterChallenge Page Mobile.
Ken Haggerty
Created 07/04/21
Updated 08/16/21 04:00 GMT

Log In or Reset Quota to read more.

Article Tags:

FIDO JavaScript Json Model
Successfully completed. Thank you for contributing.
Contribute to enjoy content without advertisments.
Something went wrong. Please try again.



Not accepting new comments.

Submit your comment. Comments are moderated.

User Image.
DisplayedName - Member Since ?