Condividi tramite


Autenticazione a più fattori in ASP.NET Core

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Di Damien Bowden

Visualizzare o scaricare il codice di esempio (repository GitHub damienbod/AspNetCoreHybridFlowWithApi)

L'autenticazione a più fattori (MFA) è un processo in cui un utente viene richiesto durante un evento di accesso per ulteriori forme di identificazione. Questo prompt potrebbe essere quello di immettere un codice da un cellulare, usare un tasto FIDO2 o per fornire un'analisi delle impronte digitali. Quando è necessaria una seconda forma di autenticazione, la sicurezza viene migliorata. Il fattore aggiuntivo non è facilmente ottenuto o duplicato da un cyberattacker.

Questo articolo illustra le aree seguenti:

  • Che cos'è MFA e quali flussi MFA sono consigliati
  • Configurare MFA per le pagine di amministrazione usando ASP.NET Core Identity
  • Inviare il requisito di accesso MFA al server OpenID Connect
  • Forzare il client ASP.NET Core OpenID Connect a richiedere l'autenticazione a più fattori

MFA (Autenticazione a più fattori), 2FA (Autenticazione a due fattori)

L'autenticazione a più fattori richiede almeno due o più tipi di prova per un'identità come qualcosa di noto, qualcosa di cui si dispone o una convalida biometrica per l'autenticazione dell'utente.

L'autenticazione a due fattori (2FA) è simile a un subset di MFA, ma la differenza consiste nel fatto che l'autenticazione a più fattori può richiedere due o più fattori per dimostrare l'identità.

2FA è supportato per impostazione predefinita quando si usa ASP.NET Core Identity. Per abilitare o disabilitare 2FA per un utente specifico, impostare la IdentityUser<TKey>.TwoFactorEnabled proprietà . L'interfaccia utente Identity predefinita di base ASP.NET include pagine per la configurazione di 2FA.

Autenticazione MFA TOTP (algoritmo di password monouso basato sul tempo)

L'autenticazione a più fattori tramite TOTP è supportata per impostazione predefinita quando si usa ASP.NET Core Identity. Questo approccio può essere usato insieme a qualsiasi app di autenticazione conforme, tra cui:

  • Microsoft Authenticator
  • Google Authenticator

Per informazioni dettagliate sull'implementazione, vedere Abilitare la generazione di codice QR per le app di autenticazione TOTP in ASP.NET Core.

Per disabilitare il supporto per MFA TOTP, configurare l'autenticazione usando AddIdentity anziché AddDefaultIdentity. AddDefaultIdentity chiama AddDefaultTokenProviders internamente, che registra più provider di token, tra cui uno per MFA TOTP. Per registrare solo provider di token specifici, chiamare AddTokenProvider per ogni provider necessario. Per ulteriori informazioni sui provider di token disponibili, consultare la fonte di riferimento AddDefaultTokenProviders (repository GitHubdotnet/aspnetcore).

Nota

I collegamenti della documentazione al codice sorgente di riferimento .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo corrente per la prossima versione di .NET. Per selezionare un tag per una versione specifica, utilizzare il menu a discesa Cambia rami o tag. Per ulteriori informazioni, vedere Come selezionare un tag di versione del codice sorgente di ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Passkey MFA/FIDO2 o senza password

