Delen via


Meervoudige verificatie in ASP.NET Core

Notitie

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Waarschuwing

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie de .NET- en .NET Core-ondersteuningsbeleidvoor meer informatie. Zie de .NET 9-versie van dit artikelvoor de huidige release.

Belangrijk

Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.

Zie de .NET 9-versie van dit artikelvoor de huidige release.

Door Damien Bowden

Voorbeeldcode (damienbod/AspNetCoreHybridFlowWithApi GitHub-opslagplaats) weergeven of downloaden

Multi-factor authenticatie (MFA) is een proces waarbij tijdens een aanmeldingsproces om aanvullende vormen van identificatie wordt gevraagd. Deze prompt kan zijn om een code van een mobiele telefoon in te voeren, een FIDO2-toets te gebruiken of om een vingerafdrukscan op te geven. Wanneer u een tweede vorm van verificatie nodig hebt, wordt de beveiliging verbeterd. De extra factor wordt niet eenvoudig verkregen of gedupliceerd door een cyberaanval.

In dit artikel worden de volgende gebieden behandeld:

  • Wat is MFA en welke MFA-stromen worden aanbevolen
  • MFA configureren voor beheerpagina's met behulp van ASP.NET Core Identity
  • MFA-aanmeldingsvereiste verzenden naar OpenID Connect-server
  • Dwing ASP.NET Core OpenID Connect-client om meervoudige verificatie te vereisen

MFA, 2FA

MFA vereist ten minste twee of meer soorten bewijs voor een identiteit, zoals iets dat u weet, iets dat u bezit of biometrische validatie voor de gebruiker om te verifiëren.

Tweeledige verificatie (2FA) is vergelijkbaar met een subset van MFA, maar het verschil is dat MFA twee of meer factoren kan vereisen om de identiteit te bewijzen.

2FA wordt standaard ondersteund bij het gebruik van ASP.NET Core Identity. Als u 2FA voor een specifieke gebruiker wilt in- of uitschakelen, stelt u de eigenschap IdentityUser<TKey>.TwoFactorEnabled in. De ASP.NET Core Identity Default UI bevat pagina's voor het configureren van 2FA.

MFA TOTP (eenmalige wachtwoordalgoritmen op basis van tijd)

MFA met TOTP wordt standaard ondersteund bij het gebruik van ASP.NET Core Identity. Deze benadering kan samen met elke compatibele authenticator-app worden gebruikt, waaronder:

  • Microsoft Authenticator
  • Google Authenticator

Zie HET genereren van QR-code inschakelen voor TOTP authenticator-apps in ASP.NET Corevoor meer informatie over de implementatie.

Als u ondersteuning voor MFA TOTP wilt uitschakelen, configureert u verificatie met behulp van AddIdentity in plaats van AddDefaultIdentity. AddDefaultIdentity roept intern AddDefaultTokenProviders aan, waarmee meerdere tokenproviders worden geregistreerd, waaronder één voor MFA TOTP. Als u alleen specifieke tokenproviders wilt registreren, roept u AddTokenProvider aan voor elke vereiste provider. Zie de AddDefaultTokenProviders-bron op GitHubvoor meer informatie over beschikbare tokenproviders.

MFA-wachtwoordsleutels/FIDO2 of wachtwoordloos

wachtwoordsleutels/FIDO2 is momenteel:

  • De veiligste manier om MFA te bereiken.
  • MFA die bescherming biedt tegen phishingaanvallen. (Evenals certificaatverificatie en Windows voor Bedrijven)

Op dit moment biedt ASP.NET Core geen ondersteuning voor wachtwoordsleutels/FIDO2. Wachtwoordsleutels/FIDO2 kunnen worden gebruikt voor MFA- of wachtwoordloze stromen.

Microsoft Entra ID biedt ondersteuning voor passkeys/FIDO2 en wachtwoordloze processen. Zie opties voor verificatie zonder wachtwoordvoor meer informatie.

Andere vormen van wachtwoordloze MFA beschermen niet of mogelijk niet tegen phishing.

MFA SMS

MFA met SMS verhoogt de beveiliging enorm vergeleken met wachtwoordverificatie (één factor). Het gebruik van sms als een tweede factor wordt echter niet meer aanbevolen. Er bestaan te veel bekende aanvalsvectoren voor dit type implementatie.

NIST-richtlijnen

MFA configureren voor beheerpagina's met behulp van ASP.NET Core Identity

MFA kan verplicht worden voor gebruikers voor toegang tot gevoelige pagina's binnen een ASP.NET Core Identity-app. Dit kan handig zijn voor apps waarbij verschillende toegangsniveaus bestaan voor de verschillende identiteiten. Gebruikers kunnen bijvoorbeeld de profielgegevens bekijken met behulp van een wachtwoordaanmelding, maar een beheerder moet MFA gebruiken voor toegang tot de beheerpagina's.

De aanmelding uitbreiden met een MFA-claim

De democode wordt ingesteld met ASP.NET Core met Identity en Razor Pages. De AddIdentity methode wordt gebruikt in plaats van AddDefaultIdentity, zodat een IUserClaimsPrincipalFactory implementatie kan worden gebruikt om claims toe te voegen aan de identiteit na een geslaagde aanmelding.

Waarschuwing

