ASP.NET Core 3.1 - Round Trip Challenge

Ken Haggerty
Created 05/08/2020 - Updated 06/09/2020 21:45

This article will describe the round trip of the challenge, a unique code used by FIDO, from the server to the client-js to the postback verification. I will assume you have created a new ASP.NET Core 3.1 Razor Pages project and have reviewed the previous articles of the series. I won't use Identity or Individual User Accounts. See Tutorial: Get started with Razor Pages in ASP.NET Core.

FIDO Utilties Project and Article Series

The project implements Bootstrap Native v3 with a jQuery option. The Message Modal requires jQuery or Bootstrap Native.

Access to the research project source code may be purchased on KenHaggerty.Com at Manage > Assets.

I will publish the FIDO Utilities Project at until I publish the Users Without Passwords Project.

I enjoy writing these articles. It often enhances and clarifies my coding. The research project is a result of a lot of refactoring and hopefully provides logical segues for the articles. Thank you for supporting my efforts.

The Users Without Passwords Project's registration and login processes involve communication between the server, client-js, authenticator, and user. The server provides a unique code called a challenge to the client. The client transforms the challenge to a UInt8Array expected by the authenticator. The client requests the user's login name. The challenge and username are used to register or verify a public key with the authenticator. The client sends the response from the authenticator to the server. The response is decoded and verified. The response includes the challenge which is decoded from the UInt8Array to verify a match to the server's original code. If the response and challenge are verified the response is stored and action is implemented like create or login the user.

You should detect if Credentials and PublicKeyCredential are supported. Most but not all browsers provide support which is required by FIDO2 processes. I developed a getWebAuthnError() function in site.js which also notifies the lack of https on hosts other than localhost.

