Introducción a Identity en ASP.NET Core

Por Rick Anderson

Identity de ASP.NET Core:

  • Es una API que admite la funcionalidad de inicio de sesión de la interfaz de usuario (UI).
  • Administra usuarios, contraseñas, datos de perfil, roles, notificaciones, tokens, confirmación por correo electrónico, etc.

Los usuarios pueden crear una cuenta con la información de inicio de sesión almacenada en Identity o pueden usar un proveedor de inicio de sesión externo. Entre los proveedores de inicio de sesión externos admitidos se incluyen Facebook, Google, Cuenta Microsoft y Twitter.

Para obtener información sobre cómo requerir globalmente la autenticación de todos los usuarios, vea Requerir usuarios autenticados.

El código fuente de Identity está disponible en GitHub. Haga scaffolding Identity y vea los archivos generados para revisar la interacción de la plantilla con Identity.

Identity suele configurarse usando una base de datos de SQL Server para almacenar nombres de usuario, contraseñas y datos de perfil. Como alternativa, se puede usar otro almacén persistente, por ejemplo, Azure Table Storage.

En este tema, obtendrá información sobre cómo usar Identity para registrar un usuario e iniciar o cerrar su sesión. Nota: las plantillas tratan el nombre de usuario y el correo electrónico como los mismos para los usuarios. Para obtener instrucciones más detalladas sobre cómo crear aplicaciones que usan Identity, consulte Pasos siguientes.

ASP.NET Core Identity no está relacionado con la Plataforma de identidad de Microsoft. La Plataforma de identidad de Microsoft es:

  • Una evolución de la plataforma para desarrolladores de Azure Active Directory (Azure AD).
  • Una solución de identidad alternativa para la autenticación y autorización en aplicaciones de ASP.NET Core.

ASP.NET Core Identity agrega la funcionalidad de inicio de sesión de la interfaz de usuario (IU) a las aplicaciones web de ASP.NET Core. Para proteger las API web y las SPA, use una de las siguientes opciones:

Duende Identity Server es un marco de OpenID Connect y OAuth 2.0 para ASP.NET Core. Duende Identity Server permite las siguientes características de seguridad:

  • Autenticación como servicio (AaaS)
  • Inicio de sesión único (SSO) mediante varios tipos de aplicaciones
  • Control de acceso para API
  • Federation Gateway

Importante

Duende Software puede requerir que pague una tarifa de licencia para el uso en producción de Duende Identity Server. Para más información, vea Migración de ASP.NET Core 5.0 a 6.0.

Para más información, consulte la documentación de Duende Identity Server (sitio web de Duende Software).

Vea o descargue el código de ejemplo (cómo descargarlo).

Creación de una aplicación web con autenticación

Cree un proyecto de aplicación web de ASP.NET Core con cuentas de usuario individuales.

  • Seleccione la plantilla Aplicación web ASP.NET Core. Asigne al proyecto el nombre WebApp1 para que tenga el mismo espacio de nombres que la descarga del proyecto. Haga clic en Aceptar.
  • En la entrada Tipo de autenticación, seleccione Cuentas de usuario individuales.

El proyecto generado proporciona ASP.NET Core Identity como biblioteca de clases Razor. La biblioteca de clases IdentityRazor expone puntos de conexión con el área Identity. Por ejemplo:

  • /Identity/Account/Login
  • /Identity/Account/Logout
  • /Identity/Account/Manage

Aplicación de migraciones

Aplique las migraciones para inicializar la base de datos.

Ejecute el siguiente comando en la consola del administrador de paquetes (PMC):

Update-Database

Prueba del registro e inicio de sesión

Ejecute la aplicación y registre un usuario. En función del tamaño de la pantalla, es posible que tenga que seleccionar el botón de alternancia de navegación para ver los vínculos Registrar e Iniciar sesión .

Ver la base de datos Identity

  • En el menú Ver, seleccione Explorador de objetos de SQL Server (SSOX).
  • Vaya a (localdb)MSSQLLocalDB(SQL Server 13). Haga clic con el botón derecho en dbo. AspNetUsers>Ver datos:

Contextual menu on AspNetUsers table in SQL Server Object Explorer

