07. IdentityServer4 MFA – TOTP .NET Core 3.1

You can find the project here.


Multi-factor authentication or MFA requires multiple factors to authenticate a user. Two-factor authentication (2FA) is an MFA with two factors. There is no real limit on how many factors we can add but it’s not practical for a user to use many factors as it hurts usability of the application if the login process is too long and complicated. Adding a second factor is usually enough to stop the brute-force and dictionary attacks.

Not all MFA is created equal. Some factors are still vulnerable to reverse proxy scenarios where a phishing attack is launched to trick users into clicking a malicious link that will mimic a real application look and feel using a reverse proxy and will allow attackers to capture username, password and any type of code entered as a second factor. Other factors like hardware-based authenticators (think YubiKey 5 for example) using FIDO2 will protect you against reverse proxy man-in-the-middle attack scenarios. It all boils down to making trade-offs between usability, price, and security. Adding a second factor, even if it’s not an optimal one, is still better than having a single factor. There are only 3 categories of things that can be used to authenticate you:

  1. Something you know (password, PIN, gesture, etc.)
  2. Something you have (a card, key, one-time password, some kind of device, etc.)
  3. Something you are (face, fingerprint, retina, typing style, etc.)

Good authentication flow will use multiple factors from multiple categories. It might use a password and a one-time password (2FA with TOTP Google Authenticator for example), card and a PIN (ATM for example), face and password (on your phone for example).

Legacy ways of implementing MFA by sending codes using email or SMS are not recommended anymore as they are considered not secure enough as those channels can be easily intercepted. Time-based One-time Password (TOTP) is considered more secure with code being automatically generated every 30 seconds without the server and TOTP app talking to each other. It is based on a timestamp and TOTP algorithm. TOTP is generally accepted as a minimum these days to implement MFA.

An even better way of doing it is using FIDO2 hardware authenticators like YubiKey 5, SoloKeys, etc based on RSA encryption with public/private key pairs. There is currently no known way to bypass hardware authenticators with phishing or man-in-the-middle attacks or using social engineering approach. The only way would be for someone to steal your key (it is a small device like a USB dongle).

IdentityServer4, as we previously learned, has nothing to do with users and doesn’t care much about them. That is why we implemented the ASP.NET Core Identity as our user store. Along with user data storage, we got a handful of useful methods to deal with registering users, setting the password and adding additional factors. Our focus is to actually extend the ASP.NET Core Identity to work with the desired factors. We will only use factors that are considered secure in 2019 and avoid any legacy ways of doing MFA.

The first way we will implement MFA is using TOTP with Google Authenticator (or any other standard TOTP authenticator app) and the second way is using FIDO2 with YubiKey 5 (we will add FIDO2 in my next tutorial). Let’s get to it.


Code changes

This tutorial is based on a project from a previous tutorial. Scaffolded ASP.NET Core Identity comes with TOTP support out of the box. We just need to add some missing bits and pieces to our IdentityServer4 “Quickstart” to make it work properly.

The easiest way to connect to the TOTP authenticator app (Google Authenticator for example) is by scanning the QR code. We need to add the JavaScript library that will generate the QR code for us. Download the qrcode.js library from here https://davidshimjs.github.io/qrcodejs/ and put “qrcode.min.js” into “wwwroot/lib/qrcode” folder like so:

