Condividi tramite


Conferma dell'account e ripristino della password in ASP.NET Core Blazor

Nota

Questa non è la versione più recente di questo articolo. 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.

Questo articolo illustra come configurare un ASP.NET Core Blazor Web App con la conferma tramite posta elettronica e il ripristino della password.

Nota

Questo articolo si applica solo a Blazor Web Apps. Per implementare la conferma tramite posta elettronica e il ripristino delle password per le app autonome Blazor WebAssembly con ASP.NET Core Identity, vedere Conferma dell'account e ripristino delle password in ASP.NET Core Blazor WebAssembly con ASP.NET Core Identity.

Spazio dei nomi

Lo spazio dei nomi dell'app usato dall'esempio in questo articolo è BlazorSample. Aggiornare gli esempi di codice per usare lo spazio dei nomi dell'app.

Selezionare e configurare un provider di posta elettronica

In questo articolo l'API transazionale di Mailchimp viene usata tramite Mandrill.net per inviare messaggi di posta elettronica. È consigliabile usare un servizio di posta elettronica per inviare messaggi di posta elettronica anziché SMTP. SMTP è difficile da configurare e proteggere correttamente. Indipendentemente dal servizio di posta elettronica usato, accedere alle linee guida per le app .NET, creare un account, configurare una chiave API per il servizio e installare i pacchetti NuGet necessari.

Creare una classe per contenere la chiave API del provider di posta elettronica segreto. Nell'esempio riportato in questo articolo viene usata una classe denominata AuthMessageSenderOptions con una EmailAuthKey proprietà per contenere la chiave.

AuthMessageSenderOptions.cs:

namespace BlazorSample;

public class AuthMessageSenderOptions
{
    public string? EmailAuthKey { get; set; }
}

Registrare l'istanza AuthMessageSenderOptions di configurazione nel Program file :

builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

Configurare un segreto per la chiave di sicurezza del provider di posta elettronica

Ricevere la chiave di sicurezza del provider di posta elettronica dal provider e usarla nelle indicazioni seguenti.

Usare uno o entrambi gli approcci seguenti per fornire il segreto all'app:

  • strumento Secret Manager: lo strumento Secret Manager archivia i dati privati nel computer locale e viene usato solo durante lo sviluppo locale.
  • Azure Key Vault: è possibile archiviare i segreti in un Key Vault per usarli in qualsiasi ambiente, incluso l'ambiente di sviluppo quando si lavora in locale. Alcuni sviluppatori preferiscono utilizzare le key vault per le distribuzioni di staging e di produzione e lo strumento Secret Manager per lo sviluppo locale.

È consigliabile evitare di archiviare segreti nel codice del progetto o nei file di configurazione. Usare flussi di autenticazione sicuri, ad esempio uno o entrambi gli approcci descritti in questa sezione.

Strumento di Gestione Segreti

Se il progetto è già stato inizializzato per lo strumento Secret Manager, avrà già un identificatore dei segreti dell'app (<AppSecretsId>) nel file di progetto (.csproj). In Visual Studio è possibile stabilire se l'ID dei segreti dell'app è presente esaminando il pannello Proprietà quando il progetto viene selezionato in Esplora soluzioni. Se l'app non è stata inizializzata, eseguire il comando seguente in una shell dei comandi aperta nella directory del progetto. In Visual Studio è possibile usare il prompt dei comandi di PowerShell per sviluppatori.

dotnet user-secrets init

Impostare la chiave API con lo strumento Secret Manager. Nell'esempio seguente il nome della chiave deve EmailAuthKey corrispondere AuthMessageSenderOptions.EmailAuthKeya e la chiave è rappresentata dal {KEY} segnaposto. Eseguire il comando seguente con la chiave API:

dotnet user-secrets set "EmailAuthKey" "{KEY}"

Se si usa Visual Studio, è possibile verificare che il segreto sia impostato facendo clic con il pulsante destro del mouse sul progetto server in Esplora soluzioni e scegliendo Gestisci segreti utente.

Per altre informazioni, vedere Archiviazione sicura dei segreti delle app in fase di sviluppo in ASP.NET Core.

Avviso

Non archiviare segreti dell'app, stringa di connessione, credenziali, password, numeri di identificazione personale (PIN), codice C#/.NET privato o chiavi/token privati nel codice lato client, che è sempre non sicuro. Negli ambienti di test/gestione temporanea e produzione, il codice lato Blazor server e le API Web devono usare flussi di autenticazione sicuri che evitano di mantenere le credenziali all'interno del codice del progetto o dei file di configurazione. Al di fuori dei test di sviluppo locali, è consigliabile evitare l'uso di variabili di ambiente per archiviare i dati sensibili, perché le variabili di ambiente non sono l'approccio più sicuro. Per i test di sviluppo locali, lo strumento Secret Manager è consigliato per proteggere i dati sensibili. Per altre informazioni, vedere Gestire in modo sicuro dati e credenziali sensibili.