Configuración de servicios de Identity

Los servicios se agregan en Program.cs. El patrón típico es llamar a métodos en el orden siguiente:

  1. Add{Service}
  2. builder.Services.Configure{Service}
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebApp1.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

builder.Services.Configure<IdentityOptions>(options =>
{
    // Password settings.
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequireUppercase = true;
    options.Password.RequiredLength = 6;
    options.Password.RequiredUniqueChars = 1;

    // Lockout settings.
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.Lockout.AllowedForNewUsers = true;

    // User settings.
    options.User.AllowedUserNameCharacters =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
    options.User.RequireUniqueEmail = false;
});

builder.Services.ConfigureApplicationCookie(options =>
{
    // Cookie settings
    options.Cookie.HttpOnly = true;
    options.ExpireTimeSpan = TimeSpan.FromMinutes(5);

    options.LoginPath = "/Identity/Account/Login";
    options.AccessDeniedPath = "/Identity/Account/AccessDenied";
    options.SlidingExpiration = true;
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();

app.Run();

El código anterior configura Identity con los valores predeterminados de las opciones. Los servicios se ponen a disposición de la aplicación mediante inyección de dependencias.

Identity se habilita llamando a UseAuthentication. UseAuthentication agrega la autenticación de middleware a la canalización de solicitudes.

La aplicación generada por la plantilla no usa la autorización. app.UseAuthorization se incluye para asegurar que se agrega en el orden correcto en caso de que la aplicación agregue la autorización. Se debe llamar a UseRouting, UseAuthentication y UseAuthorization en el orden mostrado en el código anterior.

Para más información sobre IdentityOptions, consulte IdentityOptions e Inicio de la aplicación.

Scaffold Register, Login, LogOut y RegisterConfirmation

Agregue los archivos Register, Login, LogOut y RegisterConfirmation. Siga las instrucciones de Identidad de scaffolding en un proyecto de Razor con autorización para generar el código que se muestra en esta sección.

Examen del registro

Cuando un usuario hace clic en el botón Registrar de la página Register, se invoca la acción RegisterModel.OnPostAsync. El usuario es creado por CreateAsync(TUser) en el objeto _userManager:

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
                                          .ToList();
    if (ModelState.IsValid)
    {
        var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
        var result = await _userManager.CreateAsync(user, Input.Password);
        if (result.Succeeded)
        {
            _logger.LogInformation("User created a new account with password.");

            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
            var callbackUrl = Url.Page(
                "/Account/ConfirmEmail",
                pageHandler: null,
                values: new { area = "Identity", userId = user.Id, code = code },
                protocol: Request.Scheme);

            await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

            if (_userManager.Options.SignIn.RequireConfirmedAccount)
            {
                return RedirectToPage("RegisterConfirmation", 
                                      new { email = Input.Email });
            }
            else
            {
                await _signInManager.SignInAsync(user, isPersistent: false);
                return LocalRedirect(returnUrl);
            }
        }
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

Deshabilitación de la comprobación de la cuenta predeterminada

Con las plantillas predeterminadas, se redirige al usuario al Account.RegisterConfirmation donde puede seleccionar un vínculo para que se confirme la cuenta. El Account.RegisterConfirmation predeterminado se usa solo para pruebas, la verificación automática de cuentas debería estar desactivada en una aplicación de producción.

Para requerir una cuenta confirmada e impedir el inicio de sesión inmediato en el registro, establezca DisplayConfirmAccountLink = false en /Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs:

[AllowAnonymous]
public class RegisterConfirmationModel : PageModel
{
    private readonly UserManager<IdentityUser> _userManager;
    private readonly IEmailSender _sender;

    public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender)
    {
        _userManager = userManager;
        _sender = sender;
    }

    public string Email { get; set; }

    public bool DisplayConfirmAccountLink { get; set; }

    public string EmailConfirmationUrl { get; set; }

    public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
    {
        if (email == null)
        {
            return RedirectToPage("/Index");
        }

        var user = await _userManager.FindByEmailAsync(email);
        if (user == null)
        {
            return NotFound($"Unable to load user with email '{email}'.");
        }

        Email = email;
        // Once you add a real email sender, you should remove this code that lets you confirm the account
        DisplayConfirmAccountLink = false;
        if (DisplayConfirmAccountLink)
        {
            var userId = await _userManager.GetUserIdAsync(user);
            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
            EmailConfirmationUrl = Url.Page(
                "/Account/ConfirmEmail",
                pageHandler: null,
                values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
                protocol: Request.Scheme);
        }

        return Page();
    }
}

Iniciar sesión

El formulario de inicio de sesión aparece cuando:

  • Se selecciona el vínculo Iniciar sesión.
  • Un usuario intenta acceder a una página restringida a la que no está autorizado para acceder o cuando el sistema no lo ha autenticado.

Cuando se envía el formulario en la página Inicio de sesión, se llama a la acción OnPostAsync. Se llama a PasswordSignInAsync en el objeto _signInManager.

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");

    if (ModelState.IsValid)
    {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, 
        // set lockoutOnFailure: true
        var result = await _signInManager.PasswordSignInAsync(Input.Email,
                           Input.Password, Input.RememberMe, lockoutOnFailure: true);
        if (result.Succeeded)
        {
            _logger.LogInformation("User logged in.");
            return LocalRedirect(returnUrl);
        }
        if (result.RequiresTwoFactor)
        {
            return RedirectToPage("./LoginWith2fa", new
            {
                ReturnUrl = returnUrl,
                RememberMe = Input.RememberMe
            });
        }
        if (result.IsLockedOut)
        {
            _logger.LogWarning("User account locked out.");
            return RedirectToPage("./Lockout");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return Page();
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

Para obtener información sobre cómo tomar decisiones de autorización, consulte Introducción a la autorización en ASP.NET Core.

Cerrar la sesión

El vínculo Cerrar sesión invoca la acción LogoutModel.OnPost.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace WebApp1.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class LogoutModel : PageModel
    {
        private readonly SignInManager<IdentityUser> _signInManager;
        private readonly ILogger<LogoutModel> _logger;

        public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger)
        {
            _signInManager = signInManager;
            _logger = logger;
        }

        public void OnGet()
        {
        }

        public async Task<IActionResult> OnPost(string returnUrl = null)
        {
            await _signInManager.SignOutAsync();
            _logger.LogInformation("User logged out.");
            if (returnUrl != null)
            {
                return LocalRedirect(returnUrl);
            }
            else
            {
                return RedirectToPage();
            }
        }
    }
}

En el código anterior, el código return RedirectToPage(); debe ser un redireccionamiento para que el explorador realice una nueva solicitud y la identidad del usuario se actualice.

SignOutAsync borra las notificaciones del usuario almacenadas en cookie.

La publicación se especifica en Pages/Shared/_LoginPartial.cshtml:

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

<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
    <li class="nav-item">
        <a  class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" 
                                              title="Manage">Hello @User.Identity.Name!</a>
    </li>
    <li class="nav-item">
        <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" 
                                  asp-route-returnUrl="@Url.Page("/", new { area = "" })" 
                                  method="post" >
            <button  type="submit" class="nav-link btn btn-link text-dark">Logout</button>
        </form>
    </li>
}
else
{
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li>
}
</ul>

Probar Identity

Las plantillas de proyecto web predeterminadas permiten el acceso anónimo a las páginas principales. Para probar Identity, agregue [Authorize]:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace WebApp1.Pages
{
    [Authorize]
    public class PrivacyModel : PageModel
    {
        private readonly ILogger<PrivacyModel> _logger;

        public PrivacyModel(ILogger<PrivacyModel> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {
        }
    }
}

Si ha iniciado sesión, cierre la sesión. Ejecute la aplicación y seleccione el vínculo Privacy. Se le redirige a la página de inicio de sesión.

Explore Identity

Para explorar Identity con más detalle:

Identity Components

Todos los paquetes NuGet dependientes de Identity se incluyen en el marco compartido de ASP.NET Core.

El paquete principal para Identity es Microsoft.AspNetCore.Identity. Este paquete contiene el conjunto básico de interfaces para ASP.NET Core Identity, y está incluido por Microsoft.AspNetCore.Identity.EntityFrameworkCore.

Migración a ASP.NET Core Identity

Para más información e instrucciones sobre cómo migrar el almacén existente Identity, consulte Migración de la autenticación y Identity.

Establecimiento de la seguridad de la contraseña

Consulte Configuración para obtener un ejemplo que establece los requisitos mínimos de contraseña.

AddDefaultIdentity y AddIdentity

AddDefaultIdentity se introdujo en ASP.NET Core 2.1. Llamar a AddDefaultIdentity es similar a llamar a lo siguiente:

Consulte Origen de AddDefaultIdentity) para más información.

Impedir la publicación de recursos estáticos Identity

Para evitar la publicación de recursos estáticos Identity (hojas de estilos y archivos de JavaScript para la interfaz de usuario de Identity) en la raíz web, agregue la siguiente propiedad ResolveStaticWebAssetsInputsDependsOn y el destino RemoveIdentityAssets al archivo de proyecto de la aplicación:

<PropertyGroup>
  <ResolveStaticWebAssetsInputsDependsOn>RemoveIdentityAssets</ResolveStaticWebAssetsInputsDependsOn>
</PropertyGroup>

<Target Name="RemoveIdentityAssets">
  <ItemGroup>
    <StaticWebAsset Remove="@(StaticWebAsset)" Condition="%(SourceId) == 'Microsoft.AspNetCore.Identity.UI'" />
  </ItemGroup>
</Target>

Pasos siguientes

Por Rick Anderson

Identity de ASP.NET Core:

  • Es una API que admite la funcionalidad de inicio de sesión de la interfaz de usuario (UI).
  • Administra usuarios, contraseñas, datos de perfil, roles, notificaciones, tokens, confirmación por correo electrónico, etc.

Los usuarios pueden crear una cuenta con la información de inicio de sesión almacenada en Identity o pueden usar un proveedor de inicio de sesión externo. Entre los proveedores de inicio de sesión externos admitidos se incluyen Facebook, Google, Cuenta Microsoft y Twitter.

Para obtener información sobre cómo requerir globalmente la autenticación de todos los usuarios, vea Requerir usuarios autenticados.

El código fuente de Identity está disponible en GitHub. Haga scaffolding Identity y vea los archivos generados para revisar la interacción de la plantilla con Identity.

Identity suele configurarse usando una base de datos de SQL Server para almacenar nombres de usuario, contraseñas y datos de perfil. Como alternativa, se puede usar otro almacén persistente, por ejemplo, Azure Table Storage.

En este tema, obtendrá información sobre cómo usar Identity para registrar un usuario e iniciar o cerrar su sesión. Nota: las plantillas tratan el nombre de usuario y el correo electrónico como los mismos para los usuarios. Para obtener instrucciones más detalladas sobre cómo crear aplicaciones que usan Identity, consulte Pasos siguientes.

La Plataforma de Identidad de Microsoft es:

  • Una evolución de la plataforma para desarrolladores de Azure Active Directory (Azure AD).
  • Una solución de identidad alternativa para la autenticación y autorización en aplicaciones de ASP.NET Core.
  • No relacionado con ASP.NET Core Identity.

ASP.NET Core Identity agrega la funcionalidad de inicio de sesión de la interfaz de usuario (IU) a las aplicaciones web de ASP.NET Core. Para proteger las API web y las SPA, use una de las siguientes opciones:

Duende IdentityServer es un marco de OpenID Connect y OAuth 2.0 para ASP.NET Core. Duende IdentityServer permite las siguientes características de seguridad:

  • Autenticación como servicio (AaaS)
  • Inicio de sesión único (SSO) mediante varios tipos de aplicaciones
  • Control de acceso para API
  • Federation Gateway

Para más información, consulte Información general de Duende IdentityServer.

Para más información sobre otros proveedores de autenticación, consulte Opciones de autenticación de OSS de la comunidad para ASP.NET Core.

Vea o descargue el código de ejemplo (cómo descargarlo).

Creación de una aplicación web con autenticación

Cree un proyecto de aplicación web de ASP.NET Core con cuentas de usuario individuales.

  • Seleccione Archivo>Nuevo>Proyecto.
  • Seleccione Aplicación web de ASP.NET Core. Asigne al proyecto el nombre WebApp1 para que tenga el mismo espacio de nombres que la descarga del proyecto. Haga clic en Aceptar.
  • Seleccione una aplicación web de ASP.NET Core y seleccione Cambiar autenticación.
  • Seleccione Cuentas de usuario individuales y haga clic en Aceptar.

El proyecto generado proporciona ASP.NET Core Identity como biblioteca de clases Razor. La biblioteca de clases IdentityRazor expone puntos de conexión con el área Identity. Por ejemplo:

  • /Identity/Account/Login
  • /Identity/Account/Logout
  • /Identity/Account/Manage

Aplicación de migraciones

Aplique las migraciones para inicializar la base de datos.

Ejecute el siguiente comando en la consola del administrador de paquetes (PMC):

PM> Update-Database

Prueba del registro e inicio de sesión

Ejecute la aplicación y registre un usuario. En función del tamaño de la pantalla, es posible que tenga que seleccionar el botón de alternancia de navegación para ver los vínculos Registrar e Iniciar sesión .

Ver la base de datos Identity

  • En el menú Ver, seleccione Explorador de objetos de SQL Server (SSOX).
  • Vaya a (localdb)MSSQLLocalDB(SQL Server 13). Haga clic con el botón derecho en dbo. AspNetUsers>Ver datos:

Contextual menu on AspNetUsers table in SQL Server Object Explorer

Configuración de servicios de Identity

Los servicios se agregan en ConfigureServices. El patrón habitual consiste en llamar a todos los métodos Add{Service} y, luego, a todos los métodos services.Configure{Service}.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
     // options.UseSqlite(
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();

    services.Configure<IdentityOptions>(options =>
    {
        // Password settings.
        options.Password.RequireDigit = true;
        options.Password.RequireLowercase = true;
        options.Password.RequireNonAlphanumeric = true;
        options.Password.RequireUppercase = true;
        options.Password.RequiredLength = 6;
        options.Password.RequiredUniqueChars = 1;

        // Lockout settings.
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
        options.Lockout.MaxFailedAccessAttempts = 5;
        options.Lockout.AllowedForNewUsers = true;

        // User settings.
        options.User.AllowedUserNameCharacters =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
        options.User.RequireUniqueEmail = false;
    });

    services.ConfigureApplicationCookie(options =>
    {
        // Cookie settings
        options.Cookie.HttpOnly = true;
        options.ExpireTimeSpan = TimeSpan.FromMinutes(5);

        options.LoginPath = "/Identity/Account/Login";
        options.AccessDeniedPath = "/Identity/Account/AccessDenied";
        options.SlidingExpiration = true;
    });
}

