Saltar al contenido

Usuarios personalizados con Identity en ASP.NET Core

Compartir en:

El framework de Microsoft ASP.NET core tiene muchas funcionalidades que nos facilitan mucho la vida y estandarizan nuestros desarrollos aportando seguridad y eficiencia. Identity es una de las API’s que podemos utilizar para delegar el proceso de autenticación y autorización de usuarios.

Me he basado para explicar un poco esto en los tutoriales de Microsoft que conviene leer.

Identity es muy potente y extenso, tiene muchos frentes y varias ramificaciones, en este post veremos de forma general como construir una autenticación sencilla en nuestra plataforma.

Antes de nada, hay que comentar que Identity, además de funcionar con cuentas locales, puede usar proveedores de inicio de sesión externos como Google, Microsoft o Facebook. Empezaremos configurando la identidad local y en otros post comentaremos las otras formas de demostrar quienes somos.

Los paquetes NuGet que debemos incluir (además de Entity Framework Core) son los siguientes:

Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.AspNetCore.Identity.UI

Generalmente, si creamos un proyecto MVC desde las plantillas de VS todo esto ya viene implementado, pero conviene comentarlo para entender cómo funciona.

También podemos añadir los paquetes manualmente:

dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Identity.UI

Para poder trabajar con datos, debemos aplicar la correspondiente migración inicial para que se creen las tablas necesarias, para esto:

dotnet ef database update

O si estáis en visual estudio, desde la consola del administrador de paquetes:

PM> Update-Database

El paquete que añadimos Microsoft.AspNetCore.Identity.EntityFrameworkCore nos da la capacidad de trabajar con ASP.CORE Identity y poder utilizar en nuestro contexto de base de datos la clase IdentityDbContext, heredando tas las características de esta.

El propio VS nos crea el contexto, si no, debemos crearlo nosotros por nuestra cuenta:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace IdentityPruebas.Data
{
    public class ApplicationDbContext : IdentityDbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
}

Además, debemos tener en cuenta nuestro pipeline, en este debemos agregar UseAutentication y UseAutorization, estos middelware hacen que las peticiones sean tratadas por Identity para validar accesos entre otras cosas. Lo añadimos en StartUp.Configure:

public void Configure(IApplicationBuilder app)
{           
	app.UseAuthentication();
	app.UseAuthorization();
}

Configuraremos también el servicio en nuestro ConfigureServices sin añadir de momento ninguna configuración:

services.AddDefaultIdentity<IdentityUser>().AddEntityFrameworkStores<ApplicationDbContext>();

En este momento ya podemos arrancar la aplicación y nuestros usuarios pueden registrarse:

Hay que cambiar a True el campo EmailConfirmed del usuario creado si queremos iniciar la sesión, ya que no tenemos habilitado ningún email de validación si hemos dejado la opción por defecto que nos crea Visual Studio.

options => options.SignIn.RequireConfirmedAccount = true

Si hemos creado el contexto sin opciones, nos permitirá entrar sin problemas.

Si husmeamos un poco en las tablas de nuestra base de datos confiugrada (por defecto una SQL local) vemos que identity se ha encargado de crear todo lo necesario para operar con nuestros usuarios.

En caso de que nuestra aplicación sea muy simple, podemos aprovechar la clase por defecto de IdentityUser. Yo siempre modifico este estándar para añadir nuevos campos que pueda necesitar, no hay nada peor que tener que cambiar esto en una aplicación de producción ya instalada y funcionando.

Para extender el usuario lo primero es crearnos una clase en donde tengamos nuestro modelo que extienda de IdentityUser

public class ApplicationUser : IdentityUser{
	[PersonalData]
	public int DepartamentID { get; set; }
	[PersonalData]
	public string FirstName { get; set; }
	[PersonalData]
	public string LastName { get; set; }
}

Es importante añadir también nuestro usuario personalizado en el contexto para que en la migración el Framework sepa que debe hacer cambios en el esquema.

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }

Una vez hecho esto, podemos hacer la migración.

Add-Migration InitialCreateUser

Y vemos el resultado (como se añaden los nuevos campos):

public partial class InitialCreateUsers : Migration {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AddColumn<int>(
                name: "DepartamentID",
                table: "AspNetUsers",
                nullable: false,
                defaultValue: 0);
            migrationBuilder.AddColumn<string>(
                name: "FirstName",
                table: "AspNetUsers",
                nullable: true);
            migrationBuilder.AddColumn<string>(
                name: "LastName",
                table: "AspNetUsers",
                nullable: true);
        }
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropColumn(
                name: "DepartamentID",
                table: "AspNetUsers");
            migrationBuilder.DropColumn(
                name: "FirstName",
                table: "AspNetUsers");
            migrationBuilder.DropColumn(
                name: "LastName",
                table: "AspNetUsers");
        }
    }

Actualizamos la base de datos con Update-Database y vemos los cambios.

Una vez hemos hecho los cambios, si ejecutamos la aplicación ya no nos dejara utilizar los usuarios. Esto es debido a que debemos cambiar el tipo de usuario que usamos de IdentityUser (el genérico de Identity) a ApplicationUser (nuestro usuario particular).

Primero debemos cambiar en la configuración (Método configureServices en Startup.cs) el usuario que añadimos a Identity por el nuestro particular:

services.AddDefaultIdentity<ApplicationUser>().AddEntityFrameworkStores<ApplicationDbContext>();

Esto ya nos permite operar, pero aún tenemos que cambiar en la vista el tipo de modelo fuertemente tipado que añadimos a esta. En Views/Shared/_LoginPartial.cshtml tenemos inyectados 2 servicios que son los que utiliza identity para hacer el login y gestionar los usuarios (SignInManager y UserManager), a estos debemos definirles un tipo de usuario que coincida con el que configuramos en Startup.cs:

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