In dit artikel wordt het gebruik van verbindingsreeksen beschreven. Met een lokale database hoeft de gebruiker niet te worden geverifieerd, maar in productie bevatten verbindingsreeksen soms een wachtwoord om te verifiëren. Een wachtwoordgegevens voor een eigenaar van een bron (ROPC) is een beveiligingsrisico dat moet worden vermeden in productiedatabases. Productie-apps moeten gebruikmaken van de veiligste verificatiestroom die beschikbaar is. Zie Beveiligde verificatiestromenvoor meer informatie over verificatie voor apps die zijn geïmplementeerd voor test- of productieomgevingen.

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();

De AdditionalUserClaimsPrincipalFactory-klasse voegt de amr-claim alleen toe aan de gebruikersclaims na een geslaagde aanmelding. De waarde van de claim wordt gelezen uit de database. De claim wordt hier toegevoegd omdat de gebruiker alleen toegang moet krijgen tot de hogere beveiligde weergave als de identiteit is aangemeld met MFA. Als de databaseweergave rechtstreeks uit de database wordt gelezen in plaats van de claim te gebruiken, is het mogelijk om rechtstreeks na het activeren van de MFA toegang te krijgen tot de weergave zonder MFA.

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;
        }
    }
}

Omdat de installatie van de Identity-service in de klasse Startup is gewijzigd, moeten de indelingen van de Identity worden bijgewerkt. Integreer de Identity pagina's in de app. Definieer de indeling in het Identity/Account/Manage/_Layout.cshtml-bestand.

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

Wijs ook de indeling toe voor alle beheerpagina's van de Identity pagina's:

@{
    Layout = "_Layout.cshtml";
}

Valideer de MFA-vereiste op de beheerpagina

De beheerpagina Razor valideert dat de gebruiker is aangemeld met multi-factor authenticatie. In de OnGet-methode wordt de identiteit gebruikt voor toegang tot de gebruikersclaims. De amr claim wordt gecontroleerd op de waarde mfa. Als de claim ontbreekt of als de identiteit de claim falseheeft, wordt de pagina omgeleid naar de pagina voor het inschakelen van MFA. Dit is mogelijk omdat de gebruiker zich al heeft aangemeld, maar zonder 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();
        }
    }
}

UI-logica voor het in-/uitschakelen van gebruikersaanmeldingsgegevens

Er is een autorisatiebeleid toegevoegd bij het opstarten. Het beleid vereist de amr claim met de waarde mfa.

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

Dit beleid kan vervolgens worden gebruikt in de weergave _Layout om het menu Admin weer te geven of te verbergen met de waarschuwing:

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

Als de identiteit is aangemeld met MFA, wordt het menu Admin zonder de waarschuwing voor tooltips weergegeven. Wanneer de gebruiker zich heeft aangemeld zonder MFA, wordt het Admin (Niet ingeschakeld) menu weergegeven, samen met de knopinfo waarmee de gebruiker wordt geïnformeerd (waarin de waarschuwing wordt uitgelegd).

@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>
    }
}

Als de gebruiker zich aanmeldt zonder MFA, wordt de waarschuwing weergegeven:

MFA-verificatie van beheerders

De gebruiker wordt omgeleid naar de weergave MFA inschakelen wanneer u op de koppeling Admin klikt:

administrator MFA-verificatie activeert

MFA-aanmeldingsvereiste verzenden naar OpenID Connect-server

De parameter acr_values kan worden gebruikt om de mfa vereiste waarde van de client door te geven aan de server in een verificatieaanvraag.

Notitie

De parameter acr_values moet worden verwerkt op de OpenID Connect-server om dit te laten werken.

OpenID Connect ASP.NET Core-client

De ASP.NET Core Razor Pages OpenID Connect-client-app maakt gebruik van de AddOpenIdConnect methode om u aan te melden bij de OpenID Connect-server. De parameter acr_values wordt ingesteld met de mfa waarde en verzonden met de verificatieaanvraag. De OpenIdConnectEvents wordt gebruikt om dit toe te voegen.

Zie Referentiewaarden voor verificatiemethodevoor aanbevolen acr_values parameterwaarden.

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");
});

Voorbeeld van OpenID Connect Duende IdentityServer-server met ASP.NET Core Identity

Op de OpenID Connect-server, die wordt geïmplementeerd met ASP.NET Core Identity met Razor Pages, wordt een nieuwe pagina met de naam ErrorEnable2FA.cshtml gemaakt. Het uitzicht:

  • Geeft weer of de Identity afkomstig is van een app waarvoor MFA is vereist, maar de gebruiker dit niet heeft geactiveerd in Identity.
  • Informeert de gebruiker en voegt een koppeling toe om deze te activeren.
@{
    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 de Login methode wordt de IIdentityServerInteractionService interface-implementatie _interaction gebruikt voor toegang tot de OpenID Connect-aanvraagparameters. De parameter acr_values wordt geopend met behulp van de eigenschap AcrValues. Wanneer de client dit met mfa set heeft verzonden, kan dit vervolgens worden gecontroleerd.

Als MFA is vereist en de gebruiker in ASP.NET Core Identity MFA heeft ingeschakeld, wordt de aanmelding voortgezet. Wanneer de gebruiker geen MFA heeft ingeschakeld, wordt de gebruiker omgeleid naar de aangepaste weergave ErrorEnable2FA.cshtml. Vervolgens ASP.NET Core Identity de gebruiker aanmeldt.

De Fido2Store wordt gebruikt om te controleren of de gebruiker MFA heeft geactiveerd met behulp van een aangepaste FIDO2-tokenprovider.

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();
}