El código resaltado anterior configura Identity con los valores predeterminados de las opciones. Los servicios se ponen a disposición de la aplicación mediante inyección de dependencias.

Identity se habilita llamando a UseAuthentication. UseAuthentication agrega la autenticación de middleware a la canalización de solicitudes.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        // options.UseSqlite(
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDatabaseDeveloperPageExceptionFilter();
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();

    services.Configure<IdentityOptions>(options =>
    {
        // Password settings.
        options.Password.RequireDigit = true;
        options.Password.RequireLowercase = true;
        options.Password.RequireNonAlphanumeric = true;
        options.Password.RequireUppercase = true;
        options.Password.RequiredLength = 6;
        options.Password.RequiredUniqueChars = 1;

        // Lockout settings.
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
        options.Lockout.MaxFailedAccessAttempts = 5;
        options.Lockout.AllowedForNewUsers = true;

        // User settings.
        options.User.AllowedUserNameCharacters =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
        options.User.RequireUniqueEmail = false;
    });

    services.ConfigureApplicationCookie(options =>
    {
        // Cookie settings
        options.Cookie.HttpOnly = true;
        options.ExpireTimeSpan = TimeSpan.FromMinutes(5);

        options.LoginPath = "/Identity/Account/Login";
        options.AccessDeniedPath = "/Identity/Account/AccessDenied";
        options.SlidingExpiration = true;
    });
}