passkeys/FIDO2 è attualmente:

  • Il modo più sicuro per ottenere l'autenticazione a più fattori.
  • MFA che protegge dagli attacchi di phishing. (Oltre all'autenticazione del certificato e a Windows per le aziende)

Attualmente, ASP.NET Core non supporta direttamente passkeys/FIDO2. Passkeys/FIDO2 può essere usato per i flussi MFA o senza password.

Microsoft Entra ID fornisce supporto per le passkey/FIDO2 e i meccanismi senza password. Per altre informazioni, vedere Opzioni di autenticazione senza password.

Altre forme di autenticazione a più fattori senza password non proteggono o meno dal phishing.

MFA SMS

L'autenticazione a più fattori con SMS aumenta notevolmente la sicurezza rispetto all'autenticazione della password (fattore singolo). Tuttavia, l'uso di SMS come secondo fattore non è più consigliato. Per questo tipo di implementazione esistono troppi vettori di attacco noti.

Linee guida NIST

Configurare MFA per le pagine di amministrazione usando ASP.NET Core Identity

L'autenticazione a più fattori potrebbe essere richiesta agli utenti per accedere alle pagine sensibili di un'app ASP.NET Core Identity. Ciò può essere utile per le app in cui esistono diversi livelli di accesso per le diverse identità. Ad esempio, gli utenti potrebbero essere in grado di visualizzare i dati del profilo usando un account di accesso con password, ma un amministratore deve usare l'autenticazione a più fattori per accedere alle pagine amministrative.

Estendere l'account di accesso con un'attestazione MFA

Il codice demo viene configurato usando ASP.NET Core con Identity e Razor Pages. Il metodo AddIdentity viene usato invece di AddDefaultIdentity uno, quindi è possibile usare un'implementazione IUserClaimsPrincipalFactory per aggiungere attestazioni all'identità dopo un accesso riuscito.

Avviso

Questo articolo illustra l'uso di stringa di connessione. Con un database locale l'utente non deve essere autenticato, ma nell'ambiente di produzione stringa di connessione talvolta include una password per l'autenticazione. Una credenziale password del proprietario della risorsa (ROPC) è un rischio per la sicurezza che andrebbe evitato nei database di produzione. Le app di produzione devono usare il flusso di autenticazione più sicuro disponibile. Per altre informazioni sull'autenticazione per le app distribuite in ambienti di test o di produzione, vedere Proteggere i flussi di autenticazione.

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

La AdditionalUserClaimsPrincipalFactory classe aggiunge l'attestazione amr alle attestazioni utente solo dopo un accesso riuscito. Il valore dell'attestazione viene letto dal database. La richiesta di autenticazione viene aggiunta qui perché l'utente può accedere alla vista protetta avanzata solo se l'identità ha effettuato l'accesso con autenticazione multi-fattore (MFA). Se la vista del database viene letta direttamente dal database anziché usare l'attestazione, è possibile accedere alla vista senza MFA direttamente dopo l'attivazione dell'autenticazione a più fattori.

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

Poiché l'installazione del servizio Identity è stata modificata nella classe Startup, è necessario aggiornare i layout di Identity. Generare le Identity pagine nell'app. Definire il layout nel Identity/Account/Manage/_Layout.cshtml file.

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

Inoltre, assegnare il layout a tutte le pagine di gestione dalle pagine Identity.

@{
    Layout = "_Layout.cshtml";
}

Convalidare il requisito MFA nella pagina di amministrazione

La pagina di amministrazione Razor verifica che l'utente abbia eseguito l'accesso tramite MFA. Nel metodo OnGet l'identità viene usata per accedere alle attestazioni utente. L'attestazione amr viene verificata per il valore mfa. Se all'identità manca questa dichiarazione o è false, la pagina viene reindirizzata alla pagina Abilita autenticazione a più fattori. Ciò è possibile perché l'utente ha già eseguito l'accesso, ma senza 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();
        }
    }
}

Logica dell'interfaccia utente per attivare o disattivare le informazioni di accesso utente

All'avvio è stato aggiunto un criterio di autorizzazione. La politica richiede la dichiarazione amr con il valore mfa.

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

Questo criterio può quindi essere usato nella _Layout visualizzazione per visualizzare o nascondere il menu Admin con l'avviso:

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