Als de gebruiker al is aangemeld, doet de client-app het volgende:

  • Valideert nog steeds de amr claim.
  • Kan de MFA instellen met een koppeling naar de ASP.NET Core-weergave, Identity.

acr_values-1 afbeelding

Dwing af dat de ASP.NET Core OpenID Connect-client MFA vereist

In dit voorbeeld ziet u hoe een ASP.NET Core Razor Page-app, die Gebruikmaakt van OpenID Connect om zich aan te melden, kan vereisen dat gebruikers zijn geverifieerd met behulp van MFA.

Als u de MFA-vereiste wilt valideren, wordt er een IAuthorizationRequirement vereiste gemaakt. Dit wordt toegevoegd aan de pagina's met behulp van een beleid waarvoor MFA is vereist.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc;

public class RequireMfa : IAuthorizationRequirement{}

Er wordt een AuthorizationHandler geïmplementeerd die de amr claim gebruikt en controleert op de waarde mfa. De amr wordt geretourneerd in het id_token van een geslaagde verificatie en kan veel verschillende waarden hebben, zoals gedefinieerd in de referentiewaarden voor verificatiemethode specificatie.

De geretourneerde waarde is afhankelijk van de verificatie van de identiteit en van de implementatie van de OpenID Connect-server.

De AuthorizationHandler maakt gebruik van de RequireMfa vereiste en valideert de amr claim. De OpenID Connect-server kan worden geïmplementeerd met behulp van Duende Identity Server met ASP.NET Core Identity. Wanneer een gebruiker zich aanmeldt met TOTP, wordt de amr claim geretourneerd met een MFA-waarde. Als u een andere OpenID Connect-server-implementatie of een ander MFA-type gebruikt, kan de amr claim een andere waarde hebben. De code moet ook worden uitgebreid om dit te accepteren.

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 het programmabestand wordt de AddOpenIdConnect methode gebruikt als het standaarduitdagingsschema. De autorisatie-handler, die wordt gebruikt om claim amr te controleren, wordt toegevoegd aan de Inversion of Control-container. Er wordt vervolgens een beleid gemaakt waarmee de RequireMfa vereiste wordt toegevoegd.

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();

Dit beleid wordt vervolgens indien nodig gebruikt op de Razor pagina. Het beleid kan ook wereldwijd worden toegevoegd voor de hele app.

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

Als de gebruiker zich zonder MFA verifieert, heeft de amr claim waarschijnlijk een pwd waarde. De aanvraag wordt niet geautoriseerd voor toegang tot de pagina. Met behulp van de standaardwaarden wordt de gebruiker omgeleid naar de pagina Account/AccessDenied. Dit gedrag kan worden gewijzigd of u kunt hier uw eigen aangepaste logica implementeren. In dit voorbeeld wordt er een koppeling toegevoegd, zodat de geldige gebruiker MFA voor zijn account kan instellen.

@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>

Nu hebben alleen gebruikers die verifiëren met MFA toegang tot de pagina of website. Als er verschillende MFA-typen worden gebruikt of als 2FA in orde is, heeft de claim amr verschillende waarden en moet deze correct worden verwerkt. Verschillende OpenID Connect-servers retourneren ook verschillende waarden voor deze claim en volgen mogelijk niet de referentiewaarden voor verificatiemethode specificatie.

Wanneer u zich aanmeldt zonder MFA (bijvoorbeeld met behulp van alleen een wachtwoord):

  • De amr heeft de pwd waarde:

    amr heeft de pwd-waarde

  • De toegang wordt geweigerd:

    toegang wordt geweigerd

U kunt zich ook aanmelden met OTP met Identity:

aanmelden met OTP met Identity

Aanpassing van OIDC- en OAuth-parameters

Met de OAuth- en OIDC-verificatiehandlers AdditionalAuthorizationParameters optie kunt u parameters voor autorisatieberichten aanpassen die meestal zijn opgenomen als onderdeel van de omleidingsquerytekenreeks:

builder.Services.AddAuthentication().AddOpenIdConnect(options =>
{
    options.AdditionalAuthorizationParameters.Add("prompt", "login");
    options.AdditionalAuthorizationParameters.Add("audience", "https://api.example.com");
});

Aanvullende informatiebronnen

Door Damien Bowden

Voorbeeldcode (damienbod/AspNetCoreHybridFlowWithApi GitHub-opslagplaats) weergeven of downloaden

Multi-factor authenticatie (MFA) is een proces waarbij aan een gebruiker tijdens een aanmelding wordt gevraagd om aanvullende vormen van identificatie. Deze prompt kan zijn om een code van een mobiele telefoon in te voeren, een FIDO2-toets te gebruiken of om een vingerafdrukscan op te geven. Wanneer u een tweede vorm van verificatie nodig hebt, wordt de beveiliging verbeterd. De extra factor wordt niet eenvoudig verkregen of gedupliceerd door een cyberaanval.

In dit artikel worden de volgende gebieden behandeld:

  • Wat is MFA en welke MFA-stromen worden aanbevolen
  • MFA configureren voor beheerpagina's met behulp van ASP.NET Core Identity
  • MFA-aanmeldingsvereiste verzenden naar OpenID Connect-server
  • Zorg ervoor dat de ASP.NET Core OpenID Connect-client een MFA vereist.