Now let’s open “Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml” and modify the “Scripts” section at the end of the file like so:

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
    <script type="text/javascript" src="~/lib/qrcode/qrcode.min.js"></script>
    <script type="text/javascript">
        new QRCode(document.getElementById("qrCode"),
                text: "@Html.Raw(Model.AuthenticatorUri)",
                width: 150,
                height: 150

Comment out (or remove) the section of the page that shows how to implement the QR code as we already implemented it like so:

@*<div class="alert alert-info">To enable QR code generation please read our <a href="https://go.microsoft.com/fwlink/?Linkid=852423">documentation</a>.</div>*@

In order for local login to allow user to enter TOTP we need to modify the response after successful first factor authentication with username and password. Open “Quickstart/Account/AccountController.cs” and locate HttpPost “Login” method. We will not get the “result.Succeeded” result anymore but “result.RequiresTwoFactor” result after the 2FA is enabled for a user. Let’s modify the code to handle this scenario like so:

if (result.Succeeded)
	(existing code - do not change)
else if (result.RequiresTwoFactor)
	string twoFactorUrl = "~/Identity/Account/LoginWith2fa?ReturnUrl={0}";
	if (context != null || Url.IsLocalUrl(model.ReturnUrl))
		return Redirect(string.Format(twoFactorUrl, HttpUtility.UrlEncode(model.ReturnUrl)));
		return Redirect(string.Format(twoFactorUrl, HttpUtility.UrlEncode("~/")));

That is all that is pretty much needed to get the TOTP to work and show with QR code. To get the full experience however we need to make a couple of changes to get the scaffolded ASP.NET Core Identity to work with us. To prove my point, start the application and navigate to http://localhost:5000/Identity/Account/Manage.

You will see an error like so:

ASP.NET Core Identity needs the registered implementation of IEmailSender to resolve the missing service. I will use SendGrid as an example, you can change it to whatever suits you.

First, install the “Sendgrid” NuGet package like so:

Next in the “Services” folder add a new class and name it “EmailSender”. It will implement the “IEmailSender” interface like so:

using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Configuration;
using SendGrid;
using SendGrid.Helpers.Mail;
using System.Threading.Tasks;

namespace IdentityServer.Services
    public class EmailSender : IEmailSender
        private readonly IConfiguration _configuration;

        public EmailSender(IConfiguration configuration)
            _configuration = configuration;

        public Task SendEmailAsync(string email, string subject, string message)
            string key = _configuration.GetSection("SendGrid").GetValue<string>("ApiKey");
            return Execute(key, subject, message, email);

        public Task Execute(string apiKey, string subject, string message, string email)
            var client = new SendGridClient(apiKey);
            string fromEmail = _configuration.GetSection("SendGrid").GetValue<string>("FromEmail");
            string fromName = _configuration.GetSection("SendGrid").GetValue<string>("FromName");
            var msg = new SendGridMessage()
                From = new EmailAddress(fromEmail, fromName),
                Subject = subject,
                PlainTextContent = message,
                HtmlContent = message
            msg.AddTo(new EmailAddress(email));

            // Disable click tracking.
            // See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
            msg.SetClickTracking(false, false);

            return client.SendEmailAsync(msg);

Let’s add configuration for the SendGrid in “appsettings.json” like so:

"SendGrid": {
	"ApiKey": "xxxxxxxx",
	"FromEmail": "identity@server.com",
	"FromName": "Identity Server"

Note: Change the SendGrid settings in “appsettings.json” to reflect your SendGrid config.

Let’s register the service in “Startup.cs”. Find the “ConfigureServices” method and at the end of the method add

services.AddTransient<IEmailSender, EmailSender>();

To make our lives a bit easier later let’s add these razor page options to the start of the “ConfigureServices” method like so:

                .AddRazorPagesOptions(options => 
                        options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");

Add the missing “using” directive:

using Microsoft.AspNetCore.Identity.UI.Services;

Also in “Startup.cs” find Configure method and change this:

app.UseEndpoints(endopoints =>

to this:

app.UseEndpoints(endpoints =>
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");

This code will allow application to show Razor pages.


Now we have the “EmailSender” registered and ready to send email using SendGrid.

Let’s do some cosmetic changes to align the ASP.NET Core Identity layout with the IdentityServer4 layout.

Open “Areas/Identity/Pages/Account/Manage/_Layout.cshtml” and change the master layout like so.

    Layout = "/Views/Shared/_Layout.cshtml";

Final change is to create “_ViewStart.cshtml” in same folder “Areas/Identity/Pages/Account/Manage” like so:

    Layout = "_Layout.cshtml";



TOTP Enrollment process

Launch IdentityServer4 application and navigate to http://localhost:5000/Identity/Account/Manage. You should see it now working in full glory

Click on “Two-factor authentication”.

Click on the “Add authenticator app”. You should now see the generated QR code.

On your mobile device install the “Google Authenticator” application (or any other TOTP authenticator app). Start the app and tap on “Scan a barcode”

You should get the code generated on your screen like so:

Enter the code on the page and click “Verify”. An authenticator is now verified. You can see the recovery codes. Save them as a backup in case you lose your phone or else you will not be able to authenticate.

Tap on “Add account” on the authenticator app to confirm you want to add the account.



Take a look at the user tables

Take a look at these tables “dbo.AspNetUsers” and “dbo.AspNetUserTokens”. You will see that the “TwoFactorEnabled” flag is turned on for user “alice” (that is the user I used). You will also notice that two users tokens got created for the same user “alice”. One is the authenticator key itself and another one is for the recovery codes.

Play with it

Sign out, sign in, reset authenticator app, disable 2FA. Play with it and get to know it good as we are going to extend this part of the ASP.NET Core Identity to add FIDO2 in my next tutorial. Because we want all them factors!

You can find the project here.


For direct assistance schedule a technical meeting with Ivan to talk about your requirements.
For a general overview of our services and a live demo schedule a meeting with Maja.


Comments are closed.