Freigeben über


Multi-Faktor-Authentifizierung in ASP.NET Core

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Wichtig

Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.

Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Von Damien Bowden

Beispielcode anzeigen oder herunterladen (Repository: damienbod/AspNetCoreHybridFlowWithApi GitHub)

Die Multi-Faktor-Authentifizierung (MFA) ist ein Prozess, in dem Benutzer*innen während eines Anmeldevorgangs aufgefordert werden, weitere Formen der Identifizierung zu verwenden. Dabei können Benutzer*innen aufgefordert werden, einen Code von einem Mobiltelefon einzugeben, einen FIDO2-Schlüssel zu verwenden oder einen Fingerabdruck zu scannen. Durch Anfordern einer zweiten Form der Authentifizierung wird die Sicherheit erhöht. Der zusätzliche Faktor kann von Angreifer*innen nicht ohne Weiteres abgerufen oder dupliziert werden.

In diesem Artikel werden die folgenden Themenbereiche behandelt:

  • Was ist MFA und welche MFA-Flows werden empfohlen?
  • Konfigurieren von MFA für Verwaltungsseiten mit ASP.NET Core Identity
  • Senden der MFA-Anmeldeanforderung an den OpenID Connect-Server
  • Erzwingen, dass der ASP.NET Core-OpenID Connect-Client MFA anfordert

MFA, 2FA

MFA erfordert mindestens zwei Arten von Identitätsnachweisen. Das kann etwas sein, das Sie kennen, etwas, das Sie besitzen, oder eine Validierung durch biometrische Faktoren.

Die Zwei-Faktor-Authentifizierung (2FA) ist im Grunde eine Untergruppe der Multi-Faktor-Authentifizierung (MFA). Der Unterschied besteht darin, dass die MFA zwei oder mehr Faktoren zum Nachweis der Identität erfordern kann.

Die 2FA wird bei Verwendung von ASP.NET Core Identity standardmäßig unterstützt. Um die 2FA für bestimmte Benutzer*innen zu aktivieren oder zu deaktivieren, legen Sie die Eigenschaft IdentityUser<TKey>.TwoFactorEnabled fest. Die Standardbenutzeroberfläche von ASP.NET Core Identity enthält Seiten zum Konfigurieren der 2FA.

MFA mit TOTP-Algorithmus

Die MFA mit TOTP-Algorithmus (Time-based One-time Password, zeitbasiertes Einmalkennwort) wird bei Verwendung von ASP.NET Core Identity standardmäßig unterstützt. Dieser Ansatz kann zusammen mit jeder kompatiblen Authentifikator-App verwendet werden, einschließlich diesen:

  • Microsoft Authenticator
  • Google Authenticator

Details zur Implementierung finden Sie unter Aktivieren der QR-Code-Generierung für TOTP-Authentifikator-Apps in ASP.NET Core.

Um die Unterstützung von MFA mit TOTP zu deaktivieren, konfigurieren Sie die Authentifizierung mit AddIdentity anstatt AddDefaultIdentity. AddDefaultIdentity ruft AddDefaultTokenProviders intern auf, wodurch mehrere Tokenanbieter registriert werden, darunter einer für MFA mit TOTP. Wenn Sie nur bestimmte Tokenanbieter registrieren möchten, rufen Sie AddTokenProvider für jeden gewünschten Anbieter auf. Weitere Informationen zu verfügbaren Tokenanbietern finden Sie in der GitHub-Quelle für AddDefaultTokenProviders.

MFA-Hauptschlüssel/FIDO2 oder kennwortlos

Für Hauptschlüssel/FIDO2 gilt derzeit:

  • Es ist die sicherste Möglichkeit zur Umsetzung der MFA.
  • Die MFA schützt vor Phishingangriffen. (Ebenso wie die zertifikatbasierte Authentifizierung und Windows for Business.)

Derzeit unterstützt ASP.NET Core Hauptschlüssel/FIDO2 nicht direkt. Hauptschlüssel/FIDO2 kann für MFA oder kennwortlose Flows verwendet werden.

Microsoft Entra ID bietet Unterstützung für Hauptschlüssel/FIDO2 und kennwortlose Flows. Weitere Informationen finden Sie unter Optionen für die kennwortlose Authentifizierung.

Andere Formen der kennwortlosen MFA schützen möglicherweise nicht vor Phishing.

MFA mit SMS

MFA mit SMS erhöht die Sicherheit im Vergleich zur Kennwortauthentifizierung (Einzelfaktor) erheblich. Die Verwendung von SMS als zweitem Faktor wird jedoch nicht mehr empfohlen. Für diese Art der Implementierung gibt es zu viele bekannte Angriffsvektoren.

NIST-Richtlinien

Konfigurieren von MFA für Verwaltungsseiten mit ASP.NET Core Identity

Beim Zugriff von Benutzer*innen auf vertrauliche Seiten innerhalb einer ASP.NET Core Identity-App kann die MFA erzwungen werden. Das kann für Apps nützlich sein, bei denen unterschiedliche Zugriffsebenen für die verschiedenen Identitäten vorhanden sind. Beispielsweise können Benutzer*innen mit einer einfachen Kennwortanmeldung Profildaten anzeigen, aber Administrator*innen müssen eine MFA-Methode verwenden, um auf Verwaltungsseiten zuzugreifen.

Erweitern der Anmeldung mit einem MFA-Anspruch

Die Erstellung des Democode erfolgte unter Verwendung von ASP.NET Core mit Identity und Razor Pages. Anstelle einer AddDefaultIdentity-Methode wird die AddIdentity-Methode verwendet, sodass eine IUserClaimsPrincipalFactory-Implementierung verwendet werden kann, um nach einer erfolgreichen Anmeldung Ansprüche zur Identität hinzuzufügen.

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(
        Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
		options.SignIn.RequireConfirmedAccount = false)
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

builder.Services.AddSingleton<IEmailSender, EmailSender>();
builder.Services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>, 
    AdditionalUserClaimsPrincipalFactory>();

builder.Services.AddAuthorization(options =>
    options.AddPolicy("TwoFactorEnabled", x => x.RequireClaim("amr", "mfa")));

builder.Services.AddRazorPages();

Die AdditionalUserClaimsPrincipalFactory-Klasse fügt den amr-Anspruch erst nach erfolgreicher Anmeldung zu den Benutzeransprüchen hinzu. Der Wert des Anspruchs wird aus der Datenbank gelesen. Der Anspruch wird hier hinzugefügt, da Benutzer*innen nur auf eine auf höherer Ebene geschützte Ansicht zugreifen dürfen, wenn sich die Identität per MFA angemeldet hat. Wenn anstatt der Verwendung des Anspruchs die Datenbankansicht direkt aus der Datenbank gelesen wird, ist ein Zugriff auf die Ansicht ohne MFA direkt nach der Aktivierung der MFA möglich.

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;

namespace IdentityStandaloneMfa
{
    public class AdditionalUserClaimsPrincipalFactory : 
        UserClaimsPrincipalFactory<IdentityUser, IdentityRole>
    {
        public AdditionalUserClaimsPrincipalFactory( 
            UserManager<IdentityUser> userManager,
            RoleManager<IdentityRole> roleManager, 
            IOptions<IdentityOptions> optionsAccessor) 
            : base(userManager, roleManager, optionsAccessor)
        {
        }

        public async override Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
        {
            var principal = await base.CreateAsync(user);
            var identity = (ClaimsIdentity)principal.Identity;

            var claims = new List<Claim>();

            if (user.TwoFactorEnabled)
            {
                claims.Add(new Claim("amr", "mfa"));
            }
            else
            {
                claims.Add(new Claim("amr", "pwd"));
            }

            identity.AddClaims(claims);
            return principal;
        }
    }
}

Da sich das Setup des Identity-Diensts in der Startup-Klasse geändert hat, müssen die Layouts von Identity aktualisiert werden. Erstellen Sie ein Gerüst für die Identity-Seiten in der App. Definieren Sie das Layout in der Identity/Account/Manage/_Layout.cshtml-Datei.

@{
    Layout = "/Pages/Shared/_Layout.cshtml";
}

Weisen Sie außerdem das Layout für alle Verwaltungsseiten von den Identity-Seiten zu:

@{
    Layout = "_Layout.cshtml";
}

Überprüfen der MFA-Anforderung auf der Verwaltungsseite