Azure Key Vault

azure Key Vault offre un approccio sicuro per fornire il segreto client dell'app all'app.

Per creare un key vault e impostare un segreto, vedere Informazioni sui segreti di Azure Key Vault (documentazione di Azure), che collega le risorse per iniziare a usare Azure Key Vault. Per implementare il codice in questa sezione, annotare l'URI del Key Vault e il nome del segreto da Azure quando si crea il Key Vault e il segreto. Quando si impostano i criteri di accesso per il segreto nel pannello criteri di accesso:

  • È necessaria solo l'autorizzazione "Ottieni segreto".
  • Selezionare l'applicazione come Principal per il segreto.

Verificare nel portale di Azure o Entra che all'app sia stato concesso l'accesso al segreto creato per la chiave del provider di posta elettronica.

Importante

Viene creato un segreto del Key Vault con una data di scadenza. Assicurarsi di tenere traccia di quando un segreto di un Key Vault sta per scadere e creare un nuovo segreto per l'app prima che tale data arrivi.

Aggiungere la classe AzureHelper seguente al progetto server. Il metodo GetKeyVaultSecret recupera un segreto da un archivio di chiavi. Modificare lo spazio dei nomi (BlazorSample.Helpers) in modo che corrisponda allo schema dello spazio dei nomi del progetto.

Helpers/AzureHelper.cs:

using Azure;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

namespace BlazorSample.Helpers;

public static class AzureHelper
{
    public static string GetKeyVaultSecret(string tenantId, string vaultUri, string secretName)
    {
        DefaultAzureCredentialOptions options = new()
        {
            // Specify the tenant ID to use the dev credentials when running the app locally
            // in Visual Studio.
            VisualStudioTenantId = tenantId,
            SharedTokenCacheTenantId = tenantId
        };

        var client = new SecretClient(new Uri(vaultUri), new DefaultAzureCredential(options));
        var secret = client.GetSecretAsync(secretName).Result;

        return secret.Value.Value;
    }
}

Dove i servizi vengono registrati nel file Program del progetto server, ottenere e legare il segreto alla configurazione delle Opzioni :

var tenantId = builder.Configuration.GetValue<string>("AzureAd:TenantId")!;
var vaultUri = builder.Configuration.GetValue<string>("AzureAd:VaultUri")!;

var emailAuthKey = AzureHelper.GetKeyVaultSecret(
    tenantId, vaultUri, "EmailAuthKey");

var authMessageSenderOptions = 
    new AuthMessageSenderOptions() { EmailAuthKey = emailAuthKey };
builder.Configuration.GetSection(authMessageSenderOptions.EmailAuthKey)
    .Bind(authMessageSenderOptions);

Se si vuole controllare l'ambiente in cui opera il codice precedente, ad esempio per evitare di eseguire il codice in locale perché si è scelto di usare lo strumento Secret Manager per lo sviluppo locale, è possibile eseguire il wrapping del codice precedente in un'istruzione condizionale che controlla l'ambiente:

if (!context.HostingEnvironment.IsDevelopment())
{
    ...
}

Nella sezione AzureAd di appsettings.json nel progetto server, verificare la presenza dell'ID Entra dell'app TenantId e aggiungere la chiave e il valore di configurazione VaultUri seguenti, se non è già presente.

"VaultUri": "{VAULT URI}"

Nell'esempio precedente, il segnaposto {VAULT URI} è l'URI dell'insieme di chiavi. Includere la barra finale nell'URI.

Esempio:

"VaultUri": "https://contoso.vault.azure.net/"

La configurazione viene utilizzata per facilitare la distribuzione di archivi di chiavi e nomi segreti dedicati in base ai file di configurazione di ambiente dell'app. Ad esempio, è possibile specificare valori di configurazione diversi per appsettings.Development.json in fase di sviluppo, appsettings.Staging.json durante la gestione temporanea e appsettings.Production.json per la distribuzione di produzione. Per altre informazioni, vedere ASP.NET Core Blazor configurazione.

Implementare IEmailSender

L'esempio seguente si basa sull'API transazionale di Mailchimp usando Mandrill.net. Per un provider diverso, fare riferimento alla relativa documentazione su come implementare l'invio di un messaggio di posta elettronica.