Se l'identità ha eseguito l'accesso tramite MFA, il menu amministratore viene visualizzato senza l'avviso tooltip. Quando l'utente ha eseguito l'accesso senza MFA, il menu Admin (Not Enabled) viene visualizzato insieme alla descrizione comando che informa l'utente (fornendo la spiegazione dell'avviso).

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

Se l'utente accede senza MFA, viene visualizzato l'avviso:

Autenticazione MFA dell'amministratore

L'utente viene reindirizzato alla pagina di abilitazione dell'autenticazione a due fattori quando si fa clic sul link Amministratore :

L'amministratore attiva l'autenticazione MFA

Inviare il requisito di accesso MFA al server OpenID Connect

Il acr_values parametro può essere usato per passare il mfa valore richiesto dal client al server in una richiesta di autenticazione.

Nota

Il acr_values parametro deve essere gestito nel server OpenID Connect per il funzionamento.

Client OpenID Connect ASP.NET Core

L'app client OpenID Connect di ASP.NET Core Razor Pages usa il AddOpenIdConnect metodo per accedere al server OpenID Connect. Il acr_values parametro viene impostato con il mfa valore e inviato con la richiesta di autenticazione. L'oggetto OpenIdConnectEvents viene utilizzato per aggiungere questo.

Per i valori dei parametri consigliati acr_values , vedere Valori di riferimento del metodo di autenticazione.

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

Esempio di server OpenID Connect Duende IdentityServer con ASP.NET Core Identity

Nel server OpenID Connect, implementato tramite ASP.NET Core Identity con Razor Pages, viene creata una nuova pagina denominata ErrorEnable2FA.cshtml . Visualizzazione :

  • Visualizza se Identity proviene da un'app che richiede l'autenticazione multi-fattore, ma l'utente non l'ha attivata in Identity.
  • Informa l'utente e aggiunge un collegamento per attivarlo.
@{
    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>

Login Nel metodo viene usata l'implementazione dell'interfaccia IIdentityServerInteractionService_interaction per accedere ai parametri della richiesta OpenID Connect. Il parametro acr_values viene accesso usando la proprietà AcrValues. Quando il client lo ha inviato con mfa set, è quindi possibile controllarne il contenuto.

Se è necessaria l'autenticazione a più fattori e l'utente in ASP.NET Core Identity ha abilitato MFA, allora il login può procedere. Quando l'utente non dispone di MFA abilitata, l'utente viene reindirizzato alla visualizzazione ErrorEnable2FA.cshtmlpersonalizzata . Poi ASP.NET Core Identity autentica l'utente.

Fido2Store viene usato per verificare se l'utente ha attivato MFA usando un provider di token FIDO2 personalizzato.

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

Se l'utente è già collegato, l'app cliente:

  • Convalida comunque l'attestazione amr .
  • È possibile configurare l'autenticazione a più fattori con un collegamento alla vista ASP.NET Core Identity.

immagine acr_values-1

Forzare il client OpenID Connect di ASP.NET Core a richiedere l'autenticazione multi-fattore

Questo esempio mostra come un'app ASP.NET Core Razor Page, che usa OpenID Connect per accedere, può richiedere che gli utenti abbiano eseguito l'autenticazione tramite MFA.

Per convalidare il requisito MFA, viene creato un IAuthorizationRequirement requisito. Questa operazione verrà aggiunta alle pagine usando un criterio che richiede l'autenticazione a più fattori.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc;

public class RequireMfa : IAuthorizationRequirement{}

Viene implementato un AuthorizationHandler che userà l'attestazione amr e controllerà il valore mfa. Il amr viene restituito nel id_token di un'autenticazione riuscita e può avere molti valori diversi, come definito nella specifica Valori di riferimento del metodo di autenticazione.

Il valore restituito dipende dalla modalità di autenticazione dell'identità e dall'implementazione del server OpenID Connect.

AuthorizationHandler usa il RequireMfa requisito e convalida l'attestazioneamr. Il server OpenID Connect può essere implementato usando Duende Identity Server con ASP.NET Core Identity. Quando un utente accede con TOTP, l'attestazione amr viene restituita con un valore MFA. Se si utilizza un'implementazione del server OpenID Connect diversa o un tipo di MFA diverso, l'attestazione amr potrà, o potrà, avere un valore diverso. Il codice deve essere esteso anche per accettarlo.

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

Nel file di programma il AddOpenIdConnect metodo viene usato come schema di verifica predefinito. Il gestore di autorizzazione, usato per controllare l'attestazione amr , viene aggiunto al contenitore Inversion of Control. Viene quindi creato un criterio che aggiunge il RequireMfa requisito.

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

Questo criterio viene quindi usato nella Razor pagina in base alle esigenze. I criteri possono essere aggiunti a livello globale anche per l'intera app.

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

Se l'utente esegue l'autenticazione senza MFA, l'attestazione amr avrà probabilmente un pwd valore. La richiesta non sarà autorizzata ad accedere alla pagina. Usando i valori predefiniti, l'utente verrà reindirizzato alla pagina Account/AccessDenied . Questo comportamento può essere modificato oppure è possibile implementare la propria logica personalizzata qui. In questo esempio viene aggiunto un collegamento in modo che l'utente valido possa configurare MFA per il proprio account.

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

Ora solo gli utenti che eseguono l'autenticazione con MFA possono accedere alla pagina o al sito Web. Se vengono usati diversi tipi di autenticazione a più fattori o se 2FA va bene, la dichiarazione amr avrà valori diversi e deve essere elaborata correttamente. I diversi server OpenID Connect restituiscono anche valori diversi per questa attestazione e potrebbero non seguire la specifica Valori di riferimento del metodo di autenticazione.

Quando si esegue l'accesso senza autenticazione a più fattori(ad esempio, usando solo una password):

  • Il amr ha il valore pwd:

    amr ha il valore pwd

  • Accesso negato:

    Accesso negato

In alternativa, accedere usando OTP con Identity:

Accesso con OTP Identity

Personalizzazione dei parametri OIDC e OAuth

L'opzione dei gestori di autenticazione OAuth e OIDC AdditionalAuthorizationParameters consente di personalizzare i parametri dei messaggi di autorizzazione che sono solitamente inclusi come parte della stringa di query del reindirizzamento:

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

Risorse aggiuntive

Di Damien Bowden

Visualizzare o scaricare il codice di esempio (repository GitHub damienbod/AspNetCoreHybridFlowWithApi)

L'autenticazione a più fattori (MFA) è un processo in cui un utente viene richiesto durante un evento di accesso per ulteriori forme di identificazione. Questo prompt potrebbe essere quello di immettere un codice da un cellulare, usare un tasto FIDO2 o per fornire un'analisi delle impronte digitali. Quando è necessaria una seconda forma di autenticazione, la sicurezza viene migliorata. Il fattore aggiuntivo non è facilmente ottenuto o duplicato da un cyberattacker.

Questo articolo illustra le aree seguenti:

  • Che cos'è MFA e quali flussi MFA sono consigliati
  • Configurare MFA per le pagine di amministrazione usando ASP.NET Core Identity
  • Inviare il requisito di accesso MFA al server OpenID Connect
  • Forzare il client di ASP.NET Core OpenID Connect a richiedere l'autenticazione a più fattori

MFA (Autenticazione a più fattori), 2FA (Autenticazione a due fattori)

L'autenticazione a più fattori richiede almeno due o più tipi di prova per un'identità come qualcosa di noto, qualcosa di cui si dispone o una convalida biometrica per l'autenticazione dell'utente.

L'autenticazione a due fattori (2FA) è simile a un subset di MFA, ma la differenza consiste nel fatto che l'autenticazione a più fattori può richiedere due o più fattori per dimostrare l'identità.

2FA è supportato per impostazione predefinita quando si usa ASP.NET Core Identity. Per abilitare o disabilitare 2FA per un utente specifico, impostare la IdentityUser<TKey>.TwoFactorEnabled proprietà . L'interfaccia utente Identity predefinita di base ASP.NET include pagine per la configurazione di 2FA.

MFA TOTP (algoritmo basato sul tempo per password monouso)

L'autenticazione a più fattori tramite TOTP è supportata per impostazione predefinita quando si usa ASP.NET Core Identity. Questo approccio può essere usato insieme a qualsiasi app di autenticazione conforme, tra cui:

  • Microsoft Authenticator
  • Google Authenticator

Per informazioni dettagliate sull'implementazione, vedere Abilitare la generazione di codici QR per le app di autenticazione TOTP in ASP.NET Core.

Per disabilitare il supporto per MFA TOTP, configurare l'autenticazione usando AddIdentity anziché AddDefaultIdentity. AddDefaultIdentity chiama internamente AddDefaultTokenProviders, che poi registra diversi provider di token, inclusi quelli per l'autenticazione MFA TOTP. Per registrare solo provider di token specifici, chiamare AddTokenProvider per ogni provider necessario. Per altre informazioni sui provider di token disponibili, vedere l'origine AddDefaultTokenProviders in GitHub.

Passkey MFA/FIDO2 o senza password

passkeys/FIDO2 è attualmente:

  • Il modo più sicuro per implementare l'autenticazione a più fattori.
  • MFA che protegge dagli attacchi di phishing. (Oltre all'autenticazione del certificato e a Windows per le aziende)

Attualmente, ASP.NET Core non supporta direttamente passkeys/FIDO2. Passkeys/FIDO2 può essere usato per i flussi MFA o senza password.

Microsoft Entra ID fornisce supporto per le chiavi di accesso/FIDO2 e flussi senza l'uso di password. Per altre informazioni, vedere Opzioni di autenticazione senza password.

Altre forme di autenticazione a più fattori senza password non proteggono o meno dal phishing.

MFA SMS

L'autenticazione a più fattori con SMS aumenta notevolmente la sicurezza rispetto all'autenticazione della password (fattore singolo). Tuttavia, l'uso di SMS come secondo fattore non è più consigliato. Per questo tipo di implementazione esistono troppi vettori di attacco noti.

Linee guida NIST

Configurare MFA per le pagine di amministrazione usando ASP.NET Core Identity

L'autenticazione a più fattori potrebbe essere imposta agli utenti per accedere alle pagine sensibili all'interno di un'app ASP.NET Core Identity. Ciò può essere utile per le app in cui esistono diversi livelli di accesso per le diverse identità. Ad esempio, gli utenti potrebbero essere in grado di visualizzare i dati del profilo usando un account di accesso con password, ma un amministratore deve usare l'autenticazione a più fattori per accedere alle pagine amministrative.

Prolungare il login con una dichiarazione MFA

Il codice demo viene configurato usando ASP.NET Core con Identity e Razor Pages. Il metodo AddIdentity viene usato invece di AddDefaultIdentity uno, quindi è possibile usare un'implementazione IUserClaimsPrincipalFactory per aggiungere attestazioni all'identità dopo un accesso riuscito.

Avviso

Questo articolo illustra l'uso di stringa di connessione. Con un database locale l'utente non deve essere autenticato, ma nell'ambiente di produzione stringa di connessione talvolta include una password per l'autenticazione. Una credenziale del proprietario delle risorse (ROPC) rappresenta un rischio per la sicurezza che dovrebbe essere evitato nei database di produzione. Le app di produzione devono usare il flusso di autenticazione più sicuro disponibile. Per altre informazioni sull'autenticazione per le app distribuite in ambienti di test o di produzione, vedere Proteggere i flussi di autenticazione.

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

La AdditionalUserClaimsPrincipalFactory classe aggiunge l'attestazione amr alle attestazioni utente solo dopo un accesso riuscito. Il valore dell'attestazione viene letto dal database. La richiesta di autenticazione viene aggiunta qui perché l'utente può accedere alla vista protetta avanzata solo se l'identità ha effettuato l'accesso con autenticazione multi-fattore (MFA). Se la vista del database viene letta direttamente dal database anziché utilizzare il reclamo, è possibile accedere alla vista senza MFA immediatamente dopo l'attivazione dell'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;
        }
    }
}