Die Razor Page für die Verwaltung überprüft, ob sich der Benutzer oder die Benutzerin per MFA angemeldet hat. In der OnGet-Methode wird die Identität für den Zugriff auf die Benutzeransprüche verwendet. Im amr-Anspruch wird nach dem Wert mfa gesucht. Wenn diese Anspruch in der Identität fehlt oder die Identität false ist, erfolgt eine Weiterleitung von dieser Seite an die Seite „MFA aktivieren“. Dies ist möglich, da sich der Benutzer bzw. die Benutzerin bereits angemeldet hat, jedoch ohne MFA.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace IdentityStandaloneMfa
{
    public class AdminModel : PageModel
    {
        public IActionResult OnGet()
        {
            var claimTwoFactorEnabled = 
                User.Claims.FirstOrDefault(t => t.Type == "amr");

            if (claimTwoFactorEnabled != null && 
                "mfa".Equals(claimTwoFactorEnabled.Value))
            {
                // You logged in with MFA, do the administrative stuff
            }
            else
            {
                return Redirect(
                    "/Identity/Account/Manage/TwoFactorAuthentication");
            }

            return Page();
        }
    }
}

Benutzeroberflächenlogik zum Umschalten von Benutzeranmeldeinformationen

Beim Start wurde eine Autorisierungsrichtlinie hinzugefügt. Für diese Richtlinie muss der amr-Anspruch mit dem Wert mfa vorhanden sein.

services.AddAuthorization(options =>
    options.AddPolicy("TwoFactorEnabled",
        x => x.RequireClaim("amr", "mfa")));

Diese Richtlinie kann dann in der _Layout-Ansicht verwendet werden, um das Menü Admin mit folgender Warnung anzuzeigen oder nicht anzuzeigen:

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
@inject IAuthorizationService AuthorizationService

Wenn sich die Identität per MFA angemeldet hat, wird das Menü Admin ohne QuickInfo mit einer Warnung angezeigt. Wenn sich der Benutzer oder die Benutzerin ohne MFA angemeldet hat, wird das Menü Admin (nicht aktiviert) sowie eine QuickInfo angezeigt, die den Benutzer bzw. die Benutzerin informiert und die Warnung erläutert.

@if (SignInManager.IsSignedIn(User))
{
    @if ((AuthorizationService.AuthorizeAsync(User, "TwoFactorEnabled")).Result.Succeeded)
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Admin">Admin</a>
        </li>
    }
    else
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Admin" 
               id="tooltip-demo"  
               data-toggle="tooltip" 
               data-placement="bottom" 
               title="MFA is NOT enabled. This is required for the Admin Page. If you have activated MFA, then logout, login again.">
                Admin (Not Enabled)
            </a>
        </li>
    }
}

Wenn sich der Benutzer oder die Benutzerin ohne MFA anmeldet, wird die Warnung angezeigt:

Multi-Faktor-Authentifizierung für Administrator*innen

Benutzer*innen werden zur Ansicht zur Aktivierung der MFA weitergeleitet, wenn sie auf den Link Admin klicken:

Administrator aktiviert Multi-Faktor-Authentifizierung

Senden der MFA-Anmeldeanforderung an den OpenID Connect-Server

Der Parameter acr_values kann verwendet werden, um den erforderlichen mfa-Wert in einer Authentifizierungsanforderung vom Client an den Server zu übergeben.

Hinweis

Der Parameter acr_values muss auf dem OpenID Connect-Server verarbeitet werden, damit dies funktioniert.

OpenID Connect-ASP.NET Core Client

Die ASP.NET Core Razor Pages-OpenID Connect-Client-App verwendet die Methode AddOpenIdConnect zur Anmeldung beim OpenID Connect-Server. Der Parameter acr_values wird mit dem Wert mfa festgelegt und mit der Authentifizierungsanforderung gesendet. Zum Hinzufügen wird OpenIdConnectEvents verwendet.

Informationen zu empfohlenen Werten für den Parameter acr_values finden Sie unter Authentication Method Reference Values (Referenzwerte für Authentifizierungsmethoden).

build.Services.AddAuthentication(options =>
{
	options.DefaultScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.DefaultChallengeScheme =
		OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
	options.SignInScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.Authority = "<OpenID Connect server URL>";
	options.RequireHttpsMetadata = true;
	options.ClientId = "<OpenID Connect client ID>";
	options.ClientSecret = "<>";
	options.ResponseType = "code";
	options.UsePkce = true;	
	options.Scope.Add("profile");
	options.Scope.Add("offline_access");
	options.SaveTokens = true;
	options.AdditionalAuthorizationParameters.Add("acr_values", "mfa");
});

Beispiel für OpenID Connect Duende IdentityServer-Server mit ASP.NET Core Identity

Auf dem OpenID Connect-Server, der mithilfe von ASP.NET Core Identity mit Razor Pages implementiert wird, wird eine neue Ansicht namens ErrorEnable2FA.cshtml erstellt. Für diese Ansicht gilt Folgendes:

  • Sie wird angezeigt, wenn die Identity von einer App stammt, die MFA erfordert, aber der Benutzer oder die Benutzerin dies in Identity nicht aktiviert hat.
  • Sie informiert den Benutzer oder die Benutzerin und fügt einen Link zum Aktivieren hinzu.
@{
    ViewData["Title"] = "ErrorEnable2FA";
}

<h1>The client application requires you to have MFA enabled. Enable this, try login again.</h1>

<br />

You can enable MFA to login here:

<br />

<a href="~/Identity/Account/Manage/TwoFactorAuthentication">Enable MFA</a>

In der Login-Methode wird die IIdentityServerInteractionService-Schnittstellenimplementierung _interaction verwendet, um auf die OpenID Connect-Anforderungsparameter zuzugreifen. Auf den Parameter acr_values wird mithilfe der Eigenschaft AcrValues zugegriffen. Da der Client dies mit festgelegtem mfa-Wert gesendet hat, kann dies dann überprüft werden.

Wenn die MFA erforderlich ist und der Benutzer oder die Benutzer in ASP.NET Core Identity die MFA aktiviert hat, wird die Anmeldung fortgesetzt. Wenn die MFA nicht aktiviert ist, wird der Benutzer oder die Benutzerin an die benutzerdefinierte Ansicht ErrorEnable2FA.cshtml umgeleitet. Dann meldet ASP.NET Core Identity den Benutzer oder die Benutzerin an.

Fido2Store wird verwendet, um zu überprüfen, ob der Benutzer oder die Benutzerin die MFA mithilfe eines benutzerdefinierten FIDO2-Tokenanbieters aktiviert hat.

public async Task<IActionResult> OnPost()
{
	// check if we are in the context of an authorization request
	var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl);

	var requires2Fa = context?.AcrValues.Count(t => t.Contains("mfa")) >= 1;

	var user = await _userManager.FindByNameAsync(Input.Username);
	if (user != null && !user.TwoFactorEnabled && requires2Fa)
	{
		return RedirectToPage("/Home/ErrorEnable2FA/Index");
	}

	// code omitted for brevity

	if (ModelState.IsValid)
	{
		var result = await _signInManager.PasswordSignInAsync(Input.Username, Input.Password, Input.RememberLogin, lockoutOnFailure: true);
		if (result.Succeeded)
		{
			// code omitted for brevity
		}
		if (result.RequiresTwoFactor)
		{
			var fido2ItemExistsForUser = await _fido2Store.GetCredentialsByUserNameAsync(user.UserName);
			if (fido2ItemExistsForUser.Count > 0)
			{
				return RedirectToPage("/Account/LoginFido2Mfa", new { area = "Identity", Input.ReturnUrl, Input.RememberLogin });
			}

			return RedirectToPage("/Account/LoginWith2fa", new { area = "Identity", Input.ReturnUrl, RememberMe = Input.RememberLogin });
		}

		await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, "invalid credentials", clientId: context?.Client.ClientId));
		ModelState.AddModelError(string.Empty, LoginOptions.InvalidCredentialsErrorMessage);
	}

	// something went wrong, show form with error
	await BuildModelAsync(Input.ReturnUrl);
	return Page();
}

Wenn der Benutzer oder die Benutzerin bereits angemeldet ist, gilt für die Client-App Folgendes:

  • Sie überprüft dennoch den amr-Anspruch.
  • Sie kann die MFA mit einem Link zur ASP.NET Core Identity-Ansicht einrichten.

acr_values-1 image

Erzwingen, dass der ASP.NET Core-OpenID Connect-Client MFA anfordert

