Partilhar via


Confirmação de conta e recuperação de senha no ASP.NET Core Blazor WebAssembly com ASP.NET Core Identity

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.

Importante

Estas informações referem-se a um produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado comercialmente. A Microsoft não oferece garantias, expressas ou implícitas, em relação às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 deste artigo.

Este artigo explica como configurar um aplicativo ASP.NET Core Blazor WebAssembly com ASP.NET Core Identity com confirmação de email e recuperação de senha.

Observação

Este artigo aplica-se apenas a aplicações Blazor WebAssembly autónomas com ASP.NET Core Identity. Para implementar a confirmação de e-mail e a recuperação de senha para Blazor Web Apps, consulte Confirmação de conta e recuperação de senha no ASP.NET Core Blazor.

Namespaces e exemplos de código de artigo

Os namespaces usados pelos exemplos neste artigo são:

  • Backend para o projeto de API Web de servidor back-end ("projeto de servidor" neste artigo).
  • BlazorWasmAuth para o cliente front-end autônomo da aplicação de Blazor WebAssembly ("projeto cliente" neste artigo).

Esses namespaces correspondem aos projetos na solução de exemplo BlazorWebAssemblyStandaloneWithIdentity no repositório GitHub dotnet/blazor-samples. Para obter mais informações, consulte Secure ASP.NET Core Blazor WebAssembly com ASP.NET Core Identity.

Se você não estiver usando a solução de exemplo BlazorWebAssemblyStandaloneWithIdentity, altere os namespaces nos exemplos de código para usar os namespaces de seus projetos.

Selecionar e configurar um provedor de email para o projeto de servidor

Neste artigo, o da API Transacional do Mailchimp é usado via Mandrill.net para enviar e-mails. Recomendamos o uso de um serviço de e-mail para enviar e-mails em vez de SMTP. SMTP é difícil de configurar e proteger corretamente. Seja qual for o serviço de email usado, acesse as orientações para aplicativos .NET, crie uma conta, configure uma chave de API para o serviço e instale todos os pacotes NuGet necessários.

No projeto de servidor, crie uma classe para armazenar a chave de API do provedor de email secreto. O exemplo neste artigo usa uma classe chamada AuthMessageSenderOptions com uma propriedade EmailAuthKey para manter a chave.

AuthMessageSenderOptions.cs:

namespace Backend;

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

Registre a instância de configuração AuthMessageSenderOptions no arquivo Program do projeto de servidor:

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

Configurar um segredo para a chave de segurança do provedor de email

Receba a chave de segurança do provedor de e-mail e use-a nas orientações a seguir.

Use uma ou ambas as seguintes abordagens para fornecer o segredo ao aplicativo:

  • ferramenta Secret Manager: A ferramenta Secret Manager armazena dados privados na máquina local e só é usada durante o desenvolvimento local.
  • Azure Key Vault: Você pode armazenar o segredo em um cofre de chaves para uso em qualquer ambiente, inclusive para o ambiente de desenvolvimento ao trabalhar localmente. Alguns desenvolvedores preferem usar cofres de chaves para implantações de preparação e produção e usar a ferramenta Secret Manager para desenvolvimento local.

É altamente recomendável que você evite armazenar segredos no código do projeto ou em arquivos de configuração. Use fluxos de autenticação seguros, como uma ou ambas as abordagens nesta seção.

Ferramenta Secret Manager

Se o projeto do servidor já tiver sido inicializado para a ferramenta Secret Manager, ele já terá um identificador de segredos do aplicativo (<AppSecretsId>) em seu arquivo de projeto (.csproj). No Visual Studio, você pode verificar se a ID dos segredos da aplicação está presente ao examinar o painel Propriedades quando o projeto é selecionado no Gerenciador de Soluções . Se o aplicativo não tiver sido inicializado, execute o seguinte comando em um shell de comando aberto no diretório do projeto de servidor. No Visual Studio, você pode usar o prompt de comando do Developer PowerShell (use o comando cd para alterar o diretório para o projeto de servidor depois de abrir o shell de comando).

dotnet user-secrets init

Defina a chave da API com a ferramenta Secret Manager. No exemplo a seguir, o nome da chave é EmailAuthKey para corresponder a AuthMessageSenderOptions.EmailAuthKeye a chave é representada pelo espaço reservado {KEY}. Execute o seguinte comando com a chave API:

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

Se estiver usando o Visual Studio, você poderá confirmar que o segredo está definido clicando com o botão direito do mouse no projeto de servidor no Gerenciador de Soluções e selecionando Gerenciar Segredos de Usuário.