Aggiungere il pacchetto NuGet Mandrill.net al progetto.

Aggiungere la classe seguente EmailSender per implementare IEmailSender<TUser>. Nell'esempio seguente è ApplicationUser un oggetto IdentityUser. Il markup HTML del messaggio può essere ulteriormente personalizzato. Se l'oggetto message passato a MandrillMessage inizia con il < carattere , l'API Mandrill.net presuppone che il corpo del messaggio sia composto in HTML.

Components/Account/EmailSender.cs:

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Mandrill;
using Mandrill.Model;
using BlazorSample.Data;

namespace BlazorSample.Components.Account;

public class EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor,
    ILogger<EmailSender> logger) : IEmailSender<ApplicationUser>
{
    private readonly ILogger logger = logger;

    public AuthMessageSenderOptions Options { get; } = optionsAccessor.Value;

    public Task SendConfirmationLinkAsync(AppUser user, string email,
        string confirmationLink) => SendEmailAsync(email, "Confirm your email",
        "<html lang=\"en\"><head></head><body>Please confirm your account by " +
        $"<a href='{confirmationLink}'>clicking here</a>.</body></html>");

    public Task SendPasswordResetLinkAsync(AppUser user, string email,
        string resetLink) => SendEmailAsync(email, "Reset your password",
        "<html lang=\"en\"><head></head><body>Please reset your password by " +
        $"<a href='{resetLink}'>clicking here</a>.</body></html>");

    public Task SendPasswordResetCodeAsync(AppUser user, string email,
        string resetCode) => SendEmailAsync(email, "Reset your password",
        "<html lang=\"en\"><head></head><body>Please reset your password " +
        $"using the following code:<br>{resetCode}</body></html>");

    public async Task SendEmailAsync(string toEmail, string subject, string message)
    {
        if (string.IsNullOrEmpty(Options.EmailAuthKey))
        {
            throw new Exception("Null EmailAuthKey");
        }

        await Execute(Options.EmailAuthKey, subject, message, toEmail);
    }

    public async Task Execute(string apiKey, string subject, string message, 
        string toEmail)
    {
        var api = new MandrillApi(apiKey);
        var mandrillMessage = new MandrillMessage("sarah@contoso.com", toEmail, 
            subject, message);
        await api.Messages.SendAsync(mandrillMessage);

        logger.LogInformation("Email to {EmailAddress} sent!", toEmail);
    }
}

Nota

Il contenuto del corpo per i messaggi potrebbe richiedere una codifica speciale per il provider di servizi di posta elettronica. Se i collegamenti nel corpo del messaggio non possono essere seguiti nel messaggio di posta elettronica, consultare la documentazione del provider di servizi per risolvere il problema.

Configurare l'app per supportare la posta elettronica

Program Nel file modificare l'implementazione del mittente del messaggio di posta elettronica in EmailSender:

- builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
+ builder.Services.AddSingleton<IEmailSender<ApplicationUser>, EmailSender>();

Rimuovere (IdentityNoOpEmailSenderComponents/Account/IdentityNoOpEmailSender.cs) dall'app.

RegisterConfirmation Nel componente (Components/Account/Pages/RegisterConfirmation.razor) rimuovere il blocco condizionale nel @code blocco che controlla se EmailSender è un IdentityNoOpEmailSenderoggetto :

- else if (EmailSender is IdentityNoOpEmailSender)
- {
-     ...
- }

Anche nel RegisterConfirmation componente rimuovere il markup e il Razor codice per controllare il emailConfirmationLink campo, lasciando solo la riga che indica all'utente di controllare il proprio messaggio di posta elettronica ...

- @if (emailConfirmationLink is not null)
- {
-     ...
- }
- else
- {
     <p>Please check your email to confirm your account.</p>
- }

@code {
-    private string? emailConfirmationLink;

     ...
}

Abilitare la conferma dell'account dopo che un sito ha utenti

L'abilitazione della conferma dell'account in un sito con gli utenti blocca tutti gli utenti esistenti. Gli utenti esistenti vengono bloccati perché gli account non vengono confermati. Per aggirare il blocco utente esistente, usare uno degli approcci seguenti:

  • Aggiornare il database per contrassegnare tutti gli utenti esistenti come confermati.
  • Confermare gli utenti esistenti. Ad esempio, inviare messaggi di posta elettronica in batch con collegamenti di conferma.

Timeout di posta elettronica e attività

Il timeout di inattività predefinito è 14 giorni. Il codice seguente imposta il timeout di inattività su cinque giorni con scadenza scorrevole:

builder.Services.ConfigureApplicationCookie(options => {
    options.ExpireTimeSpan = TimeSpan.FromDays(5);
    options.SlidingExpiration = true;
});

Modificare tutta la durata del token di protezione dei dati di base ASP.NET

Il codice seguente modifica il periodo di timeout dei token di protezione dei dati a tre ore:

builder.Services.Configure<DataProtectionTokenProviderOptions>(options =>
    options.TokenLifespan = TimeSpan.FromHours(3));

I token utente predefiniti Identity (AspNetCore/src//IdentityExtensions.Core/src/TokenOptions.cs) hanno un timeout di un giorno.

Nota

I collegamenti della documentazione all'origine del riferimento .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo corrente per la versione successiva di .NET. Per selezionare un tag per una versione specifica, usare l'elenco a discesa Switch branches or tags. Per altre informazioni, vedere How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Come selezionare un tag di versione del codice sorgente di ASP.NET - dotnet/AspNetCore.Docs #26205).

Modificare la durata del token di posta elettronica

La durata del token predefinita dei token utente è Identity.

Nota

I collegamenti della documentazione all'origine del riferimento .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo corrente per la versione successiva di .NET. Per selezionare un tag per una versione specifica, usare l'elenco a discesa Switch branches or tags. Per altre informazioni, vedere How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Come selezionare un tag di versione del codice sorgente di ASP.NET - dotnet/AspNetCore.Docs #26205).

Per modificare la durata del token di posta elettronica, aggiungere un oggetto personalizzato DataProtectorTokenProvider<TUser> e DataProtectionTokenProviderOptions.

CustomTokenProvider.cs:

using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;

namespace BlazorSample;

public class CustomEmailConfirmationTokenProvider<TUser>
    : DataProtectorTokenProvider<TUser> where TUser : class
{
    public CustomEmailConfirmationTokenProvider(
        IDataProtectionProvider dataProtectionProvider,
        IOptions<EmailConfirmationTokenProviderOptions> options,
        ILogger<DataProtectorTokenProvider<TUser>> logger)
        : base(dataProtectionProvider, options, logger)
    {
    }
}

public class EmailConfirmationTokenProviderOptions 
    : DataProtectionTokenProviderOptions
{
    public EmailConfirmationTokenProviderOptions()
    {
        Name = "EmailDataProtectorTokenProvider";
        TokenLifespan = TimeSpan.FromHours(4);
    }
}

public class CustomPasswordResetTokenProvider<TUser> 
    : DataProtectorTokenProvider<TUser> where TUser : class
{
    public CustomPasswordResetTokenProvider(
        IDataProtectionProvider dataProtectionProvider,
        IOptions<PasswordResetTokenProviderOptions> options,
        ILogger<DataProtectorTokenProvider<TUser>> logger)
        : base(dataProtectionProvider, options, logger)
    {
    }
}

public class PasswordResetTokenProviderOptions : 
    DataProtectionTokenProviderOptions
{
    public PasswordResetTokenProviderOptions()
    {
        Name = "PasswordResetDataProtectorTokenProvider";
        TokenLifespan = TimeSpan.FromHours(3);
    }
}

Configurare i servizi per l'uso del provider di token personalizzato nel Program file:

builder.Services.AddIdentityCore<ApplicationUser>(options =>
    {
        options.SignIn.RequireConfirmedAccount = true;
        options.Tokens.ProviderMap.Add("CustomEmailConfirmation",
            new TokenProviderDescriptor(
                typeof(CustomEmailConfirmationTokenProvider<ApplicationUser>)));
        options.Tokens.EmailConfirmationTokenProvider = 
            "CustomEmailConfirmation";
    })
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddSignInManager()
    .AddDefaultTokenProviders();

builder.Services
    .AddTransient<CustomEmailConfirmationTokenProvider<ApplicationUser>>();

Risoluzione dei problemi

Se non è possibile ricevere messaggi di posta elettronica funzionanti:

  • Impostare un punto di interruzione in EmailSender.Execute per verificare SendEmailAsync che venga chiamato .
  • Creare un'app console per inviare messaggi di posta elettronica usando codice simile al EmailSender.Execute debug del problema.
  • Esaminare le pagine della cronologia dell'account nel sito Web del provider di posta elettronica.
  • Controllare la cartella della posta indesiderata per i messaggi.
  • Provare un altro alias di posta elettronica su un provider di posta elettronica diverso, ad esempio Microsoft, Yahoo o Gmail.
  • Provare a inviare a account di posta elettronica diversi.

Risorse aggiuntive