Dieses Beispiel zeigt, wie eine ASP.NET Core Razor Page-App, die OpenID Connect für die Anmeldung verwendet, erfordern kann, dass Benutzer*innen sich mit MFA authentifiziert haben.

Zum Überprüfen der MFA-Anforderung wird eine IAuthorizationRequirement-Anforderung erstellt. Diese wird den Seiten mithilfe einer Richtlinie hinzugefügt, die MFA erfordert.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc;

public class RequireMfa : IAuthorizationRequirement{}

Ein AuthorizationHandler wird implementiert, der den amr-Anspruch verwendet und nach dem Wert mfa sucht. Der amr-Anspruch wird im id_token einer erfolgreichen Authentifizierung zurückgegeben und kann viele verschiedene Werte aufweisen, wie in der Spezifikation Authentication Method Reference Values (Referenzwerte für Authentifizierungsmethoden) definiert.

Der zurückgegebene Wert hängt von der Art der Authentifizierung der Identität und der OpenID Connect-Serverimplementierung ab.

Der AuthorizationHandler verwendet die Anforderung RequireMfa und überprüft den amr-Anspruch. Der OpenID Connect-Server kann mithilfe von Duende Identity Server mit ASP.NET Core Identity implementiert werden. Wenn Benutzer*innen sich mithilfe von TOTP anmelden, wird der amr-Anspruch mit einem MFA-Wert zurückgegeben. Bei Verwendung einer anderen OpenID Connect-Serverimplementierung oder eines anderen MFA-Typs kann der amr-Anspruch einen anderen Wert aufweisen. Der Code muss erweitert werden, um dies ebenfalls zu akzeptieren.

public class RequireMfaHandler : AuthorizationHandler<RequireMfa>
{
	protected override Task HandleRequirementAsync(
		AuthorizationHandlerContext context, 
		RequireMfa requirement)
	{
		if (context == null)
			throw new ArgumentNullException(nameof(context));
		if (requirement == null)
			throw new ArgumentNullException(nameof(requirement));

		var amrClaim =
			context.User.Claims.FirstOrDefault(t => t.Type == "amr");

		if (amrClaim != null && amrClaim.Value == Amr.Mfa)
		{
			context.Succeed(requirement);
		}

		return Task.CompletedTask;
	}
}

In der Programmdatei wird die AddOpenIdConnect-Methode als standardmäßiges Aufforderungsschema verwendet. Der zum Überprüfen des amr-Anspruchs verwendete Autorisierungshandler wird dem Inversion of Control-Container hinzugefügt. Dann wird eine Richtlinie erstellt, die die RequireMfa-Anforderung hinzufügt.

builder.Services.ConfigureApplicationCookie(options =>
        options.Cookie.SecurePolicy =
            CookieSecurePolicy.Always);

builder.Services.AddSingleton<IAuthorizationHandler, RequireMfaHandler>();

builder.Services.AddAuthentication(options =>
{
	options.DefaultScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.DefaultChallengeScheme =
		OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
	options.SignInScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.Authority = "https://localhost:44352";
	options.RequireHttpsMetadata = true;
	options.ClientId = "AspNetCoreRequireMfaOidc";
	options.ClientSecret = "AspNetCoreRequireMfaOidcSecret";
	options.ResponseType = "code";
	options.UsePkce = true;	
	options.Scope.Add("profile");
	options.Scope.Add("offline_access");
	options.SaveTokens = true;
});

builder.Services.AddAuthorization(options =>
{
	options.AddPolicy("RequireMfa", policyIsAdminRequirement =>
	{
		policyIsAdminRequirement.Requirements.Add(new RequireMfa());
	});
});

builder.Services.AddRazorPages();

Diese Richtlinie wird dann nach Bedarf auf der Razor Page verwendet. Die Richtlinie kann auch global für die gesamte App hinzugefügt werden.

[Authorize(Policy= "RequireMfa")]
public class IndexModel : PageModel
{
    public void OnGet()
    {
    }
}

Wenn Benutzer*innen sich ohne MFA authentifizieren, weist der amr-Anspruch möglicherweise einen Wert für pwd auf. Die Anforderung wird nicht für den Zugriff auf die Seite autorisiert. Bei Verwendung der Standardwerte werden Benutzer*innen an die Seite Account/AccessDenied umgeleitet. Dieses Verhalten kann geändert werden. Sie können hier auch Ihre eigene benutzerdefinierte Logik implementieren. In diesem Beispiel wird ein Link hinzugefügt, damit gültige Benutzer*innen die MFA für ihr Konto einrichten können.

@page
@model AspNetCoreRequireMfaOidc.AccessDeniedModel
@{
    ViewData["Title"] = "AccessDenied";
    Layout = "~/Pages/Shared/_Layout.cshtml";
}

<h1>AccessDenied</h1>

You require MFA to login here

<a href="https://localhost:44352/Manage/TwoFactorAuthentication">Enable MFA</a>

Jetzt können nur Benutzer*innen, die sich per MFA authentifizieren, auf die Seite oder Website zugreifen. Wenn andere MFA-Typen verwendet werden oder die 2FA akzeptabel ist, weist der amr-Anspruch unterschiedliche Werte auf und muss korrekt verarbeitet werden. Verschiedene OpenID Connect-Server geben ebenfalls verschiedene Werte für diesen Anspruch zurück und folgen möglicherweise nicht der Spezifikation unter Authentication Method Reference Values (Referenzwerte für Authentifizierungsmethoden).

Bei Anmeldung ohne MFA (beispielsweise nur durch Verwendung eines Kennworts):

  • amr weist den Wert pwd auf:

    amr weist den Wert „pwd“ auf

  • Der Zugriff wird verweigert:

    Zugriff verweigert

Alternativ können Sie sich über OTP mit Identity anmelden:

Anmelden über OTP mit Identity

Zusätzliche Ressourcen

Von Damien Bowden

Beispielcode anzeigen oder herunterladen (Repository: damienbod/AspNetCoreHybridFlowWithApi GitHub)

Die Multi-Faktor-Authentifizierung (MFA) ist ein Prozess, in dem Benutzer*innen während eines Anmeldevorgangs aufgefordert werden, weitere Formen der Identifizierung zu verwenden. Dabei können Benutzer*innen aufgefordert werden, einen Code von einem Mobiltelefon einzugeben, einen FIDO2-Schlüssel zu verwenden oder einen Fingerabdruck zu scannen. Durch Anfordern einer zweiten Form der Authentifizierung wird die Sicherheit erhöht. Der zusätzliche Faktor kann von Angreifer*innen nicht ohne Weiteres abgerufen oder dupliziert werden.

In diesem Artikel werden die folgenden Themenbereiche behandelt:

  • Was ist MFA und welche MFA-Flows werden empfohlen?
  • Konfigurieren von MFA für Verwaltungsseiten mit ASP.NET Core Identity
  • Senden der MFA-Anmeldeanforderung an den OpenID Connect-Server
  • Erzwingen, dass der ASP.NET Core-OpenID Connect-Client MFA anfordert

MFA, 2FA

MFA erfordert mindestens zwei Arten von Identitätsnachweisen. Das kann etwas sein, das Sie kennen, etwas, das Sie besitzen, oder eine Validierung durch biometrische Faktoren.

Die Zwei-Faktor-Authentifizierung (2FA) ist im Grunde eine Untergruppe der Multi-Faktor-Authentifizierung (MFA). Der Unterschied besteht darin, dass die MFA zwei oder mehr Faktoren zum Nachweis der Identität erfordern kann.

Die 2FA wird bei Verwendung von ASP.NET Core Identity standardmäßig unterstützt. Um die 2FA für bestimmte Benutzer*innen zu aktivieren oder zu deaktivieren, legen Sie die Eigenschaft IdentityUser<TKey>.TwoFactorEnabled fest. Die Standardbenutzeroberfläche von ASP.NET Core Identity enthält Seiten zum Konfigurieren der 2FA.

MFA mit TOTP-Algorithmus

Die MFA mit TOTP-Algorithmus (Time-based One-time Password, zeitbasiertes Einmalkennwort) wird bei Verwendung von ASP.NET Core Identity standardmäßig unterstützt. Dieser Ansatz kann zusammen mit jeder kompatiblen Authentifikator-App verwendet werden, einschließlich diesen:

  • Microsoft Authenticator
  • Google Authenticator

Details zur Implementierung finden Sie unter Aktivieren der QR-Code-Generierung für TOTP-Authentifikator-Apps in ASP.NET Core.