MFA, 2FA

MFA vereist ten minste twee of meer soorten bewijs voor een identiteit, zoals iets dat u weet, iets dat u bezit of biometrische validatie voor de gebruiker om te verifiëren.

Tweeledige verificatie (2FA) is vergelijkbaar met een subset van MFA, maar het verschil is dat MFA twee of meer factoren kan vereisen om de identiteit te bewijzen.

2FA wordt standaard ondersteund bij het gebruik van ASP.NET Core Identity. Als u 2FA voor een specifieke gebruiker wilt in- of uitschakelen, stelt u de eigenschap IdentityUser<TKey>.TwoFactorEnabled in. De ASP.NET Core Identity Default UI bevat pagina's voor het configureren van 2FA.

MFA TOTP (eenmalige wachtwoordalgoritmen op basis van tijd)

MFA met TOTP wordt standaard ondersteund bij het gebruik van ASP.NET Core Identity. Deze benadering kan samen met elke compatibele authenticator-app worden gebruikt, waaronder:

  • Microsoft Authenticator
  • Google Authenticator

Zie HET genereren van QR-code inschakelen voor TOTP authenticator-apps in ASP.NET Corevoor meer informatie over de implementatie.

Als u ondersteuning voor MFA TOTP wilt uitschakelen, configureert u verificatie met behulp van AddIdentity in plaats van AddDefaultIdentity. AddDefaultIdentity roept intern AddDefaultTokenProviders aan, waarmee meerdere tokenproviders worden geregistreerd, waaronder één voor MFA TOTP. Als u alleen specifieke tokenproviders wilt registreren, roept u AddTokenProvider aan voor elke vereiste provider. Zie de AddDefaultTokenProviders-bron op GitHubvoor meer informatie over beschikbare tokenproviders.

MFA-wachtwoordsleutels/FIDO2 of wachtwoordloos

passkeys/FIDO2 is momenteel:

  • De veiligste manier om MFA te bereiken.
  • MFA die bescherming biedt tegen phishingaanvallen. (Evenals certificaatverificatie en Windows voor Bedrijven)

Op dit moment biedt ASP.NET Core geen ondersteuning voor wachtwoordsleutels/FIDO2. Wachtwoordsleutels/FIDO2 kunnen worden gebruikt voor MFA- of wachtwoordloze stromen.

Microsoft Entra ID biedt ondersteuning voor toegangssleutels/FIDO2 en wachtwoordloze processen. Zie opties voor verificatie zonder wachtwoordvoor meer informatie.

Andere vormen van wachtwoordloze MFA beschermen niet of mogelijk niet tegen phishing.

MFA SMS

MFA met SMS verhoogt de beveiliging enorm vergeleken met wachtwoordverificatie (één factor). Het gebruik van sms als een tweede factor wordt echter niet meer aanbevolen. Er bestaan te veel bekende aanvalsvectoren voor dit type implementatie.

NIST-richtlijnen

MFA configureren voor beheerpagina's met behulp van ASP.NET Core Identity

MFA kan verplicht worden gesteld voor gebruikers om toegang te krijgen tot gevoelige pagina's binnen een ASP.NET Core Identity-app. Dit kan handig zijn voor apps waarbij verschillende toegangsniveaus bestaan voor de verschillende identiteiten. Gebruikers kunnen bijvoorbeeld de profielgegevens bekijken met behulp van een wachtwoordaanmelding, maar een beheerder moet MFA gebruiken voor toegang tot de beheerpagina's.

De aanmelding uitbreiden met een MFA-claim

De democode wordt ingesteld met ASP.NET Core met Identity en Razor Pages. De AddIdentity methode wordt gebruikt in plaats van AddDefaultIdentity, zodat een IUserClaimsPrincipalFactory implementatie kan worden gebruikt om claims toe te voegen aan de identiteit na een geslaagde aanmelding.

Waarschuwing

In dit artikel wordt het gebruik van verbindingsreeksen beschreven. Met een lokale database hoeft de gebruiker niet te worden geverifieerd, maar in productie bevatten verbindingsreeksen soms een wachtwoord om te verifiëren. Een wachtwoordreferentie voor een resource-eigenaar (ROPC) is een beveiligingsrisico dat moet worden vermeden in productiedatabases. Productie-apps moeten gebruikmaken van de veiligste verificatiestroom die beschikbaar is. Zie Beveiligde verificatiestromenvoor meer informatie over verificatie voor apps die zijn geïmplementeerd voor test- of productieomgevingen.

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();

De AdditionalUserClaimsPrincipalFactory-klasse voegt de amr claim alleen toe aan de gebruikersclaims na een geslaagde aanmelding. De waarde van de claim wordt gelezen uit de database. De claim wordt hier toegevoegd omdat de gebruiker alleen toegang moet krijgen tot de hogere beveiligde weergave als de identiteit is aangemeld met MFA. Als de databaseweergave rechtstreeks uit de database wordt gelezen in plaats van de claim te gebruiken, is het mogelijk om rechtstreeks na het activeren van de MFA toegang te krijgen tot de weergave zonder MFA.

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;
        }
    }
}

Omdat de installatie van de Identity-service in de klasse Startup is gewijzigd, moeten de indelingen van de Identity worden bijgewerkt. Scaffold de Identity pagina's in de app. Definieer de indeling in het Identity/Account/Manage/_Layout.cshtml-bestand.

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