Poiché la configurazione del Identity servizio è cambiata nella classe Startup, è necessario aggiornare i layout di Identity. Generare le Identity pagine nell'applicazione. Definire il layout nel Identity/Account/Manage/_Layout.cshtml file.

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

Assegnare anche il layout per tutte le pagine di gestione dalle Identity pagine:

@{
    Layout = "_Layout.cshtml";
}

Convalidare il requisito MFA nella pagina di amministrazione

La pagina di amministrazione Razor verifica che l'utente abbia eseguito l'accesso tramite MFA. Nel metodo OnGet l'identità viene usata per accedere alle attestazioni utente. L'attestazione amr viene verificata per il valore mfa. Se all'identità manca questa dichiarazione o è false, la pagina viene reindirizzata alla pagina Abilita autenticazione a più fattori. Ciò è possibile perché l'utente ha già eseguito l'accesso, ma senza 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();
        }
    }
}

Logica dell'interfaccia utente per attivare o disattivare le informazioni di accesso utente

All'avvio è stato aggiunto un criterio di autorizzazione. La politica richiede l'attestazione amr con il valore mfa.

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

Questo criterio può quindi essere usato nella _Layout visualizzazione per visualizzare o nascondere il menu Admin con l'avviso:

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

Se l'identità ha eseguito l'accesso tramite MFA, il menu amministratore viene visualizzato senza l'avviso tooltip. Quando l'utente ha eseguito l'accesso senza MFA, il menu Admin (Non abilitato) viene visualizzato insieme al tooltip che informa l'utente (spiegando l'avviso).

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

Se l'utente accede senza MFA, viene visualizzato l'avviso:

Autenticazione MFA dell'amministratore

L'utente viene reindirizzato alla schermata di abilitazione MFA quando si fa clic sul link Admin.

L'amministratore attiva l'autenticazione MFA

Inviare il requisito di accesso MFA al server OpenID Connect

Il acr_values parametro può essere usato per passare il mfa valore richiesto dal client al server in una richiesta di autenticazione.

Nota

Il acr_values parametro deve essere gestito nel server OpenID Connect per il funzionamento.

OpenID Connect ASP.NET Core client

L'app client OpenID Connect di ASP.NET Core Razor Pages usa il AddOpenIdConnect metodo per accedere al server OpenID Connect. Il acr_values parametro viene impostato con il mfa valore e inviato con la richiesta di autenticazione. L'oggetto OpenIdConnectEvents viene utilizzato per aggiungere questo.

Per i valori dei parametri consigliati acr_values , vedere Valori di riferimento del metodo di autenticazione.

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

Esempio di server OpenID Connect Duende IdentityServer con ASP.NET Core Identity

Nel server OpenID Connect, implementato tramite ASP.NET Core Identity con Razor Pages, viene creata una nuova pagina denominata ErrorEnable2FA.cshtml . Visualizzazione :

  • Visualizza se il Identity proviene da un'app che richiede l'autenticazione a più fattori, ma l'utente non l'ha attivata in Identity.
  • Informa l'utente e aggiunge un collegamento per attivarlo.
@{
    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>

Login Nel metodo viene usata l'implementazione dell'interfaccia IIdentityServerInteractionService_interaction per accedere ai parametri della richiesta OpenID Connect. Il parametro acr_values è accessibile tramite la proprietà AcrValues. Quando il client lo ha inviato con mfa set, è quindi possibile controllarne il contenuto.

Se è necessaria l'autenticazione a più fattori e l'utente in ASP.NET Core Identity ha l'MFA abilitato, il processo di accesso continua. Quando l'utente non dispone di MFA abilitata, l'utente viene reindirizzato alla visualizzazione ErrorEnable2FA.cshtmlpersonalizzata . Quindi ASP.NET Core Identity autentica l'utente.

Fido2Store viene usato per verificare se l'utente ha attivato MFA usando un provider di token FIDO2 personalizzato.

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

Se l'utente è già acceduto, l'applicazione client:

  • Convalida comunque l'attestazione amr .
  • Può configurare l'MFA con un collegamento alla visualizzazione di ASP.NET Core Identity.

immagine acr_values-1

Forzare il client OpenID Connect di ASP.NET Core a richiedere l'autenticazione a più fattori

Questo esempio mostra come un'app ASP.NET Core Razor Page, che usa OpenID Connect per accedere, può richiedere che gli utenti abbiano eseguito l'autenticazione tramite MFA.

Per convalidare il requisito MFA, viene creato un IAuthorizationRequirement requisito. Questa operazione verrà aggiunta alle pagine usando un criterio che richiede l'autenticazione a più fattori.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc;

public class RequireMfa : IAuthorizationRequirement{}

È stato implementato un AuthorizationHandler che utilizzerà l'attestazione amr e verificherà il valore mfa. L'oggetto amr viene restituito nel id_token di un'autenticazione riuscita e può avere molti valori diversi, come definito nella specifica Valori di riferimento del metodo di autenticazione.

Il valore restituito dipende dalla modalità di autenticazione dell'identità e dall'implementazione del server OpenID Connect.

AuthorizationHandler usa il RequireMfa requisito e convalida l'attestazioneamr. Il server OpenID Connect può essere implementato usando Duende Identity Server con ASP.NET Core Identity. Quando un utente accede con TOTP, l'attestazione amr viene restituita con un valore MFA. Se si utilizza un'implementazione diversa del server OpenID Connect o un tipo di MFA diverso, l'attestazione amr avrà o potrebbe avere un valore diverso. Il codice deve essere esteso anche per accettarlo.

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

Nel file di programma il AddOpenIdConnect metodo viene usato come schema di verifica predefinito. Il gestore di autorizzazione, usato per controllare l'attestazione amr , viene aggiunto al contenitore Inversion of Control. Viene quindi creato un criterio che aggiunge il RequireMfa requisito.

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

Questo criterio viene quindi usato nella Razor pagina in base alle esigenze. I criteri possono essere aggiunti a livello globale anche per l'intera app.

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

Se l'utente esegue l'autenticazione senza MFA, l'attestazione amr avrà probabilmente un pwd valore. La richiesta non sarà autorizzata ad accedere alla pagina. Usando i valori predefiniti, l'utente verrà reindirizzato alla pagina Account/AccessDenied . Questo comportamento può essere modificato oppure è possibile implementare la propria logica personalizzata qui. In questo esempio viene aggiunto un collegamento in modo che l'utente valido possa configurare MFA per il proprio account.

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

Ora solo gli utenti che eseguono l'autenticazione con MFA possono accedere alla pagina o al sito Web. Se vengono utilizzati diversi tipi di autenticazione a più fattori oppure se viene utilizzata l'autenticazione a due fattori (2FA), l'attestazione amr avrà valori differenti e dovrà essere gestita in modo corretto. I diversi server OpenID Connect restituiscono anche valori diversi per questa attestazione e potrebbero non seguire la specifica Valori di riferimento del metodo di autenticazione.

Quando si esegue l'accesso senza autenticazione a più fattori(ad esempio, usando solo una password):

  • amr ha il valore pwd:

    amr ha il valore pwd

  • Accesso negato:

    Accesso negato

In alternativa, accedere usando OTP con Identity:

Accesso con OTP Identity

Risorse aggiuntive

Di Damien Bowden

Visualizzare o scaricare il codice di esempio (repository GitHub damienbod/AspNetCoreHybridFlowWithApi)

L'autenticazione a più fattori (MFA) è un processo in cui un utente viene richiesto durante un evento di accesso per ulteriori forme di identificazione. Questo prompt potrebbe essere quello di immettere un codice da un cellulare, usare un tasto FIDO2 o per fornire un'analisi delle impronte digitali. Quando è necessaria una seconda forma di autenticazione, la sicurezza viene migliorata. Il fattore aggiuntivo non è facilmente ottenuto o duplicato da un cyberattacker.

Questo articolo illustra le aree seguenti:

  • Che cos'è MFA e quali flussi MFA sono consigliati
  • Configurare MFA per le pagine di amministrazione usando ASP.NET Core Identity
  • Inviare il requisito di accesso MFA al server OpenID Connect
  • Forzare il client OpenID Connect di ASP.NET Core a richiedere l'autenticazione a più fattori

MFA (Autenticazione a fattori multipli), 2FA (Autenticazione a due fattori)

L'autenticazione a più fattori richiede almeno due o più tipi di prova per un'identità come qualcosa di noto, qualcosa di cui si dispone o una convalida biometrica per l'autenticazione dell'utente.

L'autenticazione a due fattori (2FA) è simile a un subset di MFA, ma la differenza consiste nel fatto che l'autenticazione a più fattori può richiedere due o più fattori per dimostrare l'identità.

MFA TOTP (algoritmo per password monouso basato sul tempo)

MFA con TOTP è un'implementazione supportata usando ASP.NET Core Identity. Può essere usato insieme a qualsiasi app di autenticazione conforme, tra cui:

  • App Microsoft Authenticator
  • Google Authenticator App

Per informazioni dettagliate sull'implementazione, vedere il collegamento seguente:

Abilitare la generazione di QR Code per le app di autenticazione TOTP in ASP.NET Core

Passkey MFA/FIDO2 o senza password

passkeys/FIDO2 è attualmente:

  • Il modo più sicuro di ottenere l'AMF (Autenticazione a Multi-Fattore).
  • MFA che protegge dagli attacchi di phishing. (Oltre all'autenticazione del certificato e a Windows per le aziende)

Attualmente, ASP.NET Core non supporta direttamente passkeys/FIDO2. Passkeys/FIDO2 può essere usato per i flussi MFA o senza password.

Microsoft Entra ID fornisce il supporto per le chiavi di accesso FIDO2 e i flussi senza password. Per altre informazioni, vedere Opzioni di autenticazione senza password.

Altre forme di autenticazione a più fattori senza password non proteggono o meno dal phishing.

Autenticazione Multi-Fattore tramite SMS

L'autenticazione a più fattori con SMS aumenta notevolmente la sicurezza rispetto all'autenticazione della password (fattore singolo). Tuttavia, l'uso di SMS come secondo fattore non è più consigliato. Per questo tipo di implementazione esistono troppi vettori di attacco noti.

Linee guida NIST

Configurare MFA per le pagine di amministrazione usando ASP.NET Core Identity

L'autenticazione a più fattori potrebbe essere forzata agli utenti per accedere alle pagine sensibili all'interno di un'app ASP.NET Core Identity. Ciò può essere utile per le app in cui esistono diversi livelli di accesso per le diverse identità. Ad esempio, gli utenti potrebbero essere in grado di visualizzare i dati del profilo usando un account di accesso con password, ma un amministratore deve usare l'autenticazione a più fattori per accedere alle pagine amministrative.

Estendere il login con una richiesta MFA

Il codice demo viene configurato usando ASP.NET Core con Identity e Razor Pages. Il metodo AddIdentity viene usato invece di AddDefaultIdentity uno, quindi è possibile usare un'implementazione IUserClaimsPrincipalFactory per aggiungere attestazioni all'identità dopo un accesso riuscito.

Avviso

Questo articolo illustra l'uso di stringa di connessione. Con un database locale l'utente non deve essere autenticato, ma nell'ambiente di produzione stringa di connessione talvolta include una password per l'autenticazione. Una credenziale della password del proprietario della risorsa (ROPC) è un rischio per la sicurezza che deve essere evitato nei database di produzione. Le app di produzione devono usare il flusso di autenticazione più sicuro disponibile. Per altre informazioni sull'autenticazione per le app distribuite in ambienti di test o di produzione, vedere Proteggere i flussi di autenticazione.

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

La AdditionalUserClaimsPrincipalFactory classe aggiunge l'attestazione amr alle attestazioni utente solo dopo un accesso riuscito. Il valore dell'attestazione viene letto dal database. La richiesta di autenticazione viene aggiunta qui perché l'utente può accedere alla vista protetta avanzata solo se l'identità ha effettuato l'accesso con autenticazione multi-fattore (MFA). Se la vista del database viene letta direttamente dal database anziché tramite l'utilizzo di una "claim", è possibile accedere alla vista senza l'autenticazione a più fattori (MFA) subito dopo l'attivazione della 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;
        }
    }
}

