ASP.NET Core 3.1 - 2FA Authenticating


Ken Haggerty
Created 08/20/2020 - Updated 08/31/2020 06:28

This article will describe the implementation of functions to generate 2FA keys and verify the code provided by the authenticator app. I will assume you have created a new ASP.NET Core 3.1 Razor Pages project. See Tutorial: Get started with Razor Pages in ASP.NET Core. You should review the earlier articles of the Users Without Identity Project series.

Users Without Identity Project and Article Series

Access to the research project source code may be purchased on KenHaggerty.Com at Manage > Assets. A project which implements users without Identity has been published to demo.kenhaggerty.com. 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.

I developed a TwoFactorAuth class with functions to generate an authenticator key and verify the code provided by the authenticator app. The authenticator key must use base 32 characters. TwoFactorAuth.cs depends on the Identity Base32 class. See GitHub - Identity/ src/ Core/ Base32.cs

Authenticator apps generate a time-based one-time password (TOTP). The TOTP is a 6 digit code which is a hash of the key and the current time. To verify the TOTP you need to generate a code with the same algorithm and compare.

Services > TwoFactorAuth.cs:
public static class TwoFactorAuth
{
    public static string GetAuthenticatorKey()
    {
        // Generates a new base32 encoded 160-bit security secret (size of SHA1 hash).
        byte[] bytes = new byte[20];
        using (var rng = RandomNumberGenerator.Create())
            rng.GetBytes(bytes);
        return Base32.ToBase32(bytes);
    }

    public static int GetAuthenticatorCode(string key)
    {
        var unixTimestamp = (DateTime.UtcNow.Ticks - 621355968000000000L) / 10000000L;
        var window = unixTimestamp / (long)30;
        var keyBytes = Base32.FromBase32(key);
        var counter = BitConverter.GetBytes(window);
        if (BitConverter.IsLittleEndian)
            Array.Reverse(counter);

        var hmac = new HMACSHA1(keyBytes);
        var hash = hmac.ComputeHash(counter);
        var offset = hash[^1] & 0xf;

        // Convert the 4 bytes into an integer, ignoring the sign.
        var binary =
            ((hash[offset] & 0x7f) << 24)
            | (hash[offset + 1] << 16)
            | (hash[offset + 2] << 8)
            | (hash[offset + 3]);

        return binary % (int)Math.Pow(10, 6);
    }

    public static long GetCurrentCounter()
    {
        DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        return 30 - (long)(DateTime.UtcNow - unixEpoch).TotalSeconds % 30;
    }
}

I developed a demo which simulates an authenticator app. It allows you to scan an anonymous key into your app and compare verification codes and time remaining. See: Ken's Demo Site - Authenticator App

Authenticator App Demo
Update 08/28/2020

I updated the series article links.

Update 08/31/2020

I added the 2FA Admin Override article link.

Article Tags:

2FA Authorization

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