Um die Unterstützung von MFA mit TOTP zu deaktivieren, konfigurieren Sie die Authentifizierung mit AddIdentity anstatt AddDefaultIdentity. AddDefaultIdentity ruft AddDefaultTokenProviders intern auf, wodurch mehrere Tokenanbieter registriert werden, darunter einer für MFA mit TOTP. Wenn Sie nur bestimmte Tokenanbieter registrieren möchten, rufen Sie AddTokenProvider für jeden gewünschten Anbieter auf. Weitere Informationen zu verfügbaren Tokenanbietern finden Sie in der GitHub-Quelle für AddDefaultTokenProviders.

MFA-Hauptschlüssel/FIDO2 oder kennwortlos

Für Hauptschlüssel/FIDO2 gilt derzeit:

  • Es ist die sicherste Möglichkeit zur Umsetzung der MFA.
  • Die MFA schützt vor Phishingangriffen. (Ebenso wie die zertifikatbasierte Authentifizierung und Windows for Business.)

Derzeit unterstützt ASP.NET Core Hauptschlüssel/FIDO2 nicht direkt. Hauptschlüssel/FIDO2 kann für MFA oder kennwortlose Flows verwendet werden.

Microsoft Entra ID bietet Unterstützung für Hauptschlüssel/FIDO2 und kennwortlose Flows. Weitere Informationen finden Sie unter Optionen für die kennwortlose Authentifizierung.

Andere Formen der kennwortlosen MFA schützen möglicherweise nicht vor Phishing.

MFA mit SMS

MFA mit SMS erhöht die Sicherheit im Vergleich zur Kennwortauthentifizierung (Einzelfaktor) erheblich. Die Verwendung von SMS als zweitem Faktor wird jedoch nicht mehr empfohlen. Für diese Art der Implementierung gibt es zu viele bekannte Angriffsvektoren.

NIST-Richtlinien

Konfigurieren von MFA für Verwaltungsseiten mit ASP.NET Core Identity

Beim Zugriff von Benutzer*innen auf vertrauliche Seiten innerhalb einer ASP.NET Core Identity-App kann die MFA erzwungen werden. Das kann für Apps nützlich sein, bei denen unterschiedliche Zugriffsebenen für die verschiedenen Identitäten vorhanden sind. Beispielsweise können Benutzer*innen mit einer einfachen Kennwortanmeldung Profildaten anzeigen, aber Administrator*innen müssen eine MFA-Methode verwenden, um auf Verwaltungsseiten zuzugreifen.

Erweitern der Anmeldung mit einem MFA-Anspruch

Die Erstellung des Democode erfolgte unter Verwendung von ASP.NET Core mit Identity und Razor Pages. Anstelle einer AddDefaultIdentity-Methode wird die AddIdentity-Methode verwendet, sodass eine IUserClaimsPrincipalFactory-Implementierung verwendet werden kann, um nach einer erfolgreichen Anmeldung Ansprüche zur Identität hinzuzufügen.

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(
        Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
		options.SignIn.RequireConfirmedAccount = false)
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

builder.Services.AddSingleton<IEmailSender, EmailSender>();
builder.Services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>, 
    AdditionalUserClaimsPrincipalFactory>();

builder.Services.AddAuthorization(options =>
    options.AddPolicy("TwoFactorEnabled", x => x.RequireClaim("amr", "mfa")));

builder.Services.AddRazorPages();

Die AdditionalUserClaimsPrincipalFactory-Klasse fügt den amr-Anspruch erst nach erfolgreicher Anmeldung zu den Benutzeransprüchen hinzu. Der Wert des Anspruchs wird aus der Datenbank gelesen. Der Anspruch wird hier hinzugefügt, da Benutzer*innen nur auf eine auf höherer Ebene geschützte Ansicht zugreifen dürfen, wenn sich die Identität per MFA angemeldet hat. Wenn anstatt der Verwendung des Anspruchs die Datenbankansicht direkt aus der Datenbank gelesen wird, ist ein Zugriff auf die Ansicht ohne MFA direkt nach der Aktivierung der MFA möglich.

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;

namespace IdentityStandaloneMfa
{
    public class AdditionalUserClaimsPrincipalFactory : 
        UserClaimsPrincipalFactory<IdentityUser, IdentityRole>
    {
        public AdditionalUserClaimsPrincipalFactory( 
            UserManager<IdentityUser> userManager,
            RoleManager<IdentityRole> roleManager, 
            IOptions<IdentityOptions> optionsAccessor) 
            : base(userManager, roleManager, optionsAccessor)
        {
        }

        public async override Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
        {
            var principal = await base.CreateAsync(user);
            var identity = (ClaimsIdentity)principal.Identity;

            var claims = new List<Claim>();

            if (user.TwoFactorEnabled)
            {
                claims.Add(new Claim("amr", "mfa"));
            }
            else
            {
                claims.Add(new Claim("amr", "pwd"));
            }

            identity.AddClaims(claims);
            return principal;
        }
    }
}

Da sich das Setup des Identity-Diensts in der Startup-Klasse geändert hat, müssen die Layouts von Identity aktualisiert werden. Erstellen Sie ein Gerüst für die Identity-Seiten in der App. Definieren Sie das Layout in der Identity/Account/Manage/_Layout.cshtml-Datei.

@{
    Layout = "/Pages/Shared/_Layout.cshtml";
}

Weisen Sie außerdem das Layout für alle Verwaltungsseiten von den Identity-Seiten zu:

@{
    Layout = "_Layout.cshtml";
}

Überprüfen der MFA-Anforderung auf der Verwaltungsseite

Die Razor Page für die Verwaltung überprüft, ob sich der Benutzer oder die Benutzerin per MFA angemeldet hat. In der OnGet-Methode wird die Identität für den Zugriff auf die Benutzeransprüche verwendet. Im amr-Anspruch wird nach dem Wert mfa gesucht. Wenn diese Anspruch in der Identität fehlt oder die Identität false ist, erfolgt eine Weiterleitung von dieser Seite an die Seite „MFA aktivieren“. Dies ist möglich, da sich der Benutzer bzw. die Benutzerin bereits angemeldet hat, jedoch ohne MFA.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace IdentityStandaloneMfa
{
    public class AdminModel : PageModel
    {
        public IActionResult OnGet()
        {
            var claimTwoFactorEnabled = 
                User.Claims.FirstOrDefault(t => t.Type == "amr");

            if (claimTwoFactorEnabled != null && 
                "mfa".Equals(claimTwoFactorEnabled.Value))
            {
                // You logged in with MFA, do the administrative stuff
            }
            else
            {
                return Redirect(
                    "/Identity/Account/Manage/TwoFactorAuthentication");
            }

            return Page();
        }
    }
}

Benutzeroberflächenlogik zum Umschalten von Benutzeranmeldeinformationen

Beim Start wurde eine Autorisierungsrichtlinie hinzugefügt. Für diese Richtlinie muss der amr-Anspruch mit dem Wert mfa vorhanden sein.

services.AddAuthorization(options =>
    options.AddPolicy("TwoFactorEnabled",
        x => x.RequireClaim("amr", "mfa")));

Diese Richtlinie kann dann in der _Layout-Ansicht verwendet werden, um das Menü Admin mit folgender Warnung anzuzeigen oder nicht anzuzeigen:

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
@inject IAuthorizationService AuthorizationService

Wenn sich die Identität per MFA angemeldet hat, wird das Menü Admin ohne QuickInfo mit einer Warnung angezeigt. Wenn sich der Benutzer oder die Benutzerin ohne MFA angemeldet hat, wird das Menü Admin (nicht aktiviert) sowie eine QuickInfo angezeigt, die den Benutzer bzw. die Benutzerin informiert und die Warnung erläutert.

@if (SignInManager.IsSignedIn(User))
{
    @if ((AuthorizationService.AuthorizeAsync(User, "TwoFactorEnabled")).Result.Succeeded)
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Admin">Admin</a>
        </li>
    }
    else
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Admin" 
               id="tooltip-demo"  
               data-toggle="tooltip" 
               data-placement="bottom" 
               title="MFA is NOT enabled. This is required for the Admin Page. If you have activated MFA, then logout, login again.">
                Admin (Not Enabled)
            </a>
        </li>
    }
}

Wenn sich der Benutzer oder die Benutzerin ohne MFA anmeldet, wird die Warnung angezeigt:

Multi-Faktor-Authentifizierung für Administrator*innen