Poiché l'installazione del servizio Identity è stata modificata nella classe Startup, è necessario aggiornare i layout dell'oggetto Identity. Eseguire lo scaffolding delle Identity pagine nell'app. Definire il layout nel Identity/Account/Manage/_Layout.cshtml file.

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

Assegna il layout per tutte le pagine di gestione dalle pagine Identity.

@{
    Layout = "_Layout.cshtml";
}

Convalidare il requisito MFA nella pagina di amministrazione

La pagina di amministrazione Razor verifica che l'utente abbia eseguito l'accesso tramite MFA. Nel metodo OnGet l'identità viene usata per accedere alle attestazioni utente. L'attestazione amr viene verificata per il valore mfa. Se all'identità manca questa dichiarazione o è false, la pagina viene reindirizzata alla pagina Abilita autenticazione a più fattori. Ciò è possibile perché l'utente ha già eseguito l'accesso, ma senza 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();
        }
    }
}

Logica dell'interfaccia utente per attivare o disattivare le informazioni di accesso utente

Nel file di programma è stato aggiunto un criterio di autorizzazione. La politica richiede l'attestazione amr con il valore mfa.

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

Questo criterio può quindi essere usato nella _Layout visualizzazione per visualizzare o nascondere il menu Admin con l'avviso:

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

