ASP.NET Core  

Multi-Factor Authentication (MFA) in ASP.NET Core Applications

As cyberattacks become more sophisticated, relying solely on passwords for authentication is no longer sufficient. Multi-Factor Authentication (MFA) adds an extra layer of security by requiring users to provide additional verification factors beyond just a password. ASP.NET Core Identity provides built-in support for implementing MFA using email, SMS, and authenticator apps (like Microsoft Authenticator or Google Authenticator).

In this article, we’ll explore how to set up and implement MFA in ASP.NET Core applications, along with best practices for securing user accounts.

What is Multi-Factor Authentication (MFA)?

MFA requires users to verify their identity using at least two of the following factors:

  1. Something you know – Password or PIN

  2. Something you have – Mobile phone, authenticator app, security key

  3. Something you are – Biometric factors like fingerprint or facial recognition

Example: A user enters their password (factor 1) and then confirms a code sent via SMS or an authenticator app (factor 2).

Why Use MFA?

  • Protects against credential theft and phishing attacks

  • Reduces risk of account takeover

  • Provides compliance with industry standards (GDPR, HIPAA, PCI-DSS)

Setting Up ASP.NET Core Identity with MFA

Step 1. Configure Identity in Program.cs

builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
    options.SignIn.RequireConfirmedEmail = true;
    options.Tokens.AuthenticatorTokenProvider = TokenOptions.DefaultAuthenticatorProvider;

    options.Lockout.MaxFailedAccessAttempts = 5;
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);

    options.Password.RequiredLength = 8;
    options.Password.RequireDigit = true;
    options.Password.RequireUppercase = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

Step 2. Enable Two-Factor Authentication in User Profile

ASP.NET Core Identity has built-in APIs to manage 2FA.

public class ManageController : Controller
{
    private readonly UserManager<ApplicationUser> _userManager;

    public ManageController(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }

    [HttpGet]
    public async Task<IActionResult> Enable2FA()
    {
        var user = await _userManager.GetUserAsync(User);
        var is2faEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
        return View(new Enable2FAViewModel { Is2FAEnabled = is2faEnabled });
    }

    [HttpPost]
    public async Task<IActionResult> Enable2FA(bool enable)
    {
        var user = await _userManager.GetUserAsync(User);
        await _userManager.SetTwoFactorEnabledAsync(user, enable);
        return RedirectToAction("Index", "Home");
    }
}

Step 3. Using an Authenticator App (TOTP)

Users can scan a QR code with Google Authenticator or Microsoft Authenticator.

[HttpGet]
public async Task<IActionResult> SetupAuthenticator()
{
    var user = await _userManager.GetUserAsync(User);
    var authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user);

    if (string.IsNullOrEmpty(authenticatorKey))
    {
        await _userManager.ResetAuthenticatorKeyAsync(user);
        authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user);
    }

    // Generate QR Code for authenticator apps
    var qrCodeUri = $"otpauth://totp/{_urlEncoder.Encode("MyApp")}:{_urlEncoder.Encode(user.Email)}?secret={authenticatorKey}&issuer=MyApp";
    
    return View(new SetupAuthenticatorViewModel
    {
        AuthenticatorKey = authenticatorKey,
        QrCodeUri = qrCodeUri
    });
}

Step 4. Verifying 2FA Codes During Login

[HttpPost]
public async Task<IActionResult> Verify2FA(string code, bool rememberMe)
{
    var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(code, rememberMe, rememberClient: false);

    if (result.Succeeded)
        return RedirectToAction("Index", "Home");

    if (result.IsLockedOut)
        return View("Lockout");

    ModelState.AddModelError("", "Invalid authentication code.");
    return View();
}

Step 5. SMS or Email as a Second Factor

Instead of authenticator apps, you can also configure SMS or email providers.

var code = await _userManager.GenerateTwoFactorTokenAsync(user, "Email");
await _emailSender.SendEmailAsync(user.Email, "Security Code", $"Your code is {code}");

Then verify the code at login:

var result = await _signInManager.TwoFactorSignInAsync("Email", code, false, false);

Best Practices for MFA in ASP.NET Core

  1. Use Authenticator Apps (TOTP) instead of SMS where possible
    SMS can be intercepted via SIM swapping attacks.

  2. Allow “Remember this device” option
    Reduce friction by remembering trusted devices for a period of time.

  3. Backup Codes
    Provide one-time recovery codes for users in case they lose their phone.

    var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 5);
    
  4. Secure Token Providers
    Use DataProtectionTokenProvider with strong keys stored in a secure vault (Azure Key Vault, AWS KMS, etc.).

  5. Enforce MFA for Admin Roles
    Always require MFA for privileged accounts.

  6. Monitor Failed Attempts
    Lock accounts temporarily after multiple failed 2FA attempts.

Conclusion

Enabling Multi-Factor Authentication (MFA) in ASP.NET Core applications significantly strengthens security and reduces risks of unauthorized access. With ASP.NET Core Identity’s built-in support, implementing MFA using authenticator apps, SMS, or email is straightforward.

By following best practices—such as preferring authenticator apps over SMS, offering recovery codes, and enforcing MFA for sensitive accounts—you can provide a stronger security posture while maintaining a good user experience.