El código anterior configura Identity con los valores predeterminados de las opciones. Los servicios se ponen a disposición de la aplicación mediante inyección de dependencias.

Identity se habilita llamando a UseAuthentication. UseAuthentication agrega la autenticación de middleware a la canalización de solicitudes.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseMigrationsEndPoint();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

La aplicación generada por la plantilla no usa la autorización. app.UseAuthorization se incluye para asegurar que se agrega en el orden correcto en caso de que la aplicación agregue la autorización. Se debe llamar a UseRouting, UseAuthentication, UseAuthorization y UseEndpoints en el orden mostrado en el código anterior.

Para más información sobre IdentityOptions y Startup, consulte IdentityOptions e Inicio de la aplicación.

Scaffold Register, Login, LogOut y RegisterConfirmation

Agregue los archivos Register, Login, LogOut y RegisterConfirmation. Siga las instrucciones de Identidad de scaffolding en un proyecto de Razor con autorización para generar el código que se muestra en esta sección.

Examen del registro

Cuando un usuario hace clic en el botón Registrar de la página Register, se invoca la acción RegisterModel.OnPostAsync. El usuario es creado por CreateAsync(TUser) en el objeto _userManager:

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
                                          .ToList();
    if (ModelState.IsValid)
    {
        var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
        var result = await _userManager.CreateAsync(user, Input.Password);
        if (result.Succeeded)
        {
            _logger.LogInformation("User created a new account with password.");

            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
            var callbackUrl = Url.Page(
                "/Account/ConfirmEmail",
                pageHandler: null,
                values: new { area = "Identity", userId = user.Id, code = code },
                protocol: Request.Scheme);

            await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

            if (_userManager.Options.SignIn.RequireConfirmedAccount)
            {
                return RedirectToPage("RegisterConfirmation", 
                                      new { email = Input.Email });
            }
            else
            {
                await _signInManager.SignInAsync(user, isPersistent: false);
                return LocalRedirect(returnUrl);
            }
        }
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

Deshabilitación de la comprobación de la cuenta predeterminada

Con las plantillas predeterminadas, se redirige al usuario al Account.RegisterConfirmation donde puede seleccionar un vínculo para que se confirme la cuenta. El Account.RegisterConfirmation predeterminado se usa solo para pruebas, la verificación automática de cuentas debería estar desactivada en una aplicación de producción.

Para requerir una cuenta confirmada e impedir el inicio de sesión inmediato en el registro, establezca DisplayConfirmAccountLink = false en /Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs:

[AllowAnonymous]
public class RegisterConfirmationModel : PageModel
{
    private readonly UserManager<IdentityUser> _userManager;
    private readonly IEmailSender _sender;

    public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender)
    {
        _userManager = userManager;
        _sender = sender;
    }

    public string Email { get; set; }

    public bool DisplayConfirmAccountLink { get; set; }

    public string EmailConfirmationUrl { get; set; }

    public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
    {
        if (email == null)
        {
            return RedirectToPage("/Index");
        }

        var user = await _userManager.FindByEmailAsync(email);
        if (user == null)
        {
            return NotFound($"Unable to load user with email '{email}'.");
        }

        Email = email;
        // Once you add a real email sender, you should remove this code that lets you confirm the account
        DisplayConfirmAccountLink = false;
        if (DisplayConfirmAccountLink)
        {
            var userId = await _userManager.GetUserIdAsync(user);
            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
            EmailConfirmationUrl = Url.Page(
                "/Account/ConfirmEmail",
                pageHandler: null,
                values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
                protocol: Request.Scheme);
        }

        return Page();
    }
}