Wijs ook de indeling toe voor alle beheerpagina's van de Identity pagina's:

@{
    Layout = "_Layout.cshtml";
}

Valideer de MFA-vereiste op de beheerpagina

De administratiepagina Razor valideert dat de gebruiker zich heeft aangemeld met Multi-Factor Authenticatie. In de OnGet-methode wordt de identiteit gebruikt voor toegang tot de gebruikersclaims. De amr claim wordt gecontroleerd op de waarde mfa. Als deze claim ontbreekt of als de identiteit falseis, wordt de pagina omgeleid naar de pagina MFA inschakelen. Dit is mogelijk omdat de gebruiker zich al heeft aangemeld, maar zonder 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();
        }
    }
}

UI-logica voor het in-/uitschakelen van gebruikersaanmeldingsgegevens

Er is een autorisatiebeleid toegevoegd bij het opstarten. Het beleid vereist de amr claim met de waarde mfa.

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

Dit beleid kan vervolgens worden gebruikt in de weergave _Layout om het menu Admin weer te geven of te verbergen met de waarschuwing:

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

Als de identiteit is aangemeld met multi-factor authenticatie (MFA), wordt het menu Admin weergegeven zonder de tooltipwaarschuwing. Wanneer de gebruiker zich heeft aangemeld zonder MFA, wordt het Admin (Niet ingeschakeld) menu weergegeven, samen met de knopinfo waarmee de gebruiker wordt geïnformeerd (waarin de waarschuwing wordt uitgelegd).

@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>
    }
}

Als de gebruiker zich aanmeldt zonder MFA, wordt de waarschuwing weergegeven:

MFA-verificatie van beheerders

De gebruiker wordt omgeleid naar de weergave MFA inschakelen wanneer u op de koppeling Admin klikt:

administrator MFA-verificatie activeert

MFA-aanmeldingsvereiste verzenden naar OpenID Connect-server

De parameter acr_values kan worden gebruikt om de mfa vereiste waarde van de client door te geven aan de server in een verificatieaanvraag.

Notitie

De parameter acr_values moet worden verwerkt op de OpenID Connect-server om dit te laten werken.

OpenID Connect ASP.NET Core-client

De ASP.NET Core Razor Pages OpenID Connect-client-app maakt gebruik van de AddOpenIdConnect methode om u aan te melden bij de OpenID Connect-server. De parameter acr_values wordt ingesteld met de mfa waarde en verzonden met de verificatieaanvraag. De OpenIdConnectEvents wordt gebruikt om dit toe te voegen.

Zie Referentiewaarden voor verificatiemethodevoor aanbevolen acr_values parameterwaarden.

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);
		}
	};
});

Voorbeeld van OpenID Connect Duende IdentityServer-server met ASP.NET Core Identity

Op de OpenID Connect-server, die wordt geïmplementeerd met ASP.NET Core Identity met Razor Pages, wordt een nieuwe pagina met de naam ErrorEnable2FA.cshtml gemaakt. De weergave:

  • Geeft weer of de Identity afkomstig is van een app waarvoor MFA is vereist, maar de gebruiker dit niet heeft geactiveerd in Identity.
  • Informeert de gebruiker en voegt een koppeling toe om deze te activeren.
@{
    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 de Login methode wordt de IIdentityServerInteractionService interface-implementatie _interaction gebruikt voor toegang tot de OpenID Connect-aanvraagparameters. De parameter acr_values wordt geopend met behulp van de eigenschap AcrValues. Wanneer de client dit met mfa set heeft verzonden, kan dit vervolgens worden gecontroleerd.

Als MFA is vereist en de gebruiker in ASP.NET Core Identity MFA heeft ingeschakeld, wordt de aanmelding voortgezet. Wanneer de gebruiker geen MFA heeft ingeschakeld, wordt de gebruiker omgeleid naar de aangepaste weergave ErrorEnable2FA.cshtml. Vervolgens ASP.NET Core Identity de gebruiker aanmeldt.

De Fido2Store wordt gebruikt om te controleren of de gebruiker MFA heeft geactiveerd met behulp van een aangepaste FIDO2-tokenprovider.

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();
}

Als de gebruiker al is aangemeld, doet de client-app het volgende:

  • Valideert nog steeds de amr claim.
  • Kan de MFA instellen via een koppeling naar de ASP.NET Core Identity view.

afbeelding acr_values-1

Dwing ASP.NET Core OpenID Connect-client om MFA te vereisen

In dit voorbeeld ziet u hoe een ASP.NET Core Razor Page-app, die Gebruikmaakt van OpenID Connect om zich aan te melden, kan vereisen dat gebruikers zijn geverifieerd met behulp van MFA.

Als u de MFA-vereiste wilt valideren, wordt er een IAuthorizationRequirement vereiste gemaakt. Dit wordt toegevoegd aan de pagina's met behulp van een beleid waarvoor MFA is vereist.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc;

public class RequireMfa : IAuthorizationRequirement{}

Er wordt een AuthorizationHandler geïmplementeerd die de amr claim gebruikt en controleert op de waarde mfa. De amr wordt geretourneerd in het id_token van een geslaagde verificatie en kan veel verschillende waarden hebben, zoals gedefinieerd in de referentiewaarden voor verificatiemethode specificatie.