Se l'identità ha eseguito l'accesso tramite MFA, il menu amministratore viene visualizzato senza l'avviso tooltip. Quando l'utente ha eseguito l'accesso senza MFA, il menu Admin (Not Enabled) viene visualizzato insieme al tooltip che informa l'utente (spiegando l'avviso).

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

Se l'utente accede senza MFA, viene visualizzato l'avviso:

Autenticazione MFA dell'amministratore

L'utente viene reindirizzato alla schermata di abilitazione dell'autenticazione a più fattori quando si fa clic sul collegamento Amministratore.

L'amministratore attiva l'autenticazione MFA

Inviare il requisito di accesso MFA al server OpenID Connect

Il acr_values parametro può essere usato per passare il mfa valore richiesto dal client al server in una richiesta di autenticazione.

Nota

Il acr_values parametro deve essere gestito nel server OpenID Connect per il funzionamento.

Client OpenID Connect ASP.NET Core

L'app client OpenID Connect di ASP.NET Core Razor Pages usa il AddOpenIdConnect metodo per accedere al server OpenID Connect. Il acr_values parametro viene impostato con il mfa valore e inviato con la richiesta di autenticazione. L'oggetto OpenIdConnectEvents viene utilizzato per aggiungere questo.

Per i valori dei parametri consigliati acr_values , vedere Valori di riferimento del metodo di autenticazione.

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

Esempio di server OpenID Connect IdentityServer 4 con ASP.NET Core Identity

Nel server OpenID Connect, implementato usando ASP.NET Core Identity con le visualizzazioni MVC, viene creata una nuova visualizzazione denominata ErrorEnable2FA.cshtml . Visualizzazione :

  • Visualizza se il Identity proviene da un'app che richiede l'autenticazione a più fattori, ma l'utente non ha attivato questa funzione in Identity.
  • Informa l'utente e aggiunge un collegamento per attivarlo.
@{
    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>

Login Nel metodo viene usata l'implementazione dell'interfaccia IIdentityServerInteractionService_interaction per accedere ai parametri della richiesta OpenID Connect. Il acr_values parametro è accessibile tramite la proprietà AcrValues. Quando il client lo ha inviato con mfa set, è quindi possibile controllarne il contenuto.

Se è necessaria l'autenticazione a più fattori e l'utente in ASP.NET Core Identity ha abilitato l'autenticazione a più fattori, l'accesso procede. Quando l'utente non dispone di MFA abilitata, l'utente viene reindirizzato alla visualizzazione ErrorEnable2FA.cshtmlpersonalizzata . Quindi ASP.NET Core Identity autentica l'utente.

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

Il metodo ExternalLoginCallback funziona come il login locale Identity. La AcrValues proprietà viene controllata per il mfa valore . Se il valore mfa è presente, l'autenticazione a più fattori viene forzata prima che il login sia completato, ad esempio reindirizzata alla schermata 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

Se l'utente è già connesso, l'applicazione client:

  • Convalida comunque l'attestazione amr .
  • È possibile configurare l'autenticazione a più fattori mediante un collegamento alla vista di ASP.NET Core Identity.

immagine acr_values-1

Forzare ASP.NET client OpenID Connect core per richiedere l'autenticazione a più fattori

Questo esempio mostra come un'app ASP.NET Core Razor Page, che usa OpenID Connect per accedere, può richiedere che gli utenti abbiano eseguito l'autenticazione tramite MFA.

Per convalidare il requisito MFA, viene creato un IAuthorizationRequirement requisito. Questa operazione verrà aggiunta alle pagine usando un criterio che richiede l'autenticazione a più fattori.

using Microsoft.AspNetCore.Authorization;

namespace AspNetCoreRequireMfaOidc
{
    public class RequireMfa : IAuthorizationRequirement{}
}

Viene implementata una funzione che userà l'attestazione amr e verificherà il valore mfa. L'oggetto amr viene restituito nella id_token di un'autenticazione riuscita e può avere molti valori diversi, come definito nella specifica dei Valori di Riferimento del Metodo di Autenticazione.

Il valore restituito dipende dalla modalità di autenticazione dell'identità e dall'implementazione del server OpenID Connect.

AuthorizationHandler usa il RequireMfa requisito e convalida l'attestazioneamr. Il server OpenID Connect può essere implementato usando IdentityServer4 con ASP.NET Core Identity. Quando un utente accede con TOTP, l'attestazione amr viene restituita con un valore MFA. Se si utilizza un'implementazione del server OpenID Connect diversa o un tipo di MFA diverso, l'attestazione amr avrà, o potrà avere, un valore diverso. Il codice deve essere esteso anche per accettarlo.

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

Nel metodo Startup.ConfigureServices, il metodo AddOpenIdConnect viene usato come schema di verifica predefinito. Il gestore di autorizzazione, usato per controllare l'attestazione amr , viene aggiunto al contenitore Inversion of Control. Viene quindi creato un criterio che aggiunge il RequireMfa requisito.

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

Questo criterio viene quindi usato nella Razor pagina in base alle esigenze. I criteri possono essere aggiunti a livello globale anche per l'intera app.

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

Se l'utente esegue l'autenticazione senza MFA, l'attestazione amr avrà probabilmente un pwd valore. La richiesta non sarà autorizzata ad accedere alla pagina. Usando i valori predefiniti, l'utente verrà reindirizzato alla pagina Account/AccessDenied . Questo comportamento può essere modificato oppure è possibile implementare la propria logica personalizzata qui. In questo esempio viene aggiunto un collegamento in modo che l'utente valido possa configurare MFA per il proprio account.

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

Ora solo gli utenti che eseguono l'autenticazione con MFA possono accedere alla pagina o al sito Web. Se vengono usati diversi tipi di autenticazione a più fattori o se è accettabile usare 2FA, l'attestazione amr avrà valori diversi e deve essere elaborata correttamente. I diversi server OpenID Connect restituiscono anche valori diversi per questa attestazione e potrebbero non seguire la specifica Valori di riferimento del metodo di autenticazione.

Quando si esegue l'accesso senza autenticazione a più fattori(ad esempio, usando solo una password):

  • L'amr ha il valore pwd:

    amr ha il valore pwd

  • Accesso negato:

    Accesso negato

In alternativa, accedere usando OTP con Identity:

Accesso con OTP Identity

Risorse aggiuntive