Iniciar sesión

El formulario de inicio de sesión aparece cuando:

  • Se selecciona el vínculo Iniciar sesión.
  • Un usuario intenta acceder a una página restringida a la que no está autorizado para acceder o cuando el sistema no lo ha autenticado.

Cuando se envía el formulario en la página Inicio de sesión, se llama a la acción OnPostAsync. Se llama a PasswordSignInAsync en el objeto _signInManager.

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");

    if (ModelState.IsValid)
    {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, 
        // set lockoutOnFailure: true
        var result = await _signInManager.PasswordSignInAsync(Input.Email,
                           Input.Password, Input.RememberMe, lockoutOnFailure: true);
        if (result.Succeeded)
        {
            _logger.LogInformation("User logged in.");
            return LocalRedirect(returnUrl);
        }
        if (result.RequiresTwoFactor)
        {
            return RedirectToPage("./LoginWith2fa", new
            {
                ReturnUrl = returnUrl,
                RememberMe = Input.RememberMe
            });
        }
        if (result.IsLockedOut)
        {
            _logger.LogWarning("User account locked out.");
            return RedirectToPage("./Lockout");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return Page();
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

Para obtener información sobre cómo tomar decisiones de autorización, consulte Introducción a la autorización en ASP.NET Core.

Cerrar la sesión

El vínculo Cerrar sesión invoca la acción LogoutModel.OnPost.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace WebApp1.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class LogoutModel : PageModel
    {
        private readonly SignInManager<IdentityUser> _signInManager;
        private readonly ILogger<LogoutModel> _logger;

        public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger)
        {
            _signInManager = signInManager;
            _logger = logger;
        }

        public void OnGet()
        {
        }

        public async Task<IActionResult> OnPost(string returnUrl = null)
        {
            await _signInManager.SignOutAsync();
            _logger.LogInformation("User logged out.");
            if (returnUrl != null)
            {
                return LocalRedirect(returnUrl);
            }
            else
            {
                return RedirectToPage();
            }
        }
    }
}