Benutzer*innen werden zur Ansicht zur Aktivierung der MFA weitergeleitet, wenn sie auf den Link Admin klicken:

Administrator aktiviert Multi-Faktor-Authentifizierung

Senden der MFA-Anmeldeanforderung an den OpenID Connect-Server

Der Parameter acr_values kann verwendet werden, um den erforderlichen mfa-Wert in einer Authentifizierungsanforderung vom Client an den Server zu übergeben.

Hinweis

Der Parameter acr_values muss auf dem OpenID Connect-Server verarbeitet werden, damit dies funktioniert.

OpenID Connect-ASP.NET Core Client

Die ASP.NET Core Razor Pages-OpenID Connect-Client-App verwendet die Methode AddOpenIdConnect zur Anmeldung beim OpenID Connect-Server. Der Parameter acr_values wird mit dem Wert mfa festgelegt und mit der Authentifizierungsanforderung gesendet. Zum Hinzufügen wird OpenIdConnectEvents verwendet.

Informationen zu empfohlenen Werten für den Parameter acr_values finden Sie unter Authentication Method Reference Values (Referenzwerte für Authentifizierungsmethoden).

build.Services.AddAuthentication(options =>
{
	options.DefaultScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.DefaultChallengeScheme =
		OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
	options.SignInScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.Authority = "<OpenID Connect server URL>";
	options.RequireHttpsMetadata = true;
	options.ClientId = "<OpenID Connect client ID>";
	options.ClientSecret = "<>";
	options.ResponseType = "code";
	options.UsePkce = true;	
	options.Scope.Add("profile");
	options.Scope.Add("offline_access");
	options.SaveTokens = true;
	options.Events = new OpenIdConnectEvents
	{
		OnRedirectToIdentityProvider = context =>
		{
			context.ProtocolMessage.SetParameter("acr_values", "mfa");
			return Task.FromResult(0);
		}
	};
});

Beispiel für OpenID Connect Duende IdentityServer-Server mit ASP.NET Core Identity

Auf dem OpenID Connect-Server, der mithilfe von ASP.NET Core Identity mit Razor Pages implementiert wird, wird eine neue Ansicht namens ErrorEnable2FA.cshtml erstellt. Für diese Ansicht gilt Folgendes:

  • Sie wird angezeigt, wenn die Identity von einer App stammt, die MFA erfordert, aber der Benutzer oder die Benutzerin dies in Identity nicht aktiviert hat.
  • Sie informiert den Benutzer oder die Benutzerin und fügt einen Link zum Aktivieren hinzu.
@{
    ViewData["Title"] = "ErrorEnable2FA";
}

<h1>The client application requires you to have MFA enabled. Enable this, try login again.</h1>

<br />

You can enable MFA to login here:

<br />

<a href="~/Identity/Account/Manage/TwoFactorAuthentication">Enable MFA</a>

In der Login-Methode wird die IIdentityServerInteractionService-Schnittstellenimplementierung _interaction verwendet, um auf die OpenID Connect-Anforderungsparameter zuzugreifen. Auf den Parameter acr_values wird mithilfe der Eigenschaft AcrValues zugegriffen. Da der Client dies mit festgelegtem mfa-Wert gesendet hat, kann dies dann überprüft werden.

Wenn die MFA erforderlich ist und der Benutzer oder die Benutzer in ASP.NET Core Identity die MFA aktiviert hat, wird die Anmeldung fortgesetzt. Wenn die MFA nicht aktiviert ist, wird der Benutzer oder die Benutzerin an die benutzerdefinierte Ansicht ErrorEnable2FA.cshtml umgeleitet. Dann meldet ASP.NET Core Identity den Benutzer oder die Benutzerin an.

Fido2Store wird verwendet, um zu überprüfen, ob der Benutzer oder die Benutzerin die MFA mithilfe eines benutzerdefinierten FIDO2-Tokenanbieters aktiviert hat.

public async Task<IActionResult> OnPost()
{
	// check if we are in the context of an authorization request
	var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl);

	var requires2Fa = context?.AcrValues.Count(t => t.Contains("mfa")) >= 1;

	var user = await _userManager.FindByNameAsync(Input.Username);
	if (user != null && !user.TwoFactorEnabled && requires2Fa)
	{
		return RedirectToPage("/Home/ErrorEnable2FA/Index");
	}

	// code omitted for brevity

	if (ModelState.IsValid)
	{
		var result = await _signInManager.PasswordSignInAsync(Input.Username, Input.Password, Input.RememberLogin, lockoutOnFailure: true);
		if (result.Succeeded)
		{
			// code omitted for brevity
		}
		if (result.RequiresTwoFactor)
		{
			var fido2ItemExistsForUser = await _fido2Store.GetCredentialsByUserNameAsync(user.UserName);
			if (fido2ItemExistsForUser.Count > 0)
			{
				return RedirectToPage("/Account/LoginFido2Mfa", new { area = "Identity", Input.ReturnUrl, Input.RememberLogin });
			}

			return RedirectToPage("/Account/LoginWith2fa", new { area = "Identity", Input.ReturnUrl, RememberMe = Input.RememberLogin });
		}

		await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, "invalid credentials", clientId: context?.Client.ClientId));
		ModelState.AddModelError(string.Empty, LoginOptions.InvalidCredentialsErrorMessage);
	}

	// something went wrong, show form with error
	await BuildModelAsync(Input.ReturnUrl);
	return Page();
}

Wenn der Benutzer oder die Benutzerin bereits angemeldet ist, gilt für die Client-App Folgendes:

  • Sie überprüft dennoch den amr-Anspruch.
  • Sie kann die MFA mit einem Link zur ASP.NET Core Identity-Ansicht einrichten.

acr_values-1 image

Erzwingen, dass der ASP.NET Core-OpenID Connect-Client MFA anfordert

Dieses Beispiel zeigt, wie eine ASP.NET Core Razor Page-App, die OpenID Connect für die Anmeldung verwendet, erfordern kann, dass Benutzer*innen sich mit MFA authentifiziert haben.

Zum Überprüfen der MFA-Anforderung wird eine IAuthorizationRequirement-Anforderung erstellt. Diese wird den Seiten mithilfe einer Richtlinie hinzugefügt, die MFA erfordert.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc;

public class RequireMfa : IAuthorizationRequirement{}

Ein AuthorizationHandler wird implementiert, der den amr-Anspruch verwendet und nach dem Wert mfa sucht. Der amr-Anspruch wird im id_token einer erfolgreichen Authentifizierung zurückgegeben und kann viele verschiedene Werte aufweisen, wie in der Spezifikation Authentication Method Reference Values (Referenzwerte für Authentifizierungsmethoden) definiert.

Der zurückgegebene Wert hängt von der Art der Authentifizierung der Identität und der OpenID Connect-Serverimplementierung ab.

Der AuthorizationHandler verwendet die Anforderung RequireMfa und überprüft den amr-Anspruch. Der OpenID Connect-Server kann mithilfe von Duende Identity Server mit ASP.NET Core Identity implementiert werden. Wenn Benutzer*innen sich mithilfe von TOTP anmelden, wird der amr-Anspruch mit einem MFA-Wert zurückgegeben. Bei Verwendung einer anderen OpenID Connect-Serverimplementierung oder eines anderen MFA-Typs kann der amr-Anspruch einen anderen Wert aufweisen. Der Code muss erweitert werden, um dies ebenfalls zu akzeptieren.

public class RequireMfaHandler : AuthorizationHandler<RequireMfa>
{
	protected override Task HandleRequirementAsync(
		AuthorizationHandlerContext context, 
		RequireMfa requirement)
	{
		if (context == null)
			throw new ArgumentNullException(nameof(context));
		if (requirement == null)
			throw new ArgumentNullException(nameof(requirement));

		var amrClaim =
			context.User.Claims.FirstOrDefault(t => t.Type == "amr");

		if (amrClaim != null && amrClaim.Value == Amr.Mfa)
		{
			context.Succeed(requirement);
		}

		return Task.CompletedTask;
	}
}

In der Programmdatei wird die AddOpenIdConnect-Methode als standardmäßiges Aufforderungsschema verwendet. Der zum Überprüfen des amr-Anspruchs verwendete Autorisierungshandler wird dem Inversion of Control-Container hinzugefügt. Dann wird eine Richtlinie erstellt, die die RequireMfa-Anforderung hinzufügt.

builder.Services.ConfigureApplicationCookie(options =>
        options.Cookie.SecurePolicy =
            CookieSecurePolicy.Always);