Para obter mais informações, consulte Armazenamento seguro de segredos de aplicativos em desenvolvimento no ASP.NET Core.

Advertência

Não armazene segredos de aplicativos, cadeias de conexão, credenciais, senhas, números de identificação pessoal (PINs), código C#/.NET privado ou chaves/tokens privados no código do lado do cliente, que é sempre inseguro. Em ambientes de teste/preparação e produção, o código Blazor do lado do servidor e as APIs da Web devem usar fluxos de autenticação seguros que evitem a manutenção de credenciais no código do projeto ou nos arquivos de configuração. Fora dos testes de desenvolvimento local, recomendamos evitar o uso de variáveis de ambiente para armazenar dados confidenciais, pois as variáveis de ambiente não são a abordagem mais segura. Para testes de desenvolvimento local, a ferramenta Secret Manager é recomendada para proteger dados confidenciais. Para obter mais informações, consulte Manter com segurança dados confidenciais e credenciais.

Azure Key Vault

Azure Key Vault fornece uma abordagem segura para fornecer o segredo do cliente do aplicativo para o aplicativo.

Para criar um cofre de chaves e definir um segredo, consulte Sobre segredos do Cofre de Chaves do Azure (documentação do Azure), que vincula recursos para começar a usar o Cofre de Chaves do Azure. Para implementar o código nesta seção, registre o URI do cofre de chaves e o nome secreto do Azure ao criar o cofre de chaves e o segredo. Quando você define a política de acesso para o segredo no painel Políticas de acesso:

  • Apenas a permissão secreta Obter é necessária.
  • Selecione o aplicativo como o Principal para o segredo.

Confirme no portal do Azure ou do Entra que o aplicativo recebeu acesso ao segredo que você criou para a chave do provedor de email.

Importante

Um segredo do cofre de chaves é criado com uma data de validade. Certifique-se de controlar quando um segredo do cofre de chaves vai expirar e crie um novo segredo para o aplicativo antes que essa data passe.

Se os pacotes do Microsoft Identity ainda não fizerem parte dos registros de pacotes do aplicativo, adicione os seguintes pacotes ao projeto de servidor para o Azure Identity e o Azure Key Vault. Esses pacotes são fornecidos transitoriamente pelos pacotes da Microsoft Identity Web, portanto, você só precisará adicioná-los se o aplicativo não estiver fazendo referência Microsoft.Identity.Web:

Adicione a seguinte classe AzureHelper ao projeto de servidor. O método GetKeyVaultSecret recupera um segredo de um cofre de chaves. Ajuste o namespace (BlazorSample.Helpers) para corresponder ao esquema de namespace do projeto.

Helpers/AzureHelper.cs:

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

Observação

O exemplo anterior usa DefaultAzureCredential para simplificar a autenticação ao desenvolver aplicativos que implantam no Azure combinando credenciais usadas em ambientes de hospedagem do Azure com credenciais usadas no desenvolvimento local. Ao passar para a produção, uma alternativa é uma escolha melhor, como ManagedIdentityCredential. Para obter mais informações, consulte Autenticar aplicativos .NET hospedados no Azure em recursos do Azure usando uma identidade gerenciada atribuída ao sistema.

Onde os serviços são registados no arquivo Program do projeto do servidor, obtenha e vincule o segredo com as Opções de configuração :

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 você deseja controlar o ambiente onde o código anterior opera, por exemplo, para evitar executar o código localmente porque optou por usar a ferramenta Secret Manager para desenvolvimento local, você pode encapsular o código anterior em uma instrução condicional que verifica o ambiente:

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

Na seção AzureAd de appsettings.json, que talvez seja necessário adicionar se ainda não estiver presente, no projeto do servidor, adicione os seguintes valores e chaves de configuração TenantId e VaultUri, se ainda não estiverem presentes:

"AzureAd": {
  "TenantId": "{TENANT ID}",
  "VaultUri": "{VAULT URI}"
}

No exemplo anterior:

  • O espaço reservado {TENANT ID} é o ID do tenant do aplicativo no Azure.
  • O espaço reservado {VAULT URI} é o URI do cofre de chaves. Inclua a barra final no URI.

Exemplo:

"TenantId": "00001111-aaaa-2222-bbbb-3333cccc4444",
"VaultUri": "https://contoso.vault.azure.net/"

A configuração é usada para facilitar o fornecimento de cofres de chaves dedicados e nomes secretos com base nos arquivos de configuração ambiental do aplicativo. Por exemplo, você pode fornecer valores de configuração diferentes para appsettings.Development.json em desenvolvimento, appsettings.Staging.json durante o preparo e appsettings.Production.json para a implantação de produção. Para obter mais informações, consulte ASP.NET Core Blazor de configuração.

Implementar IEmailSender no projeto de servidor

O exemplo a seguir é baseado na API Transacional do Mailchimp usando Mandrill.net. Para um provedor diferente, consulte a documentação sobre como implementar o envio de uma mensagem de email.

Adicione o Mandrill.net pacote NuGet ao projeto de servidor.

Adicione a seguinte classe EmailSender para implementar IEmailSender<TUser>. No exemplo a seguir, AppUser é um IdentityUser. A marcação HTML da mensagem pode ser ainda mais personalizada. Desde que o message passado para MandrillMessage comece com o caractere <, a API Mandrill.net assume que o corpo da mensagem é composto em HTML.

EmailSender.cs:

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

namespace Backend;

public class EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor,
    ILogger<EmailSender> logger) : IEmailSender<AppUser>
{
    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);
    }
}

Observação

O conteúdo do corpo das mensagens pode exigir codificação especial para o provedor de serviços de e-mail. Se os links no corpo da mensagem não puderem ser seguidos na mensagem de email, consulte a documentação do provedor de serviços para solucionar o problema.

Adicione o seguinte registro de serviço de IEmailSender<TUser> ao arquivo de Program do projeto de servidor:

builder.Services.AddTransient<IEmailSender<AppUser>, EmailSender>();

Configurar o projeto do servidor para exigir confirmação de e-mail

No arquivo Program do projeto de servidor, exija um endereço de e-mail confirmado para entrar no aplicativo.

Localize a linha que chama AddIdentityCore e defina a propriedade RequireConfirmedEmail como true:

- builder.Services.AddIdentityCore<AppUser>()
+ builder.Services.AddIdentityCore<AppUser>(o => o.SignIn.RequireConfirmedEmail = true)

Atualizar a resposta de registro da conta do projeto cliente

No componente Register do projeto cliente (Components/Identity/Register.razor), altere a mensagem para os usuários em um registro de conta bem-sucedido para instruí-los a confirmar sua conta. O exemplo a seguir inclui um link para acionar Identity no servidor para reenviar o e-mail de confirmação.

- You successfully registered and can <a href="login">login</a> to the app.
+ You successfully registered. You must now confirm your account by clicking 
+ the link in the email that was sent to you. After confirming your account, 
+ you can <a href="login">login</a> to the app. 
+ <a href="resendConfirmationEmail">Resend confirmation email</a>

Atualizar o código de dados de semente para confirmar contas semeadas

Na classe de dados seed do projeto de servidor (SeedData.cs), altere o código no método InitializeAsync para confirmar as contas semeadas, o que evita exigir confirmação de endereço de e-mail para cada execução de teste da solução com uma das contas:

- if (appUser is not null && user.RoleList is not null)
- {
-     await userManager.AddToRolesAsync(appUser, user.RoleList);
- }
+ if (appUser is not null)
+ {
+     if (user.RoleList is not null)
+     {
+         await userManager.AddToRolesAsync(appUser, user.RoleList);
+     }
+ 
+     var token = await userManager.GenerateEmailConfirmationTokenAsync(appUser);
+     await userManager.ConfirmEmailAsync(appUser, token);
+ }

Ativar a confirmação da conta depois de um site ter utilizadores

Habilitar a confirmação de conta em um site com usuários bloqueia todos os usuários existentes. Os usuários existentes são bloqueados porque suas contas não são confirmadas. Use uma das seguintes abordagens, que estão além do escopo deste artigo:

  • Atualize o banco de dados para marcar todos os usuários existentes como confirmados.
  • Envio de e-mails em lote com links de confirmação para todos os usuários existentes, o que exige que cada usuário confirme sua própria conta.

Recuperação de palavra-passe

A recuperação de senha requer que o aplicativo do servidor adote um provedor de e-mail para enviar códigos de redefinição de senha aos usuários. Portanto, siga as orientações anteriores neste artigo para adotar um provedor de e-mail:

A recuperação de senha é um processo de duas etapas:

  1. Uma solicitação POST é feita para o endpoint /forgotPassword fornecido por MapIdentityApi no projeto de servidor. Uma mensagem na interface do usuário instrui o usuário a verificar seu e-mail em busca de um código de redefinição.
  2. Uma solicitação POST é feita para o ponto de extremidade /resetPassword do projeto de servidor com o endereço de e-mail do usuário, código de redefinição de senha e nova senha.

