Multi-Faktor-Authentifizierung in ASP.NET Core
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der Supportrichtlinie für .NET und .NET Core. 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.
Die aktuelle Version finden Sie in der .NET 9-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 Cyber-Angreifenden 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 Nachweisen der identity. 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 identity 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.
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 identity hinzuzufügen.
Warnung
In diesem Artikel wird die Verwendung von Verbindungszeichenfolge gezeigt. Bei einer lokalen Datenbank muss der Benutzer nicht authentifiziert werden, aber in der Produktion enthalten Verbindungszeichenfolge manchmal ein Kennwort für die Authentifizierung. Ein Ressourcenbesitzer-Kennwortanmeldeinformation (ROPC) ist ein Sicherheitsrisiko, das in Produktionsdatenbanken vermieden werden sollte. Produktions-Apps sollten den sichersten verfügbaren Ablauf für die Authentifizierung verwenden. Weitere Informationen zur Authentifizierung für Apps, die für Test- oder Produktionsumgebungen bereitgestellt werden, finden Sie unter Sichere Authentifizierungsflüsse.
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 identity 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 identity für den Zugriff auf die Benutzeransprüche verwendet. Im amr
-Anspruch wird nach dem Wert mfa
gesucht. Wenn diese Anspruch in der identity fehlt oder die 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 identity 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:
Benutzer*innen werden zur Ansicht zur Aktivierung der MFA weitergeleitet, wenn sie auf den Link Admin klicken:
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 Server-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.
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 identity 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 Wertpwd
auf:Der Zugriff wird verweigert:
Alternativ können Sie sich über OTP mit Identity anmelden:
Anpassung der OIDC- und OAuth-Parameter
Die OAuth- und OIDC-Authentifizierungshandler verfügen jetzt über eine AdditionalAuthorizationParameters
-Option, mit der Sie die Parameter der Autorisierungsnachricht, die normalerweise Teil der Zeichenkette der Umleitungsabfrage sind, einfacher anpassen können.
builder.Services.AddAuthentication().AddOpenIdConnect(options =>
{
options.AdditionalAuthorizationParameters.Add("prompt", "login");
options.AdditionalAuthorizationParameters.Add("audience", "https://api.example.com");
});
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 Cyber-Angreifenden 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 Nachweisen der identity. 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 identity 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.
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 identity hinzuzufügen.
Warnung
In diesem Artikel wird die Verwendung von Verbindungszeichenfolge gezeigt. Bei einer lokalen Datenbank muss der Benutzer nicht authentifiziert werden, aber in der Produktion enthalten Verbindungszeichenfolge manchmal ein Kennwort für die Authentifizierung. Ein Ressourcenbesitzer-Kennwortanmeldeinformation (ROPC) ist ein Sicherheitsrisiko, das in Produktionsdatenbanken vermieden werden sollte. Produktions-Apps sollten den sichersten verfügbaren Ablauf für die Authentifizierung verwenden. Weitere Informationen zur Authentifizierung für Apps, die für Test- oder Produktionsumgebungen bereitgestellt werden, finden Sie unter Sichere Authentifizierungsflüsse.
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 identity 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 identity für den Zugriff auf die Benutzeransprüche verwendet. Im amr
-Anspruch wird nach dem Wert mfa
gesucht. Wenn diese Anspruch in der identity fehlt oder die 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 identity 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:
Benutzer*innen werden zur Ansicht zur Aktivierung der MFA weitergeleitet, wenn sie auf den Link Admin klicken:
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 Server-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.
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 identity 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 Wertpwd
auf:Der Zugriff wird verweigert:
Alternativ können Sie sich über OTP mit Identity anmelden:
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 Cyber-Angreifenden 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 Nachweisen der identity. 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 identity 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.
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 identity hinzuzufügen.
Warnung
In diesem Artikel wird die Verwendung von Verbindungszeichenfolge gezeigt. Bei einer lokalen Datenbank muss der Benutzer nicht authentifiziert werden, aber in der Produktion enthalten Verbindungszeichenfolge manchmal ein Kennwort für die Authentifizierung. Ein Ressourcenbesitzer-Kennwortanmeldeinformation (ROPC) ist ein Sicherheitsrisiko, das in Produktionsdatenbanken vermieden werden sollte. Produktions-Apps sollten den sichersten verfügbaren Ablauf für die Authentifizierung verwenden. Weitere Informationen zur Authentifizierung für Apps, die für Test- oder Produktionsumgebungen bereitgestellt werden, finden Sie unter Sichere Authentifizierungsflüsse.
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 identity 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 identity für den Zugriff auf die Benutzeransprüche verwendet. Im amr
-Anspruch wird nach dem Wert mfa
gesucht. Wenn diese Anspruch in der identity fehlt oder die 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 identity 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:
Benutzer*innen werden zur Ansicht zur Aktivierung der MFA weitergeleitet, wenn sie auf den Link Admin klicken:
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 IdentityServer 4-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.
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 identity 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 Wertpwd
auf:Der Zugriff wird verweigert:
Alternativ können Sie sich über OTP mit Identity anmelden:
Zusätzliche Ressourcen
ASP.NET Core