En el código anterior, el código return RedirectToPage(); debe ser un redireccionamiento para que el explorador realice una nueva solicitud y la identidad del usuario se actualice.

SignOutAsync borra las notificaciones del usuario almacenadas en cookie.

La publicación se especifica en Pages/Shared/_LoginPartial.cshtml:

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

<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
    <li class="nav-item">
        <a  class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" 
                                              title="Manage">Hello @User.Identity.Name!</a>
    </li>
    <li class="nav-item">
        <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" 
                                  asp-route-returnUrl="@Url.Page("/", new { area = "" })" 
                                  method="post" >
            <button  type="submit" class="nav-link btn btn-link text-dark">Logout</button>
        </form>
    </li>
}
else
{
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li>
}
</ul>

Probar Identity

Las plantillas de proyecto web predeterminadas permiten el acceso anónimo a las páginas principales. Para probar Identity, agregue [Authorize]:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace WebApp1.Pages
{
    [Authorize]
    public class PrivacyModel : PageModel
    {
        private readonly ILogger<PrivacyModel> _logger;

        public PrivacyModel(ILogger<PrivacyModel> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {
        }
    }
}

Si ha iniciado sesión, cierre la sesión. Ejecute la aplicación y seleccione el vínculo Privacy. Se le redirige a la página de inicio de sesión.

Explore Identity

Para explorar Identity con más detalle:

Identity Components

Todos los paquetes NuGet dependientes de Identity se incluyen en el marco compartido de ASP.NET Core.

El paquete principal para Identity es Microsoft.AspNetCore.Identity. Este paquete contiene el conjunto básico de interfaces para ASP.NET Core Identity, y está incluido por Microsoft.AspNetCore.Identity.EntityFrameworkCore.

Migración a ASP.NET Core Identity

Para más información e instrucciones sobre cómo migrar el almacén existente Identity, consulte Migración de la autenticación y Identity.

Establecimiento de la seguridad de la contraseña

Consulte Configuración para obtener un ejemplo que establece los requisitos mínimos de contraseña.

Impedir la publicación de recursos estáticos Identity

Para evitar la publicación de recursos estáticos Identity (hojas de estilos y archivos de JavaScript para la interfaz de usuario de Identity) en la raíz web, agregue la siguiente propiedad ResolveStaticWebAssetsInputsDependsOn y el destino RemoveIdentityAssets al archivo de proyecto de la aplicación:

<PropertyGroup>
  <ResolveStaticWebAssetsInputsDependsOn>RemoveIdentityAssets</ResolveStaticWebAssetsInputsDependsOn>
</PropertyGroup>

<Target Name="RemoveIdentityAssets">
  <ItemGroup>
    <StaticWebAsset Remove="@(StaticWebAsset)" Condition="%(SourceId) == 'Microsoft.AspNetCore.Identity.UI'" />
  </ItemGroup>
</Target>

Pasos siguientes