Una vez cambiado esto, podemos iniciar la aplicación con normalidad:

Ya tenemos los datos en la base de datos, y la funcionalidad preparada, nos queda editar los controladores y las vistas de identity para poder interactuar con ellos.

Por defecto, identity no nos muestra toda la implementación de la librería (vistas, controladores etc), solo nos muestra una carpeta (Admin/Identity). Para poder desplegar y editar toda la potencia de la Api debemos hacer un Scaffold.

Para esto pulsamos botón derecho en la carpeta de Identity y pulsamos en agregar nuevo elemento con scaffolding, seleccionamos Identidad y pulsamos en agregar.

Una vez procesada la solicitud, VS nos mostrará una ventana donde tenemos que seleccionar las funcionalidades que queremos implementar en nuestra aplicación, coy a seleccionar todas y pulsamos agregar.

VS nos creará una carpeta en Identity/Pages con multitud de ficheros de las vistas que dispone identity.

Vamos a añadir a la página del perfil del usuario por ejemplo el primer nombre y el apellido. Lo primero es cambiar el InputModel de la página Index del Manager de identity, por tanto, lo abrimos e incluimos los datos en el inputModel:

public class InputModel
        {
            [Phone]
            [Display(Name = "Phone number")]
            public string PhoneNumber { get; set; }
            [Required]
            [DataType(DataType.Text)]
            [Display(Name = "First name")]
            public string FirstName { get; set; }
            [Required]
            [Display(Name = "Last name")]
            [DataType(DataType.Text)]
            public string LastName { get; set; }
        }

Cambiamos la carga en el mismo fichero:

private async Task LoadAsync(ApplicationUser user){
  	        var userName = await _userManager.GetUserNameAsync(user);
            var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
            Username = userName;
            Input = new InputModel
            {
                PhoneNumber = phoneNumber,
                FirstName = user.FirstName,
                LastName = user.LastName
            };
        }

Y cambiamos finalmente la actualización:

public async Task<IActionResult> OnPostAsync() {
            var user = await _userManager.GetUserAsync(User);
            if (user == null){
                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
            }
            if (!ModelState.IsValid){
                await LoadAsync(user);
                return Page();
            }
            var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
            if (Input.PhoneNumber != phoneNumber){
                var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
                if (!setPhoneResult.Succeeded){
                    StatusMessage = "Unexpected error when trying to set phone number.";
                    return RedirectToPage();
                }
            }		
		    //Aquí nuestros campos
            if (Input.FirstName != user.FirstName){
                user.FirstName = Input.FirstName;
            }
            if (Input.LastName != user.LastName){
                user.LastName = Input.LastName;
            }
		    //Después de validar actualizamos
            await _userManager.UpdateAsync(user);
            await _signInManager.RefreshSignInAsync(user);
            StatusMessage = "Your profile has been updated";
            return RedirectToPage();
        }

¡Bien! Solo nos queda cambiar la vista para poder mostrar los nuevos campos, añadimos, por tanto, los dos campos al formulario:

<div class="form-group">
	<label asp-for="Input.FirstName"></label>
	<input asp-for="Input.FirstName" class="form-control" />
</div>
<div class="form-group">
	<label asp-for="Input.LastName"></label>
	<input asp-for="Input.LastName" class="form-control" />
</div>

Y listo, podemos comprobar el resultado en nuestra app:

Y os preguntaréis, ¿para qué demonios quiere Jon una cuenta autorizada en la web si no puede hacer nada?, en el próximo artículo resumiré como habilitar una autorización basada en roles para dar permiso a Jon en nuestra app y que pueda realizar sus gestiones.


Juan Ibero

Inmerso en la Evolución Tecnológica. Ingeniero Informático especializado en la gestión segura de entornos TI e industriales, con un profundo énfasis en seguridad, arquitectura y programación. Siempre aprendiendo, siempre explorando.

Compartir en:

6 comentarios en «Usuarios personalizados con Identity en ASP.NET Core»

  1. Quiero expresar mi agradecimiento por encontrar un blog tan valioso y útil sobre la implementación de usuarios personalizados con Identity en ASP.NET Core. Este blog ha sido una verdadera salvación para mí, ya que me ha ahorrado tiempo y esfuerzo en investigar por mi cuenta.

    Antes de descubrir este blog, me sentía abrumado por la complejidad de Identity y sus múltiples funcionalidades. Estaba buscando recursos dispersos y luchando por comprender cómo implementar la autenticación y autorización de usuarios de manera efectiva. Sin embargo, gracias a este blog, pude obtener una guía clara y concisa sobre cómo construir una autenticación sencilla en mi plataforma.

    Lo que más valoro de este blog es que no solo se centra en la identidad local, sino que también menciona la posibilidad de utilizar proveedores de inicio de sesión externos como Google, Microsoft o Facebook. Esto me brinda una visión más amplia y me permite considerar opciones adicionales para autenticar a los usuarios en mi plataforma.

    ¡Gracias nuevamente al autor por su valiosa contribución!

  2. Hola.
    Muy chulo el tutorial.
    Me da un error al crear las carpetas de Identity. Me crear los ficheros Identity/Pages pero en todos esta.
    private readonly SignInManager _signInManager;
    private readonly UserManager _userManager;

    hay que cambiarlo por nuestra clase ApplicationUser?
    Gracias

    1. Buenas, Rodrigo, si modificas él la clase del User tienes que cambiarlo en todas sus referencias de identity sí. Entiendo que el error (sin tener más datos) que te da en SignInManager y UserManager es por las referencias a la clase del User (si estás utilizando una personalizada)

      Puedes hacerlo rápidamente con Visual Studio.

      Saludos!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *