La sicurezza delle applicazioni web moderne non può più prescindere dall’autenticazione a più fattori (MFA). Con il rilascio di .NET 10, Microsoft continua a consolidare l’ecosistema di ASP.NET Core Identity, offrendo strumenti sempre più potenti, flessibili e integrati per proteggere gli account utente da accessi non autorizzati.
In questo articolo esploreremo come progettare e implementare un flusso MFA basato su Time-based One-Time Passwords (TOTP) — lo standard utilizzato da app come Google Authenticator, Microsoft Authenticator o Authy — all’interno di una Web API o di un’applicazione MVC/Razor Pages in .NET 10.
1. Prerequisiti e Configurazione Iniziale
Per seguire questa guida, assicurati di avere installato sul tuo sistema:
- .NET 10 SDK
- Un IDE compatibile (come Visual Studio 2026 o Visual Studio Code)
Nel tuo progetto ASP.NET Core 10, l’infrastruttura di base è gestita da Microsoft.AspNetCore.Identity. Se stai partendo da zero, puoi configurare il contesto del database e l’Identity di default nel file Program.cs:
builder.Services.AddDefaultIdentity<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = true;
// Configurazione dei requisiti delle password
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
})
.AddEntityFrameworkStores<ApplicationDbContext>();
2. Abilitazione e Generazione del Secret TOTP
Il primo passo per consentire a un utente di configurare l’MFA è generare una chiave segreta (Secret Key) univoca associata al suo account e visualizzarla sotto forma di QR Code.
All’interno di un controller dedicato alla gestione dell’account (ManageController), possiamo iniettare UserManager<IdentityUser> e gestire la generazione del secret:
[HttpPost]
[Authorize]
async public Task<IActionResult> EnableAuthenticator()
{
var user = await _userManager.GetUserAsync(User);
if (user == null) return NotFound();
// Genera il token e la chiave segreta per l'autenticatore
await _userManager.ResetAuthenticatorKeyAsync(user);
var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
var email = await _userManager.GetEmailAsync(user);
// Formatta la stringa URI compatibile con le app Authenticator (otpauth://)
var authenticatorUri = string.Format(
CultureInfo.InvariantCulture,
AuthenticatorUriFormat,
UrlEncoder.Encode("NomeAppTua"),
UrlEncoder.Encode(email),
unformattedKey);
return View(new EnableAuthenticatorViewModel
{
SharedKey = FormatKey(unformattedKey),
AuthenticatorUri = authenticatorUri
});
}
3. Verifica del Codice e Attivazione della MFA
Una volta che l’utente ha scansionato il QR Code con la propria applicazione mobile, l’applicazione deve verificare che il codice inserito dall’utente sia corretto prima di attivare definitivamente l’MFA sul database.
[HttpPost]
[Authorize]
async public Task<IActionResult> EnableAuthenticator(EnableAuthenticatorViewModel model)
{
var user = await _userManager.GetUserAsync(User);
if (user == null) return NotFound();
// Rimuove gli spazi e i trattini dal codice inserito
var verificationCode = model.Code.Replace(" ", string.Empty).DataTrim();
// Verifica il codice TOTP usando l'userManager
var is2FaTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
if (!is2FaTokenValid)
{
ModelState.AddModelError("Code", "Il codice di verifica non è valido.");
return View(model);
}
// Abilita la MFA per l'utente
await _userManager.SetTwoFactorEnabledAsync(user, true);
// (Opzionale) Genera i codici di recupero (Recovery Codes)
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
return RedirectToPage("./ShowRecoveryCodes");
}
4. Gestione del Login con Doppio Fattore
Con l’MFA attiva, il normale flusso di login (SignInManager) restituirà un risultato di tipo RequiresTwoFactor. Dobbiamo quindi intercettare questo stato nel controller di autenticazione per reindirizzare l’utente alla schermata di inserimento del codice TOTP.
[HttpPost]
async public Task<IActionResult> Login(LoginViewModel model)
{
if (!ModelState.IsValid) return View(model);
// Tentativo di accesso senza persistere subito il cookie
var result = await _signInManager.PasswordSignInAsync(
model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
if (result.RequiresTwoFactor)
{
// Reindirizza alla pagina di inserimento del codice TOTP
return RedirectToAction(nameof(LoginWith2fa), new { model.RememberMe });
}
if (result.Succeeded)
{
_logger.LogInformation("Utente logged in.");
return LocalRedirect(model.ReturnUrl ?? "/");
}
ModelState.AddModelError(string.Empty, "Tentativo di accesso non valido.");
return View(model);
}
Successivamente, gestiamo la conferma finale del codice inserito in fase di login:
[HttpPost]
async public Task<IActionResult> LoginWith2fa(LoginWith2faViewModel model)
{
if (!ModelState.IsValid) return View(model);
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new InvalidOperationException("Impossibile caricare l'utente per l'autenticazione a due fattori.");
}
var authenticatorCode = model.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(
authenticatorCode, model.RememberMe, model.RememberMachine);
if (result.Succeeded)
{
_logger.LogInformation("Utente {UserId} loggato tramite 2FA.", user.Id);
return LocalRedirect(model.ReturnUrl ?? "/");
}
if (result.IsLockedOut)
{
_logger.LogWarning("Account bloccato per tentativi falliti.");
return RedirectToAction(nameof(Lockout));
}
ModelState.AddModelError(string.Empty, "Codice di autenticazione non valido.");
return View(model);
}
5. Best Practices e Considerazioni di Sicurezza
Quando si implementa l’MFA in ambienti di produzione con .NET 10, è fondamentale tenere a mente alcune linee guida di sicurezza:
- Gestione dei Codici di Recupero (Recovery Codes): Fornisci sempre una serie di codici di backup monouso nel momento in cui l’utente attiva la MFA. Se l’utente perde l’accesso al proprio smartphone, i codici di recupero rappresentano l’unica via di accesso per resettare il sistema.
- Rate Limiting: Proteggi gli endpoint di verifica del codice TOTP implementando criteri di Rate Limiting (disponibili nativamente in ASP.NET Core) per prevenire attacchi di tipo Brute Force mirati a indovinare il codice a 6 cifre.
- Persistenza e Sicurezza dei Cookie: Assicurati che i cookie di autenticazione siano configurati con flag rigorosi (
HttpOnly,Secure = Always,SameSite = LaxoStrict).
Ecco solo la parte di integrazione relativa a Google e Microsoft (con i pacchetti NuGet, la configurazione in Program.cs, il file appsettings.json e la gestione del callback con verifica MFA), formattata in semplice testo con codice Markdown pulito, senza HTML:
Integrazione dei Provider Esterni: Google e Microsoft
Consentire agli utenti di accedere tramite Google o Microsoft migliora la User Experience e sposta il carico di gestione delle credenziali primarie su provider altamente sicuri. Per farlo in .NET 10, dobbiamo installare i pacchetti NuGet ufficiali:
Microsoft.AspNetCore.Authentication.GoogleMicrosoft.AspNetCore.Authentication.MicrosoftAccount
1. Configurazione in Program.cs
Registriamo e configuriamo i provider all’interno del file Program.cs, recuperando le chiavi segrete tramite il sistema di configurazione o il User Secrets Manager:
builder.Services.AddAuthentication()
.AddGoogle(options =>
{
IConfigurationSection googleAuthNSection =
builder.Configuration.GetSection("Authentication:Google");
options.ClientId = googleAuthNSection["ClientId"];
options.ClientSecret = googleAuthNSection["ClientSecret"];
})
.AddMicrosoftAccount(options =>
{
IConfigurationSection microsoftAuthNSection =
builder.Configuration.GetSection("Authentication:Microsoft");
options.ClientId = microsoftAuthNSection["ClientId"];
options.ClientSecret = microsoftAuthNSection["ClientSecret"];
});
2. Configurazione nel file appsettings.json
Nel file di configurazione, definiamo i parametri forniti dalle rispettive console sviluppatori di Google e Microsoft:
JSON
{
"Authentication": {
"Google": {
"ClientId": "IL_TUO_GOOGLE_CLIENT_ID",
"ClientSecret": "IL_TUO_GOOGLE_CLIENT_SECRET"
},
"Microsoft": {
"ClientId": "IL_TUO_MICROSOFT_CLIENT_ID",
"ClientSecret": "IL_TUO_MICROSOFT_CLIENT_SECRET"
}
}
}
3. Gestione del Callback e della MFA con Provider Esterni
Quando un utente effettua il login tramite un provider social, l’applicazione riceve il token esterno. Se l’utente ha configurato l’MFA, dobbiamo intercettare il risultato RequiresTwoFactor per richiedere comunque il codice TOTP secondario:
C#
[HttpPost]
public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginViewModel model, string? returnUrl = null)
{
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
// Effettua il login tramite il provider esterno
var result = await _signInManager.ExternalLoginSignInAsync(
info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: false);
if (result.RequiresTwoFactor)
{
// Anche se autenticato con Google/Microsoft, viene richiesto il secondo fattore (TOTP)
return RedirectToAction(nameof(LoginWith2fa), new { ReturnUrl = returnUrl });
}
if (result.Succeeded)
{
return LocalRedirect(returnUrl ?? "/");
}
// Gestione della registrazione se l'utente è al primo accesso...
return View();
}
Integrazione di AWS (Amazon Cognito / OpenID Connect)
Oltre a Google e Microsoft, molte architetture aziendali richiedono l’autenticazione tramite AWS, tipicamente sfruttando Amazon Cognito come Identity Provider (IdP) gestito, oppure configurando direttamente un provider OpenID Connect (OIDC) personalizzato.
1. Installazione del pacchetto NuGet
Per integrare un provider basato su OpenID Connect (utilizzato standard da Cognito), ASP.NET Core mette a disposizione il middleware dedicato. Installa il pacchetto:
Microsoft.AspNetCore.Authentication.OpenIdConnect
2. Configurazione in Program.cs
Aggiungi e configura il provider OIDC nel file Program.cs, puntando agli endpoint di configurazione (Discovery Document) del tuo User Pool di Amazon Cognito:
builder.Services.AddAuthentication()
.AddOpenIdConnect("AWS", "Amazon Cognito", options =>
{
IConfigurationSection awsAuthSection = builder.Configuration.GetSection("Authentication:AWS");
options.Authority = awsAuthSection["Authority"]; // E es: https://cognito-idp.{region}.amazonaws.com/{userPoolId}
options.ClientId = awsAuthSection["ClientId"];
options.ClientSecret = awsAuthSection["ClientSecret"];
options.ResponseType = "code";
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
});
3. Configurazione nel file appsettings.json
Nel file di configurazione, inserisci i parametri rilasciati da AWS Cognito (User Pool ID, Client ID e Secret):
{
"Authentication": {
"AWS": {
"Authority": "https://cognito-idp.eu-central-1.amazonaws.com/eu-central-1_ABC123XYZ",
"ClientId": "tua_client_id_cognito",
"ClientSecret": "tua_client_secret_cognito"
}
}
}
4. Gestione del Flusso e Verifica MFA
Il flusso di gestione del callback per AWS segue lo stesso standard degli altri provider esterni. Una volta che l’utente effettua il login sulla schermata ospitata da AWS Cognito, ASP.NET Core intercetta il ritorno e verifica se è richiesta l’MFA applicativa:
[HttpPost]
public async Task<IActionResult> AwsLoginCallback(string? returnUrl = null)
{
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
var result = await _signInManager.ExternalLoginSignInAsync(
info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: false);
if (result.RequiresTwoFactor)
{
// Reindirizza alla schermata di inserimento del token TOTP secondario
return RedirectToAction(nameof(LoginWith2fa), new { ReturnUrl = returnUrl });
}
if (result.Succeeded)
{
return LocalRedirect(returnUrl ?? "/");
}
// Gestione della creazione automatica dell'utente se primo accesso...
return View("ExternalLoginFailure");
}
Hai bisogno di assemblare l’intero post unendo la parte iniziale su .NET 10, TOTP, Google, Microsoft e AWS in un unico testo pronto per essere copiato?
Conclusione
L’integrazione della Multi-Factor Authentication in .NET 10 sfrutta un’architettura solida e collaudata che riduce drasticamente i rischi legati al furto di password. Seguendo i passaggi descriti, puoi innalzare istantaneamente il livello di sicurezza della tua applicazione web, garantendo agli utenti un’esperienza conforme ai moderni standard di cybersecurity.