builder.Services.AddSingleton<IAuthorizationHandler, RequireMfaHandler>();

builder.Services.AddAuthentication(options =>
{
	options.DefaultScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.DefaultChallengeScheme =
		OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
	options.SignInScheme =
		CookieAuthenticationDefaults.AuthenticationScheme;
	options.Authority = "https://localhost:44352";
	options.RequireHttpsMetadata = true;
	options.ClientId = "AspNetCoreRequireMfaOidc";
	options.ClientSecret = "AspNetCoreRequireMfaOidcSecret";
	options.ResponseType = "code";
	options.UsePkce = true;	
	options.Scope.Add("profile");
	options.Scope.Add("offline_access");
	options.SaveTokens = true;
});

builder.Services.AddAuthorization(options =>
{
	options.AddPolicy("RequireMfa", policyIsAdminRequirement =>
	{
		policyIsAdminRequirement.Requirements.Add(new RequireMfa());
	});
});

builder.Services.AddRazorPages();

Diese Richtlinie wird dann nach Bedarf auf der Razor Page verwendet. Die Richtlinie kann auch global für die gesamte App hinzugefügt werden.

[Authorize(Policy= "RequireMfa")]
public class IndexModel : PageModel
{
    public void OnGet()
    {
    }
}

Wenn Benutzer*innen sich ohne MFA authentifizieren, weist der amr-Anspruch möglicherweise einen Wert für pwd auf. Die Anforderung wird nicht für den Zugriff auf die Seite autorisiert. Bei Verwendung der Standardwerte werden Benutzer*innen an die Seite Account/AccessDenied umgeleitet. Dieses Verhalten kann geändert werden. Sie können hier auch Ihre eigene benutzerdefinierte Logik implementieren. In diesem Beispiel wird ein Link hinzugefügt, damit gültige Benutzer*innen die MFA für ihr Konto einrichten können.

@page
@model AspNetCoreRequireMfaOidc.AccessDeniedModel
@{
    ViewData["Title"] = "AccessDenied";
    Layout = "~/Pages/Shared/_Layout.cshtml";
}

<h1>AccessDenied</h1>

You require MFA to login here

<a href="https://localhost:44352/Manage/TwoFactorAuthentication">Enable MFA</a>

Jetzt können nur Benutzer*innen, die sich per MFA authentifizieren, auf die Seite oder Website zugreifen. Wenn andere MFA-Typen verwendet werden oder die 2FA akzeptabel ist, weist der amr-Anspruch unterschiedliche Werte auf und muss korrekt verarbeitet werden. Verschiedene OpenID Connect-Server geben ebenfalls verschiedene Werte für diesen Anspruch zurück und folgen möglicherweise nicht der Spezifikation unter Authentication Method Reference Values (Referenzwerte für Authentifizierungsmethoden).

Bei Anmeldung ohne MFA (beispielsweise nur durch Verwendung eines Kennworts):

  • amr weist den Wert pwd auf:

    amr weist den Wert „pwd“ auf

  • Der Zugriff wird verweigert:

    Zugriff verweigert

Alternativ können Sie sich über OTP mit Identity anmelden:

Anmelden über OTP mit Identity

Zusätzliche Ressourcen

Von Damien Bowden

Beispielcode anzeigen oder herunterladen (Repository: damienbod/AspNetCoreHybridFlowWithApi GitHub)

Die Multi-Faktor-Authentifizierung (MFA) ist ein Prozess, in dem Benutzer*innen während eines Anmeldevorgangs aufgefordert werden, weitere Formen der Identifizierung zu verwenden. Dabei können Benutzer*innen aufgefordert werden, einen Code von einem Mobiltelefon einzugeben, einen FIDO2-Schlüssel zu verwenden oder einen Fingerabdruck zu scannen. Durch Anfordern einer zweiten Form der Authentifizierung wird die Sicherheit erhöht. Der zusätzliche Faktor kann von Angreifer*innen nicht ohne Weiteres abgerufen oder dupliziert werden.

In diesem Artikel werden die folgenden Themenbereiche behandelt:

  • Was ist MFA und welche MFA-Flows werden empfohlen?
  • Konfigurieren von MFA für Verwaltungsseiten mit ASP.NET Core Identity
  • Senden der MFA-Anmeldeanforderung an den OpenID Connect-Server
  • Erzwingen, dass der ASP.NET Core-OpenID Connect-Client MFA anfordert

MFA, 2FA

MFA erfordert mindestens zwei Arten von Identitätsnachweisen. Das kann etwas sein, das Sie kennen, etwas, das Sie besitzen, oder eine Validierung durch biometrische Faktoren.

Die Zwei-Faktor-Authentifizierung (2FA) ist im Grunde eine Untergruppe der Multi-Faktor-Authentifizierung (MFA). Der Unterschied besteht darin, dass die MFA zwei oder mehr Faktoren zum Nachweis der Identität erfordern kann.

MFA mit TOTP-Algorithmus

MFA mit TOTP ist eine unterstützte Implementierung mit ASP.NET Core Identity. Sie kann zusammen mit jeder kompatiblen Authentifikator-App verwendet werden, einschließlich diesen:

  • Microsoft Authenticator-App
  • Google Authenticator App

Details zur Implementierung finden Sie unter dem folgenden Link:

Aktivieren der QR-Code-Generierung für TOTP-Authentifikator-Apps in ASP.NET Core

MFA-Hauptschlüssel/FIDO2 oder kennwortlos

Für Hauptschlüssel/FIDO2 gilt derzeit:

  • Es ist die sicherste Möglichkeit zur Umsetzung der MFA.
  • Die MFA schützt vor Phishingangriffen. (Ebenso wie die zertifikatbasierte Authentifizierung und Windows for Business.)

Derzeit unterstützt ASP.NET Core Hauptschlüssel/FIDO2 nicht direkt. Hauptschlüssel/FIDO2 kann für MFA oder kennwortlose Flows verwendet werden.

Microsoft Entra ID bietet Unterstützung für Hauptschlüssel/FIDO2 und kennwortlose Flows. Weitere Informationen finden Sie unter Optionen für die kennwortlose Authentifizierung.

Andere Formen der kennwortlosen MFA schützen möglicherweise nicht vor Phishing.

MFA mit SMS

MFA mit SMS erhöht die Sicherheit im Vergleich zur Kennwortauthentifizierung (Einzelfaktor) erheblich. Die Verwendung von SMS als zweitem Faktor wird jedoch nicht mehr empfohlen. Für diese Art der Implementierung gibt es zu viele bekannte Angriffsvektoren.

NIST-Richtlinien

Konfigurieren von MFA für Verwaltungsseiten mit ASP.NET Core Identity

Beim Zugriff von Benutzer*innen auf vertrauliche Seiten innerhalb einer ASP.NET Core Identity-App kann die MFA erzwungen werden. Das kann für Apps nützlich sein, bei denen unterschiedliche Zugriffsebenen für die verschiedenen Identitäten vorhanden sind. Beispielsweise können Benutzer*innen mit einer einfachen Kennwortanmeldung Profildaten anzeigen, aber Administrator*innen müssen eine MFA-Methode verwenden, um auf Verwaltungsseiten zuzugreifen.

Erweitern der Anmeldung mit einem MFA-Anspruch

Die Erstellung des Democode erfolgte unter Verwendung von ASP.NET Core mit Identity und Razor Pages. Anstelle einer AddDefaultIdentity-Methode wird die AddIdentity-Methode verwendet, sodass eine IUserClaimsPrincipalFactory-Implementierung verwendet werden kann, um nach einer erfolgreichen Anmeldung Ansprüche zur Identität hinzuzufügen.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlite(
            Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<IdentityUser, IdentityRole>(
            options => options.SignIn.RequireConfirmedAccount = false)
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddSingleton<IEmailSender, EmailSender>();
    services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>, 
        AdditionalUserClaimsPrincipalFactory>();

    services.AddAuthorization(options =>
        options.AddPolicy("TwoFactorEnabled",
            x => x.RequireClaim("amr", "mfa")));

    services.AddRazorPages();
}