De geretourneerde waarde is afhankelijk van de verificatie van de identiteit en van de implementatie van de OpenID Connect-server.

De AuthorizationHandler maakt gebruik van de RequireMfa vereiste en valideert de amr claim. De OpenID Connect-server kan worden geïmplementeerd met behulp van Duende Identity Server met ASP.NET Core Identity. Wanneer een gebruiker zich aanmeldt met TOTP, wordt de amr claim geretourneerd met een MFA-waarde. Als u een andere OpenID Connect-server-implementatie of een ander MFA-type gebruikt, kan de amr claim een andere waarde hebben. De code moet ook worden uitgebreid om dit te accepteren.

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 het programmabestand wordt de AddOpenIdConnect methode gebruikt als het standaarduitdagingsschema. De autorisatiehandler, die wordt gebruikt om de amr claim te controleren, wordt toegevoegd aan de Inversie van Controle-container. Er wordt vervolgens een beleid gemaakt waarmee de RequireMfa vereiste wordt toegevoegd.

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();

Dit beleid wordt vervolgens indien nodig gebruikt op de Razor pagina. Het beleid kan ook wereldwijd worden toegevoegd voor de hele app.

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

Als de gebruiker zich zonder MFA verifieert, heeft de amr claim waarschijnlijk een pwd waarde. De aanvraag wordt niet geautoriseerd voor toegang tot de pagina. Met behulp van de standaardwaarden wordt de gebruiker omgeleid naar de pagina Account/AccessDenied. Dit gedrag kan worden gewijzigd of u kunt hier uw eigen aangepaste logica implementeren. In dit voorbeeld wordt er een koppeling toegevoegd, zodat de geldige gebruiker MFA voor zijn account kan instellen.

@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>

Nu hebben alleen gebruikers die verifiëren met MFA toegang tot de pagina of website. Als er verschillende MFA-typen worden gebruikt of als 2FA in orde is, heeft de claim amr verschillende waarden en moet deze correct worden verwerkt. Verschillende OpenID Connect-servers retourneren ook verschillende waarden voor deze claim en volgen mogelijk niet de referentiewaarden voor verificatiemethode specificatie.

Wanneer u zich aanmeldt zonder MFA (bijvoorbeeld met behulp van alleen een wachtwoord):

  • De amr heeft de pwd waarde:

    amr heeft de pwd-waarde

  • De toegang wordt geweigerd:

    toegang wordt geweigerd

U kunt zich ook aanmelden met een OTP via Identity:

aanmelden met OTP met Identity

Aanvullende informatiebronnen

Door Damien Bowden

Voorbeeldcode (damienbod/AspNetCoreHybridFlowWithApi GitHub-opslagplaats) weergeven of downloaden

Meervoudige verificatie (MFA) is een proces waarbij een gebruiker tijdens een inlogmoment wordt gevraagd om aanvullende vormen van identificatie. Deze prompt kan zijn om een code van een mobiele telefoon in te voeren, een FIDO2-toets te gebruiken of om een vingerafdrukscan op te geven. Wanneer u een tweede vorm van verificatie nodig hebt, wordt de beveiliging verbeterd. De extra factor wordt niet eenvoudig verkregen of gedupliceerd door een cyberaanval.

In dit artikel worden de volgende gebieden behandeld:

  • Wat is MFA en welke MFA-stromen worden aanbevolen
  • MFA configureren voor beheerpagina's met behulp van ASP.NET Core Identity
  • MFA-aanmeldingsvereiste verzenden naar OpenID Connect-server
  • Dwing ASP.NET Core OpenID Connect-client om MFA te vereisen

MFA, 2FA

MFA vereist ten minste twee of meer soorten bewijs voor een identiteit, zoals iets dat u weet, iets dat u bezit of biometrische validatie voor de gebruiker om te verifiëren.

Tweeledige verificatie (2FA) is vergelijkbaar met een subset van MFA, maar het verschil is dat MFA twee of meer factoren kan vereisen om de identiteit te bewijzen.

MFA TOTP (eenmalige wachtwoordalgoritmen op basis van tijd)

MFA met TOTP is een ondersteunde implementatie met behulp van ASP.NET Core Identity. Dit kan samen met elke compatibele authenticator-app worden gebruikt, waaronder:

  • Microsoft Authenticator app
  • Google Authenticator app

Zie de volgende koppeling voor implementatiedetails:

HET genereren van QR-code inschakelen voor TOTP Authenticator-apps in ASP.NET Core

MFA-wachtwoordsleutels/FIDO2 of wachtwoordloos

passkeys/FIDO2 is momenteel:

  • De veiligste manier om MFA te bereiken.
  • MFA die bescherming biedt tegen phishingaanvallen. (Evenals certificaatverificatie en Windows voor Bedrijven)

Op dit moment biedt ASP.NET Core geen ondersteuning voor wachtwoordsleutels/FIDO2. Wachtwoordsleutels/FIDO2 kunnen worden gebruikt voor MFA- of wachtwoordloze stromen.

Microsoft Entra ID biedt ondersteuning voor sleutels/FIDO2 en wachtwoordloze procedures. Zie opties voor verificatie zonder wachtwoordvoor meer informatie.

Andere vormen van wachtwoordloze MFA beschermen niet of mogelijk niet tegen phishing.

MFA SMS

