Confirmación de la cuenta y recuperación de contraseñas en ASP.NET Core Blazor

En este artículo se explica cómo configurar una aplicación web de ASP.NET Core Blazor con confirmación de correo electrónico y recuperación de contraseñas.

Espacio de nombres

El espacio de nombres de la aplicación usado por el ejemplo de este artículo es BlazorSample. Actualice los ejemplos de código para usar el espacio de nombres de la aplicación.

Selección y configuración de un proveedor de correo electrónico

En este artículo, API transaccional de Mailchimp se usa a través de Mandrill.net para enviar correo electrónico. Se recomienda usar un servicio de correo electrónico para enviar correo electrónico en lugar de SMTP. SMTP es difícil de configurar y proteger correctamente. Independientemente del servicio de correo electrónico que use, acceda a sus instrucciones para aplicaciones .NET, cree una cuenta, configure una clave de API para su servicio e instale los paquetes NuGet necesarios.

Cree una clase para capturar la clave de API de correo electrónico segura. En el ejemplo de este artículo se usa una clase denominada AuthMessageSenderOptions con una propiedad EmailAuthKey para contener la clave.

AuthMessageSenderOptions:

namespace BlazorSample;

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

Registre la instancia de configuración AuthMessageSenderOptions en el archivoProgram:

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

Configuración de un secreto de usuario para la clave de seguridad del proveedor

Establezca la clave con la herramienta secret-manager. En el ejemplo siguiente, el nombre de clave es EmailAuthKey y la clave se representa mediante el marcador de posición{KEY}. En un shell de comandos, vaya a la carpeta raíz de la aplicación y ejecute el siguiente comando con la clave de API:

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

Para más información, consulte Almacenamiento seguro de secretos de aplicación en el desarrollo en ASP.NET Core

Implemente IEmailSender

Implemente IEmailSender para el proveedor. El ejemplo siguiente se basa en la API transaccional de Mailchimp mediante Mandrill.net. Para un proveedor diferente, consulte su documentación sobre cómo implementar el envío de un mensaje en el método Execute.

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(ApplicationUser user, string email, 
        string confirmationLink) => SendEmailAsync(email, "Confirm your email", 
        $"Please confirm your account by " +
        "<a href='{confirmationLink}'>clicking here</a>.");

    public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, 
        string resetLink) => SendEmailAsync(email, "Reset your password", 
        $"Please reset your password by <a href='{resetLink}'>clicking here</a>.");

    public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, 
        string resetCode) => SendEmailAsync(email, "Reset your password", 
        $"Please reset your password using the following code: {resetCode}");

    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:

El contenido del cuerpo de los mensajes puede requerir una codificación especial para el proveedor de servicios de correo electrónico. Si no se pueden seguir vínculos en el cuerpo del mensaje, consulte la documentación del proveedor de servicios.

Configuración de la aplicación para admitir correo electrónico

En el archivoProgram, cambie la implementación del remitente de correo electrónico a EmailSender:

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

Quite el IdentityNoOpEmailSender (Components/Account/IdentityNoOpEmailSender.cs) de la aplicación.

En el componente RegisterConfirmation (Components/Account/Pages/RegisterConfirmation.razor), quite el bloque condicional del bloque @code que comprueba si el EmailSender es un IdentityNoOpEmailSender:

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

Además, en el componenteRegisterConfirmation, quite el marcado Razor y el código para comprobar el campo emailConfirmationLink, dejando solo la línea que indica al usuario que compruebe su correo electrónico...

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

@code {
-    private string? emailConfirmationLink;

     ...
}

Tiempo de espera de correo electrónico y actividad

El tiempo de inactividad por defecto es de 14 días. El código siguiente establece el tiempo de espera de inactividad en 5 días con expiración deslizante:

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

Cambio de toda la duración del token de protección de datos

El código siguiente cambia el período de tiempo de espera de todos los tokens de protección de datos a 3 horas:

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

Los tokens de usuario integrados de Identity (AspNetCore/src/Identity/Extensions.Core/src/TokenOptions.cs) tienen un tiempo de espera de un día.

Nota:

Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, use la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Cambiar la duración del token de correo electrónico

La duración predeterminada del token de los Identitytokens de usuario es de un día.

Nota:

Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, use la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Para cambiar la duración del token de correo electrónico, agregue un personalizado DataProtectorTokenProvider<TUser> y 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);
    }
}

Configurar los servicios para que usen el proveedor de tokens personalizado en el archivo Program:

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

Solución de problemas

Si no puede obtener el correo electrónico funcionando:

  • Establezca un punto de interrupción en EmailSender.Execute para comprobar que se llama a SendEmailAsync.
  • Crear una aplicación de consola para enviar correo electrónico mediante código similar a EmailSender.Execute para depurar el problema.
  • Revisar las páginas del historial de correo electrónico de la cuenta en el sitio web del proveedor de correo electrónico.
  • Compruebe la carpeta de correo no deseado para ver los mensajes.
  • Pruebe otro alias de correo electrónico en otro proveedor de correo electrónico, como Microsoft, Yahoo o Gmail.
  • Intente enviar a diferentes cuentas de correo electrónico.

Advertencia

No use secretos de producción en pruebas y desarrollo. Si publica la aplicación en Azure, establezca secretos como configuración de la aplicación en el portal de Azure Web App. El sistema de configuración se configura para leer las claves de las variables de entorno.

Habilitación de la confirmación de la cuenta después de que un sitio tenga usuarios

Habilitar la confirmación de la cuenta en un sitio con usuarios bloquea todos los usuarios existentes. Los usuarios existentes están bloqueados porque sus cuentas no están confirmadas. Para solucionar el bloqueo de usuario existente, use uno de los métodos siguientes:

  • Actualice la base de datos para marcar todos los usuarios existentes como confirmados.
  • Confirme los usuarios existentes. Por ejemplo, los correos electrónicos de envío por lotes con vínculos de confirmación.

Recursos adicionales