Die AdditionalUserClaimsPrincipalFactory-Klasse fügt den amr-Anspruch erst nach erfolgreicher Anmeldung zu den Benutzeransprüchen hinzu. Der Wert des Anspruchs wird aus der Datenbank gelesen. Der Anspruch wird hier hinzugefügt, da Benutzer*innen nur auf eine auf höherer Ebene geschützte Ansicht zugreifen dürfen, wenn sich die Identität per MFA angemeldet hat. Wenn anstatt der Verwendung des Anspruchs die Datenbankansicht direkt aus der Datenbank gelesen wird, ist ein Zugriff auf die Ansicht ohne MFA direkt nach der Aktivierung der MFA möglich.

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;

namespace IdentityStandaloneMfa
{
    public class AdditionalUserClaimsPrincipalFactory : 
        UserClaimsPrincipalFactory<IdentityUser, IdentityRole>
    {
        public AdditionalUserClaimsPrincipalFactory( 
            UserManager<IdentityUser> userManager,
            RoleManager<IdentityRole> roleManager, 
            IOptions<IdentityOptions> optionsAccessor) 
            : base(userManager, roleManager, optionsAccessor)
        {
        }

        public async override Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
        {
            var principal = await base.CreateAsync(user);
            var identity = (ClaimsIdentity)principal.Identity;

            var claims = new List<Claim>();

            if (user.TwoFactorEnabled)
            {
                claims.Add(new Claim("amr", "mfa"));
            }
            else
            {
                claims.Add(new Claim("amr", "pwd"));
            }

            identity.AddClaims(claims);
            return principal;
        }
    }
}

Da sich das Setup des Identity-Diensts in der Startup-Klasse geändert hat, müssen die Layouts von Identity aktualisiert werden. Erstellen Sie ein Gerüst für die Identity-Seiten in der App. Definieren Sie das Layout in der Identity/Account/Manage/_Layout.cshtml-Datei.

@{
    Layout = "/Pages/Shared/_Layout.cshtml";
}

Weisen Sie außerdem das Layout für alle Verwaltungsseiten von den Identity-Seiten zu:

@{
    Layout = "_Layout.cshtml";
}

Überprüfen der MFA-Anforderung auf der Verwaltungsseite

Die Razor Page für die Verwaltung überprüft, ob sich der Benutzer oder die Benutzerin per MFA angemeldet hat. In der OnGet-Methode wird die Identität für den Zugriff auf die Benutzeransprüche verwendet. Im amr-Anspruch wird nach dem Wert mfa gesucht. Wenn diese Anspruch in der Identität fehlt oder die Identität false ist, erfolgt eine Weiterleitung von dieser Seite an die Seite „MFA aktivieren“. Dies ist möglich, da sich der Benutzer bzw. die Benutzerin bereits angemeldet hat, jedoch ohne MFA.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace IdentityStandaloneMfa
{
    public class AdminModel : PageModel
    {
        public IActionResult OnGet()
        {
            var claimTwoFactorEnabled = 
                User.Claims.FirstOrDefault(t => t.Type == "amr");

            if (claimTwoFactorEnabled != null && 
                "mfa".Equals(claimTwoFactorEnabled.Value))
            {
                // You logged in with MFA, do the administrative stuff
            }
            else
            {
                return Redirect(
                    "/Identity/Account/Manage/TwoFactorAuthentication");
            }

            return Page();
        }
    }
}

Benutzeroberflächenlogik zum Umschalten von Benutzeranmeldeinformationen

In der Programmdatei wurde eine Autorisierungsrichtlinie hinzugefügt. Für diese Richtlinie muss der amr-Anspruch mit dem Wert mfa vorhanden sein.

builder.Services.AddAuthorization(options =>
    options.AddPolicy("TwoFactorEnabled",
        x => x.RequireClaim("amr", "mfa")));

Diese Richtlinie kann dann in der _Layout-Ansicht verwendet werden, um das Menü Admin mit folgender Warnung anzuzeigen oder nicht anzuzeigen:

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
@inject IAuthorizationService AuthorizationService

Wenn sich die Identität per MFA angemeldet hat, wird das Menü Admin ohne QuickInfo mit einer Warnung angezeigt. Wenn sich der Benutzer oder die Benutzerin ohne MFA angemeldet hat, wird das Menü Admin (nicht aktiviert) sowie eine QuickInfo angezeigt, die den Benutzer bzw. die Benutzerin informiert und die Warnung erläutert.

@if (SignInManager.IsSignedIn(User))
{
    @if ((AuthorizationService.AuthorizeAsync(User, "TwoFactorEnabled")).Result.Succeeded)
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Admin">Admin</a>
        </li>
    }
    else
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Admin" 
               id="tooltip-demo"  
               data-toggle="tooltip" 
               data-placement="bottom" 
               title="MFA is NOT enabled. This is required for the Admin Page. If you have activated MFA, then logout, login again.">
                Admin (Not Enabled)
            </a>
        </li>
    }
}

Wenn sich der Benutzer oder die Benutzerin ohne MFA anmeldet, wird die Warnung angezeigt:

Multi-Faktor-Authentifizierung für Administrator*innen

Benutzer*innen werden zur Ansicht zur Aktivierung der MFA weitergeleitet, wenn sie auf den Link Admin klicken:

Administrator aktiviert Multi-Faktor-Authentifizierung

Senden der MFA-Anmeldeanforderung an den OpenID Connect-Server

Der Parameter acr_values kann verwendet werden, um den erforderlichen mfa-Wert in einer Authentifizierungsanforderung vom Client an den Server zu übergeben.

Hinweis

Der Parameter acr_values muss auf dem OpenID Connect-Server verarbeitet werden, damit dies funktioniert.

OpenID Connect-ASP.NET Core Client

Die ASP.NET Core Razor Pages-OpenID Connect-Client-App verwendet die Methode AddOpenIdConnect zur Anmeldung beim OpenID Connect-Server. Der Parameter acr_values wird mit dem Wert mfa festgelegt und mit der Authentifizierungsanforderung gesendet. Zum Hinzufügen wird OpenIdConnectEvents verwendet.

Informationen zu empfohlenen Werten für den Parameter acr_values finden Sie unter Authentication Method Reference Values (Referenzwerte für Authentifizierungsmethoden).

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultScheme =
            CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme =
            OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.SignInScheme =
            CookieAuthenticationDefaults.AuthenticationScheme;
        options.Authority = "<OpenID Connect server URL>";
        options.RequireHttpsMetadata = true;
        options.ClientId = "<OpenID Connect client ID>";
        options.ClientSecret = "<>";
        options.ResponseType = "code";
        options.UsePkce = true;	
        options.Scope.Add("profile");
        options.Scope.Add("offline_access");
        options.SaveTokens = true;
        options.Events = new OpenIdConnectEvents
        {
            OnRedirectToIdentityProvider = context =>
            {
                context.ProtocolMessage.SetParameter("acr_values", "mfa");
                return Task.FromResult(0);
            }
        };
    });

Beispiel: OpenID Connect IdentityServer4-Server mit ASP.NET Core Identity

Auf dem OpenID Connect-Server, der mithilfe von ASP.NET Core Identity mit MVC-Ansichten implementiert wird, wird eine neue Ansicht namens ErrorEnable2FA.cshtml erstellt. Für diese Ansicht gilt Folgendes:

  • Sie wird angezeigt, wenn die Identity von einer App stammt, die MFA erfordert, aber der Benutzer oder die Benutzerin dies in Identity nicht aktiviert hat.
  • Sie informiert den Benutzer oder die Benutzerin und fügt einen Link zum Aktivieren hinzu.
@{
    ViewData["Title"] = "ErrorEnable2FA";
}

<h1>The client application requires you to have MFA enabled. Enable this, try login again.</h1>

<br />

You can enable MFA to login here:

<br />

<a asp-controller="Manage" asp-action="TwoFactorAuthentication">Enable MFA</a>

In der Login-Methode wird die IIdentityServerInteractionService-Schnittstellenimplementierung _interaction verwendet, um auf die OpenID Connect-Anforderungsparameter zuzugreifen. Auf den Parameter acr_values wird mithilfe der Eigenschaft AcrValues zugegriffen. Da der Client dies mit festgelegtem mfa-Wert gesendet hat, kann dies dann überprüft werden.

Wenn die MFA erforderlich ist und der Benutzer oder die Benutzer in ASP.NET Core Identity die MFA aktiviert hat, wird die Anmeldung fortgesetzt. Wenn die MFA nicht aktiviert ist, wird der Benutzer oder die Benutzerin an die benutzerdefinierte Ansicht ErrorEnable2FA.cshtml umgeleitet. Dann meldet ASP.NET Core Identity den Benutzer oder die Benutzerin an.