MFA met SMS verhoogt de beveiliging enorm vergeleken met wachtwoordverificatie (één factor). Het gebruik van sms als een tweede factor wordt echter niet meer aanbevolen. Er bestaan te veel bekende aanvalsvectoren voor dit type implementatie.

NIST-richtlijnen

MFA configureren voor beheerpagina's met behulp van ASP.NET Core Identity

Binnen een ASP.NET Core Identity-app kunnen MFA worden verplicht bij gebruikers voor toegang tot gevoelige pagina's. Dit kan handig zijn voor apps waarbij verschillende toegangsniveaus bestaan voor de verschillende identiteiten. Gebruikers kunnen bijvoorbeeld de profielgegevens bekijken met behulp van een wachtwoordaanmelding, maar een beheerder moet MFA gebruiken voor toegang tot de beheerpagina's.

De aanmelding uitbreiden met een MFA-claim

De democode wordt ingesteld met ASP.NET Core met Identity en Razor Pages. De AddIdentity methode wordt gebruikt in plaats van AddDefaultIdentity, zodat een IUserClaimsPrincipalFactory implementatie kan worden gebruikt om claims toe te voegen aan de identiteit na een geslaagde aanmelding.

Waarschuwing

In dit artikel wordt het gebruik van verbindingsreeksen beschreven. Met een lokale database hoeft de gebruiker niet te worden geverifieerd, maar in productie bevatten verbindingsreeksen soms een wachtwoord om te verifiëren. Een wachtwoordinloggegeven voor een resource-eigenaar (ROPC) is een beveiligingsrisico dat moet worden vermeden in productiedatabases. Productie-apps moeten gebruikmaken van de veiligste verificatiestroom die beschikbaar is. Zie Beveiligde verificatiestromenvoor meer informatie over verificatie voor apps die zijn geïmplementeerd voor test- of productieomgevingen.

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();
}

De AdditionalUserClaimsPrincipalFactory-klasse voegt de amr-claim alleen toe aan de gebruikersclaims na een geslaagde aanmelding. De waarde van de claim wordt gelezen uit de database. De claim wordt hier toegevoegd omdat de gebruiker alleen toegang moet krijgen tot de hogere beveiligde weergave als de identiteit is aangemeld met MFA. Als de databaseweergave rechtstreeks uit de database wordt gelezen in plaats van de claim te gebruiken, is het mogelijk om rechtstreeks na het activeren van de MFA toegang te krijgen tot de weergave zonder MFA.

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;
        }
    }
}

Omdat de installatie van de Identity-service in de klasse Startup is gewijzigd, moeten de indelingen van de Identity worden bijgewerkt. Scaffold de Identity pagina's in de app. Definieer de indeling in het Identity/Account/Manage/_Layout.cshtml-bestand.

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

Wijs ook de indeling toe voor alle beheerpagina's van de Identity pagina's:

@{
    Layout = "_Layout.cshtml";
}

Valideer de MFA-vereiste op de beheerpagina

De beheerpagina Razor bevestigt dat de gebruiker is ingelogd met Multi-Factor Authenticatie. In de OnGet-methode wordt de identiteit gebruikt voor toegang tot de gebruikersclaims. De amr claim wordt gecontroleerd op de waarde mfa. Als het attribuut ontbreekt of als de identiteit falseis, wordt de pagina omgeleid naar de pagina voor het inschakelen van MFA. Dit is mogelijk omdat de gebruiker zich al heeft aangemeld, maar zonder 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();
        }
    }
}

UI-logica voor het in-/uitschakelen van gebruikersaanmeldingsgegevens

Er is een autorisatiebeleid toegevoegd aan het programmabestand. Het beleid vereist de amr claim met de waarde mfa.

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

Dit beleid kan vervolgens worden gebruikt in de weergave _Layout om het menu Admin weer te geven of te verbergen met de waarschuwing:

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

Als de identiteit zich heeft aangemeld met MFA, wordt het menu Admin weergegeven zonder de tooltipwaarschuwing. Wanneer de gebruiker zich heeft aangemeld zonder MFA, wordt het Admin (Niet ingeschakeld) menu weergegeven, samen met de knopinfo waarmee de gebruiker wordt geïnformeerd (waarin de waarschuwing wordt uitgelegd).

@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>
    }
}

Als de gebruiker zich aanmeldt zonder MFA, wordt de waarschuwing weergegeven:

MFA-verificatie van beheerders

De gebruiker wordt omgeleid naar de weergave MFA inschakelen wanneer u op de koppeling Admin klikt:

administrator MFA-verificatie activeert

MFA-aanmeldingsvereiste verzenden naar OpenID Connect-server

De parameter acr_values kan worden gebruikt om de mfa vereiste waarde van de client door te geven aan de server in een verificatieaanvraag.

Notitie

De parameter acr_values moet worden verwerkt op de OpenID Connect-server om dit te laten werken.

OpenID Connect ASP.NET Core-client

De ASP.NET Core Razor Pages OpenID Connect-client-app maakt gebruik van de AddOpenIdConnect methode om u aan te melden bij de OpenID Connect-server. De parameter acr_values wordt ingesteld met de mfa waarde en verzonden met de verificatieaanvraag. De OpenIdConnectEvents wordt gebruikt om dit toe te voegen.

Zie Referentiewaarden voor verificatiemethodevoor aanbevolen acr_values parameterwaarden.

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);
            }
        };
    });