As etapas anteriores são demonstradas pelas seguintes diretrizes de implementação para a solução de exemplo .

No projeto cliente, adicione as seguintes assinaturas de método à classe IAccountManagement (Identity/IAccountManagement.cs):

public Task<bool> ForgotPasswordAsync(string email);

public Task<FormResult> ResetPasswordAsync(string email, string resetCode, 
    string newPassword);

No projeto cliente, adicione implementações para os métodos anteriores na classe CookieAuthenticationStateProvider (Identity/CookieAuthenticationStateProvider.cs):

public async Task<bool> ForgotPasswordAsync(string email)
{
    try
    {
        using var result = await httpClient.PostAsJsonAsync(
            "forgotPassword", new
            {
                email
            });

        if (result.IsSuccessStatusCode)
        {
            return true;
        }
    }
    catch { }

    return false;
}

public async Task<FormResult> ResetPasswordAsync(string email, string resetCode, 
    string newPassword)
{
    string[] defaultDetail = [ "An unknown error prevented password reset." ];

    try
    {
        using var result = await httpClient.PostAsJsonAsync(
            "resetPassword", new
            {
                email,
                resetCode,
                newPassword
            });

        if (result.IsSuccessStatusCode)
        {
            return new FormResult { Succeeded = true };
        }

        var details = await result.Content.ReadAsStringAsync();
        var problemDetails = JsonDocument.Parse(details);
        var errors = new List<string>();
        var errorList = problemDetails.RootElement.GetProperty("errors");

        foreach (var errorEntry in errorList.EnumerateObject())
        {
            if (errorEntry.Value.ValueKind == JsonValueKind.String)
            {
                errors.Add(errorEntry.Value.GetString()!);
            }
            else if (errorEntry.Value.ValueKind == JsonValueKind.Array)
            {
                errors.AddRange(
                    errorEntry.Value.EnumerateArray().Select(
                        e => e.GetString() ?? string.Empty)
                    .Where(e => !string.IsNullOrEmpty(e)));
            }
        }

        return new FormResult
        {
            Succeeded = false,
            ErrorList = problemDetails == null ? defaultDetail : [.. errors]
        };
    }
    catch { }

    return new FormResult
    {
        Succeeded = false,
        ErrorList = defaultDetail
    };
}

No projeto cliente, adicione o seguinte componente ForgotPassword.

Components/Identity/ForgotPassword.razor:

@page "/forgot-password"
@using System.ComponentModel.DataAnnotations
@using BlazorWasmAuth.Identity
@inject IAccountManagement Acct

<PageTitle>Forgot your password?</PageTitle>

<h1>Forgot your password?</h1>
<p>Provide your email address and select the <b>Reset password</b> button.</p>
<hr />
<div class="row">
    <div class="col-md-4">
        @if (!passwordResetCodeSent)
        {
            <EditForm Model="Input" FormName="forgot-password" 
                      OnValidSubmit="OnValidSubmitStep1Async" method="post">
                <DataAnnotationsValidator />
                <ValidationSummary class="text-danger" role="alert" />

                <div class="form-floating mb-3">
                    <InputText @bind-Value="Input.Email" 
                        id="Input.Email" class="form-control" 
                        autocomplete="username" aria-required="true" 
                        placeholder="name@example.com" />
                    <label for="Input.Email" class="form-label">
                        Email
                    </label>
                    <ValidationMessage For="() => Input.Email" 
                        class="text-danger" />
                </div>
                <button type="submit" class="w-100 btn btn-lg btn-primary">
                    Request reset code
                </button>
            </EditForm>
        }
        else
        {
            if (passwordResetSuccess)
            {
                if (errors)
                {
                    foreach (var error in errorList)
                    {
                        <div class="alert alert-danger">@error</div>
                    }
                }
                else
                {
                    <div>
                        Your password was reset. You may <a href="login">login</a> 
                        to the app with your new password.
                    </div>
                }
            }
            else
            {
                <div>
                    A password reset code has been sent to your email address. 
                    Obtain the code from the email for this form.
                </div>
                <EditForm Model="Reset" FormName="reset-password" 
                    OnValidSubmit="OnValidSubmitStep2Async" method="post">
                    <DataAnnotationsValidator />
                    <ValidationSummary class="text-danger" role="alert" />

                    <div class="form-floating mb-3">
                        <InputText @bind-Value="Reset.ResetCode" 
                            id="Reset.ResetCode" class="form-control" 
                            autocomplete="username" aria-required="true" />
                        <label for="Reset.ResetCode" class="form-label">
                            Reset code
                        </label>
                        <ValidationMessage For="() => Reset.ResetCode" 
                            class="text-danger" />
                    </div>
                    <div class="form-floating mb-3">
                        <InputText type="password" @bind-Value="Reset.NewPassword" 
                            id="Reset.NewPassword" class="form-control" 
                            autocomplete="new-password" aria-required="true" 
                            placeholder="password" />
                        <label for="Reset.NewPassword" class="form-label">
                            New Password
                        </label>
                        <ValidationMessage For="() => Reset.NewPassword" 
                            class="text-danger" />
                    </div>
                    <div class="form-floating mb-3">
                        <InputText type="password" 
                            @bind-Value="Reset.ConfirmPassword" 
                            id="Reset.ConfirmPassword" class="form-control" 
                            autocomplete="new-password" aria-required="true" 
                            placeholder="password" />
                        <label for="Reset.ConfirmPassword" class="form-label">
                            Confirm Password
                        </label>
                        <ValidationMessage For="() => Reset.ConfirmPassword" 
                            class="text-danger" />
                    </div>
                    <button type="submit" class="w-100 btn btn-lg btn-primary">
                        Reset password
                    </button>
                </EditForm>
            }
        }
    </div>