site.js - getWebAuthnError
function getWebAuthnError() {
    if (!('credentials' in navigator)) return 'This browser does not currently support Credentials.';
    if (window.PublicKeyCredential === undefined || typeof window.PublicKeyCredential !== 'function') {
        let errorMessage = 'This browser does not currently support WebAuthn.';
        if (window.location.protocol === 'http:' && (window.location.hostname !== 'localhost' && window.location.hostname !== ''))
            errorMessage = 'WebAuthn only supports secure connections. For testing over HTTP, you can use the origin "localhost".';
        return errorMessage;
    return '';

This function returns an error or empty string. If an error is detected, you can disable buttons for FIDO functions or inform and redirect the user with the message modal.

document.addEventListener('DOMContentLoaded', function () {
    let waError = getWebAuthnError();
    if (waError != '') {
        showMessageModal(waError, 'alert-danger', false, '', '', true, '/', '_self', 'Home');

The server provides a unique code called the challenge which is sent to the client-js. The server must persist the code between the initial request and the callback. I use the Cookie TempDataProvider in the FIDO Challenges Project for the proof of concept. I am using the Application Session State in the Users Without Passwords Project. See Session and state management in ASP.NET Core

Add the CookieTempDataProvider to the RazorPages service.

Startup.cs > ConfigureServices

To access the TempDataProvider, inject the ITempDataProvider to the PageModel.

public class IndexModel : PageModel
    private readonly ITempDataProvider _tempData;
    public IndexModel(ITempDataProvider tempData)
        _tempData = tempData ?? throw new ArgumentNullException(nameof(tempData));

    public void OnGet()
        _tempData.SaveTempData(HttpContext, new Dictionary<string, object> { { "challenge", guidString } });

I figured out random number generators researching ASP.NET Core 3.1 - Password Hasher. The first working challenges in the Users Without Passwords Project were 16 byte arrays populated by a rng. I found a FIDO web app example which used a new guid for the unique code. I save the Guid. ToString() in TempData and I use it as a transaction id when I store the creation or verification of the credential. I use the 16 byte Guid. ToByteArray() for the challenge.

var challengeGuid = Guid.NewGuid();
var guidString = challengeGuid.ToString();
var guidByteArray = challengeGuid.ToByteArray(); // 16 bytes
var guidASCIIBytes = Encoding.ASCII.GetBytes(guidString); // 36 bytes

I am not sure Guid. ToByteArray() is compatible with JavaScript's btoa (binary to ASCII) and atob (ASCII to binary) functions. I am sure the 36 byte Encoding. ASCII. GetBytes(Guid. ToString()) is compatible. I continue to evaluate the smaller Guid. ToByteArray() with no issue. See MDN - Base64

GitHub - scottbrady91 / Fido2-Poc uses NuGet - IdentityModel. AspNetCore. OAuth2Introspection which includes a CryptoRandom class to create unique strings and byte arrays.

var uniqueId = CryptoRandom.CreateUniqueId(); // wcUsfGAgExAwC6w4NBlGTS2T6E3QjRQrDJspGHZyp3o
var uidBytes = Base64Url.Decode(uniqueId); // 32 bytes
var randomKey = CryptoRandom.CreateRandomKey(16);

I am not using the IdentityModel Nuget package, but I am using a copy of Base64Url.cs. Base64 encoding uses the = char to pad the string to a multiple of four. The Users Without Passwords Project implements Base64Url encoding required to properly decode the authenticator's response. See GitHub - IdentityModel/ src/ Base64Url. cs

The challenge must be properly encoded and decoded to JavaScript's UInt8Array which is required by the authenticator.

TypeError: Failed to execute 'create' on 'CredentialsContainer': The provided value is not of type '(ArrayBuffer or ArrayBufferView)'

The challenge demo in the project simulates an authenticator by converting the challenge byte array from the server to a UInt8Array before posting the serialized challenge array to the callback. There are many more FIDO property values which require this conversion. I developed a JavaScript function getUint8Array (byteString) 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;

I implement getUint8Array (byteString) in the Users Without Passwords Project before navigator. credentials. create({ publicKey }) and navigator. credentials. get({ publicKey }) often within a loop.

// Updates for expected Uint8Array
try {
    publicKey.challenge = getUint8Array(publicKey.challenge); = getUint8Array(;
    let allowCredLen = publicKey.excludeCredentials.length;
    for (let ci = 0; ci < allowCredLen; ci++) {
        publicKey.excludeCredentials[ci].id = getUint8Array(publicKey.excludeCredentials[ci].id);
} catch (e) {
    showMessageModal(e, 'alert-warning');

I implement getUint8Array (byteString) in the FIDO Utilities Project's Challenge demo with a Validate UInt8Array for Authenticator checkbox.

// Authenticators expect and return Uint8Array
if (document.querySelector('#ValidateUInt8ArrayCheckboxId').checked) {
        postData.Challenge = getUint8Array(postData.Challenge);
    catch (e) {
        showMessageModal('Challenge Uint8Array Failed. ' + e.toString(), 'alert-danger');
    // An authenticator will get and return the challenge
    postData.Challenge = window.btoa(String.fromCharCode.apply(null, postData.Challenge));

The callback decodes the posted challenge and compares it to the original Guid. ToString() we stored in TempData.

var tempDataDictionary = _tempData.LoadTempData(HttpContext);
// Verify TempData
if (!tempDataDictionary.ContainsKey("GuidString"))
    return new JsonResult(new { Status = "Failed", Error = "Temp Data GuidString not found." });

var tempDataGuidString = tempDataDictionary["GuidString"].ToString();
var tempDataGuid = new Guid(tempDataGuidString);
var tempDataChallengeBytes = tempDataGuid.ToByteArray();
var postChallengeBytes = Base64Url.Decode(postChallengeString);
if (!tempDataChallengeBytes.SequenceEqual(postChallengeBytes))
    return new JsonResult(new { Status = "Failed", Error = "The challenge did not verify." });

The FIDO Utilities Project's challenge demo page generates new guids then compares and validates the Guid. ToByteArray() and Encoding. ASCII. GetBytes(Guid. ToString()). Help me find a Guid. ToByteArray() which is not ASCII compatible.

Challenge Demo
Update 05/10/2020

I updated the article links.

Update 05/23/2020

I added the AES Cipher article link and updated the screenshot.

Update 06/09/2020

I updated the article to announce support for Bootstrap Native v3.

Article Tags:

FIDO JavaScript Json Modal

Comment Count = 0

Please log in to comment or follow.

Login Register
Follow to get web notifications when new comments are posted to this article.
Logged in users receive web notifications for new articles, topics and assets.
Web Notifications