Voorbeeld van OpenID Connect IdentityServer 4-server met ASP.NET Core Identity

Op de OpenID Connect-server, die wordt geïmplementeerd met behulp van ASP.NET Core Identity met MVC-weergaven, wordt een nieuwe weergave met de naam ErrorEnable2FA.cshtml gemaakt. Het uitzicht:

  • Geeft weer of de Identity afkomstig is van een app waarvoor MFA is vereist, maar de gebruiker dit niet heeft geactiveerd in Identity.
  • Informeert de gebruiker en voegt een koppeling toe om deze te activeren.
@{
    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 de Login methode wordt de IIdentityServerInteractionService interface-implementatie _interaction gebruikt voor toegang tot de OpenID Connect-aanvraagparameters. De parameter acr_values wordt geopend met behulp van de eigenschap AcrValues. Wanneer de client dit met mfa set heeft verzonden, kan dit vervolgens worden gecontroleerd.

Als MFA is vereist en de gebruiker in ASP.NET Core Identity MFA heeft ingeschakeld, wordt de aanmelding voortgezet. Wanneer de gebruiker geen MFA heeft ingeschakeld, wordt de gebruiker omgeleid naar de aangepaste weergave ErrorEnable2FA.cshtml. Vervolgens ASP.NET Core Identity de gebruiker aanmeldt.

//
// 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

De methode ExternalLoginCallback werkt als de lokale Identity aanmelding. De eigenschap AcrValues wordt gecontroleerd op de waarde mfa. Als de mfa waarde aanwezig is, wordt MFA geforceerd voordat de aanmelding is voltooid (bijvoorbeeld omgeleid naar de weergave 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

Als de gebruiker al is aangemeld, doet de client-app het volgende:

  • Valideert nog steeds de amr claim.
  • Kan de MFA instellen met een link naar de ASP.NET Core Identity-weergave.

acr_values-1 afbeelding

Dwing ASP.NET Core OpenID Connect-client tot het vereisen van MFA

In dit voorbeeld ziet u hoe een ASP.NET Core Razor Page-app, die Gebruikmaakt van OpenID Connect om zich aan te melden, kan vereisen dat gebruikers zijn geverifieerd met behulp van MFA.

Als u de MFA-vereiste wilt valideren, wordt er een IAuthorizationRequirement vereiste gemaakt. Dit wordt toegevoegd aan de pagina's met behulp van een beleid waarvoor MFA is vereist.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc
{
    public class RequireMfa : IAuthorizationRequirement{}
}

Er wordt een AuthorizationHandler geïmplementeerd die de amr claim gebruikt en controleert op de waarde mfa. De amr wordt geretourneerd in het id_token van een geslaagde verificatie en kan veel verschillende waarden hebben, zoals gedefinieerd in de referentiewaarden voor verificatiemethode specificatie.

De geretourneerde waarde is afhankelijk van de verificatie van de identiteit en van de implementatie van de OpenID Connect-server.

De AuthorizationHandler maakt gebruik van de RequireMfa vereiste en valideert de amr claim. De OpenID Connect-server kan worden geïmplementeerd met behulp van IdentityServer4 met ASP.NET Core Identity. Wanneer een gebruiker zich aanmeldt met TOTP, wordt de amr claim geretourneerd met een MFA-waarde. Als u een andere OpenID Connect-server-implementatie of een ander MFA-type gebruikt, kan de amr claim een andere waarde hebben. De code moet ook worden uitgebreid om dit te accepteren.

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 de methode Startup.ConfigureServices wordt de AddOpenIdConnect methode gebruikt als het standaardvraagschema. De autorisatiehandler, die wordt gebruikt om de amr-claim te controleren, wordt toegevoegd aan de Inversion of Control container. Er wordt vervolgens een beleid gemaakt waarmee de RequireMfa vereiste wordt toegevoegd.

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();
}

Dit beleid wordt vervolgens indien nodig gebruikt op de Razor pagina. Het beleid kan ook wereldwijd worden toegevoegd voor de hele app.

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

Als de gebruiker zich zonder MFA verifieert, heeft de amr claim waarschijnlijk een pwd waarde. De aanvraag wordt niet geautoriseerd voor toegang tot de pagina. Met behulp van de standaardwaarden wordt de gebruiker omgeleid naar de pagina Account/AccessDenied. Dit gedrag kan worden gewijzigd of u kunt hier uw eigen aangepaste logica implementeren. In dit voorbeeld wordt er een koppeling toegevoegd, zodat de geldige gebruiker MFA voor zijn account kan instellen.

@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>

Nu hebben alleen gebruikers die verifiëren met MFA toegang tot de pagina of website. Als er verschillende MFA-typen worden gebruikt of als 2FA in orde is, heeft de claim amr verschillende waarden en moet deze correct worden verwerkt. Verschillende OpenID Connect-servers retourneren ook verschillende waarden voor deze claim en volgen mogelijk niet de referentiewaarden voor verificatiemethode specificatie.

Wanneer u zich aanmeldt zonder MFA (bijvoorbeeld met behulp van alleen een wachtwoord):

  • De amr heeft de pwd waarde:

    amr heeft de pwd-waarde

  • De toegang wordt geweigerd:

    toegang wordt geweigerd

U kunt zich ook aanmelden via OTP met Identity:

aanmelden met OTP met Identity

Aanvullende informatiebronnen