</div>

@code {
    private bool passwordResetCodeSent, passwordResetSuccess, errors;
    private string[] errorList = [];

    [SupplyParameterFromForm(FormName = "forgot-password")]
    private InputModel Input { get; set; } = new();

    [SupplyParameterFromForm(FormName = "reset-password")]
    private ResetModel Reset { get; set; } = new();

    private async Task OnValidSubmitStep1Async()
    {
        passwordResetCodeSent = await Acct.ForgotPasswordAsync(Input.Email);
    }

    private async Task OnValidSubmitStep2Async()
    {
        var result = await Acct.ResetPasswordAsync(Input.Email, Reset.ResetCode, 
            Reset.NewPassword);

        if (result.Succeeded)
        {
            passwordResetSuccess = true;

        }
        else
        {
            errors = true;
            errorList = result.ErrorList;
        }
    }

    private sealed class InputModel
    {
        [Required]
        [EmailAddress]
        [Display(Name = "Email")]
        public string Email { get; set; } = string.Empty;
    }

    private sealed class ResetModel
    {
        [Required]
        [Base64String]
        public string ResetCode { get; set; } = string.Empty;

        [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at " +
            "max {1} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string NewPassword { get; set; } = string.Empty;

        [DataType(DataType.Password)]
        [Display(Name = "Confirm password")]
        [Compare("NewPassword", ErrorMessage = "The new password and confirmation " +
            "password don't match.")]
        public string ConfirmPassword { get; set; } = string.Empty;
    }
}

No componente Login (Components/Identity/Login.razor) do projeto cliente imediatamente antes do fechamento </NotAuthorized> tag, adicione um link de senha esquecida para acessar o componente ForgotPassword:

<div>
    <a href="forgot-password">Forgot password</a>
</div>

Tempo limite para e-mail e atividade

O tempo limite de inatividade padrão é de 14 dias. No projeto de servidor, o código a seguir define o tempo limite de inatividade para cinco dias com expiração deslizante:

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

Alterar todos os períodos de validade dos tokens do ASP.NET Core Data Protection

No projeto de servidor, o código a seguir altera o período de tempo limite dos tokens de Proteção de Dados para três horas:

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

Os tokens de usuário Identity internos (AspNetCore/src/Identity/Extensions.Core/src/TokenOptions.cs) têm um tempo limite de um dia .

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Alternar ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Alterar a vida útil do token de e-mail

A duração padrão dos tokens de usuário Identity é de um dia .

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Alternar ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Para alterar o tempo de vida do token de e-mail, adicione um DataProtectorTokenProvider<TUser> personalizado e o DataProtectionTokenProviderOptions ao projeto do servidor.

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

Configure os serviços para usar o provedor de token personalizado no arquivo Program do projeto de servidor:

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

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

Solução de problemas

Se não conseguir que o e-mail funcione:

  • Defina um ponto de interrupção no EmailSender.Execute para verificar se SendEmailAsync é chamado.
  • Crie um aplicativo de console para enviar e-mails usando um código semelhante ao EmailSender.Execute para depurar o problema.
  • Analise as páginas do histórico de e-mail da conta no site do provedor de e-mail.
  • Verifique se há mensagens na sua pasta de spam.
  • Experimente outro alias de e-mail num fornecedor de e-mail diferente, como a Microsoft, o Yahoo ou o Gmail.
  • Tente enviar para diferentes contas de e-mail.

Recursos adicionais