//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
    var returnUrl = model.ReturnUrl;
    var context = 
        await _interaction.GetAuthorizationContextAsync(returnUrl);
    var requires2Fa = 
        context?.AcrValues.Count(t => t.Contains("mfa")) >= 1;

    var user = await _userManager.FindByNameAsync(model.Email);
    if (user != null && !user.TwoFactorEnabled && requires2Fa)
    {
        return RedirectToAction(nameof(ErrorEnable2FA));
    }

    // code omitted for brevity

Die ExternalLoginCallback-Methode funktioniert wie die lokale Identity-Anmeldung. Die AcrValues-Eigenschaft wird für den mfa-Wert überprüft. Wenn der mfa-Wert vorhanden ist, wird die MFA erzwungen, bevor die Anmeldung abgeschlossen wird (beispielsweise durch Umleitung an die Ansicht ErrorEnable2FA).

//
// GET: /Account/ExternalLoginCallback
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginCallback(
    string returnUrl = null,
    string remoteError = null)
{
    var context =
        await _interaction.GetAuthorizationContextAsync(returnUrl);
    var requires2Fa =
        context?.AcrValues.Count(t => t.Contains("mfa")) >= 1;

    if (remoteError != null)
    {
        ModelState.AddModelError(
            string.Empty,
            _sharedLocalizer["EXTERNAL_PROVIDER_ERROR", 
            remoteError]);
        return View(nameof(Login));
    }
    var info = await _signInManager.GetExternalLoginInfoAsync();

    if (info == null)
    {
        return RedirectToAction(nameof(Login));
    }

    var email = info.Principal.FindFirstValue(ClaimTypes.Email);

    if (!string.IsNullOrEmpty(email))
    {
        var user = await _userManager.FindByNameAsync(email);
        if (user != null && !user.TwoFactorEnabled && requires2Fa)
        {
            return RedirectToAction(nameof(ErrorEnable2FA));
        }
    }

    // Sign in the user with this external login provider if the user already has a login.
    var result = await _signInManager
        .ExternalLoginSignInAsync(
            info.LoginProvider, 
            info.ProviderKey, 
            isPersistent: 
            false);

    // code omitted for brevity

Wenn der Benutzer oder die Benutzerin bereits angemeldet ist, gilt für die Client-App Folgendes:

  • Sie überprüft dennoch den amr-Anspruch.
  • Sie kann die MFA mit einem Link zur ASP.NET Core Identity-Ansicht einrichten.

acr_values-1 image

Erzwingen, dass der ASP.NET Core-OpenID Connect-Client MFA anfordert

Dieses Beispiel zeigt, wie eine ASP.NET Core Razor Page-App, die OpenID Connect für die Anmeldung verwendet, erfordern kann, dass Benutzer*innen sich mit MFA authentifiziert haben.

Zum Überprüfen der MFA-Anforderung wird eine IAuthorizationRequirement-Anforderung erstellt. Diese wird den Seiten mithilfe einer Richtlinie hinzugefügt, die MFA erfordert.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc
{
    public class RequireMfa : IAuthorizationRequirement{}
}

Ein AuthorizationHandler wird implementiert, der den amr-Anspruch verwendet und nach dem Wert mfa sucht. Der amr-Anspruch wird im id_token einer erfolgreichen Authentifizierung zurückgegeben und kann viele verschiedene Werte aufweisen, wie in der Spezifikation Authentication Method Reference Values (Referenzwerte für Authentifizierungsmethoden) definiert.

Der zurückgegebene Wert hängt von der Art der Authentifizierung der Identität und der OpenID Connect-Serverimplementierung ab.

Der AuthorizationHandler verwendet die Anforderung RequireMfa und überprüft den amr-Anspruch. Der OpenID Connect-Server kann mithilfe von IdentityServer4 mit ASP.NET Core Identity implementiert werden. Wenn Benutzer*innen sich mithilfe von TOTP anmelden, wird der amr-Anspruch mit einem MFA-Wert zurückgegeben. Bei Verwendung einer anderen OpenID Connect-Serverimplementierung oder eines anderen MFA-Typs kann der amr-Anspruch einen anderen Wert aufweisen. Der Code muss erweitert werden, um dies ebenfalls zu akzeptieren.

public class RequireMfaHandler : AuthorizationHandler<RequireMfa>
{
	protected override Task HandleRequirementAsync(
		AuthorizationHandlerContext context, 
		RequireMfa requirement)
	{
		if (context == null)
			throw new ArgumentNullException(nameof(context));
		if (requirement == null)
			throw new ArgumentNullException(nameof(requirement));

		var amrClaim =
			context.User.Claims.FirstOrDefault(t => t.Type == "amr");

		if (amrClaim != null && amrClaim.Value == Amr.Mfa)
		{
			context.Succeed(requirement);
		}

		return Task.CompletedTask;
	}
}

In der Startup.ConfigureServices-Methode wird die AddOpenIdConnect-Methode als standardmäßiges Aufforderungsschema verwendet. Der zum Überprüfen des amr-Anspruchs verwendete Autorisierungshandler wird dem Inversion of Control-Container hinzugefügt. Dann wird eine Richtlinie erstellt, die die RequireMfa-Anforderung hinzufügt.

public void ConfigureServices(IServiceCollection services)
{
    services.ConfigureApplicationCookie(options =>
        options.Cookie.SecurePolicy =
            CookieSecurePolicy.Always);

    services.AddSingleton<IAuthorizationHandler, RequireMfaHandler>();

    services.AddAuthentication(options =>
    {
        options.DefaultScheme =
            CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme =
            OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.SignInScheme =
            CookieAuthenticationDefaults.AuthenticationScheme;
        options.Authority = "https://localhost:44352";
        options.RequireHttpsMetadata = true;
        options.ClientId = "AspNetCoreRequireMfaOidc";
        options.ClientSecret = "AspNetCoreRequireMfaOidcSecret";
        options.ResponseType = "code";
        options.UsePkce = true;	
        options.Scope.Add("profile");
        options.Scope.Add("offline_access");
        options.SaveTokens = true;
    });

    services.AddAuthorization(options =>
    {
        options.AddPolicy("RequireMfa", policyIsAdminRequirement =>
        {
            policyIsAdminRequirement.Requirements.Add(new RequireMfa());
        });
    });

    services.AddRazorPages();
}

Diese Richtlinie wird dann nach Bedarf auf der Razor Page verwendet. Die Richtlinie kann auch global für die gesamte App hinzugefügt werden.

[Authorize(Policy= "RequireMfa")]
public class IndexModel : PageModel
{
    public void OnGet()
    {
    }
}

Wenn Benutzer*innen sich ohne MFA authentifizieren, weist der amr-Anspruch möglicherweise einen Wert für pwd auf. Die Anforderung wird nicht für den Zugriff auf die Seite autorisiert. Bei Verwendung der Standardwerte werden Benutzer*innen an die Seite Account/AccessDenied umgeleitet. Dieses Verhalten kann geändert werden. Sie können hier auch Ihre eigene benutzerdefinierte Logik implementieren. In diesem Beispiel wird ein Link hinzugefügt, damit gültige Benutzer*innen die MFA für ihr Konto einrichten können.

@page
@model AspNetCoreRequireMfaOidc.AccessDeniedModel
@{
    ViewData["Title"] = "AccessDenied";
    Layout = "~/Pages/Shared/_Layout.cshtml";
}

<h1>AccessDenied</h1>

You require MFA to login here

<a href="https://localhost:44352/Manage/TwoFactorAuthentication">Enable MFA</a>

Jetzt können nur Benutzer*innen, die sich per MFA authentifizieren, auf die Seite oder Website zugreifen. Wenn andere MFA-Typen verwendet werden oder die 2FA akzeptabel ist, weist der amr-Anspruch unterschiedliche Werte auf und muss korrekt verarbeitet werden. Verschiedene OpenID Connect-Server geben ebenfalls verschiedene Werte für diesen Anspruch zurück und folgen möglicherweise nicht der Spezifikation unter Authentication Method Reference Values (Referenzwerte für Authentifizierungsmethoden).

Bei Anmeldung ohne MFA (beispielsweise nur durch Verwendung eines Kennworts):

  • amr weist den Wert pwd auf:

    amr weist den Wert „pwd“ auf

  • Der Zugriff wird verweigert:

    Zugriff verweigert

Alternativ können Sie sich über OTP mit Identity anmelden:

Anmelden über OTP mit Identity

Zusätzliche Ressourcen