none
Dónde almacenar datos del usuario RRS feed

  • Pregunta

  • Buenas,

    Tengo una aplicación AspNet MVC 5 + Identity. La aplicación y el usuario realizan lo siguiente en el proceso de autenticación

    - La aplicación solicita credenciales (usuario y password)

    - Si las credenciales son incorrectas, muestra un mensaje y termina el proceso.

    - Si las credenciales son correctas, crea la cookie de autenticación

    - La aplicación verifica cuántos roles tiene asociados el usuario

    - Si el usuario no tiene ningún rol asociado, se le muestra un mensaje y termina el proceso.

    - Si  el usuario tiene un sólo rol asociado se le muestra la pantalla principal del sistema y el proceso termina.

    - Si el usuario tiene más de un rol asociado, se le muestra un combo para que seleccione con qué rol accederá al sistema

    - El usuario selecciona el rol, se muestra la pantalla principal del sistema y el proceso termina.

    En la pantalla para que seleccione el rol debo mostrar el nombre completo del usuario y de momento, sólo tengo acceso al Username a través de la propiedad Name del objeto IPrincipal.IIdentity. Necesito tener a la mano datos como Nombres, Apellidos y el Rol con el cuál el usuario accede al sistema. ¿dónde puedo almacenar estos datos? ¿sería recomendable utilizar un objeto Session? He visto en muchas aplicaciones que utilizan variables de sesión para estos propósitos pero no sé si es una buena práctica.

    Favor su apoyo, muchas gracias.

    Saludos.


    • Editado eduar2083 martes, 30 de julio de 2019 0:03
    lunes, 29 de julio de 2019 23:58

Respuestas

  • Hola, 

    entonces deberías usar tu implementación de UserManager. 

    Te propongo un ejemplo usado en un proyecto Asp.Net Core en el que he participado

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Identity;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace Myapp.Infraestructure.ID4.UserManager
    {
        /// <summary>
        /// 
        /// </summary>
        public class ApplicationUserManager : UserManager<UserApplication>
        {
            /// <summary>
            /// 
            /// </summary>
            /// <param name="store"></param>
            /// <param name="optionsAccessor"></param>
            /// <param name="passwordHasher"></param>
            /// <param name="userValidators"></param>
            /// <param name="passwordValidators"></param>
            /// <param name="keyNormalizer"></param>
            /// <param name="errors"></param>
            /// <param name="services"></param>
            /// <param name="logger"></param>
            public ApplicationUserManager(IUserStore<UserApplication> store, IOptions<IdentityOptions> optionsAccessor,
                                            IPasswordHasher<UserApplication> passwordHasher, IEnumerable<IUserValidator<UserApplication>> userValidators,
                                            IEnumerable<IPasswordValidator<UserApplication>> passwordValidators, ILookupNormalizer keyNormalizer,
                                            IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<UserApplication>> logger) : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
    
            {
            }
      
            /// <summary>
            /// Find user by id
            /// </summary>
            /// <param name="userId"></param>
            /// <returns></returns>
            public override Task<UserApplication> FindByIdAsync(string userId)
            {
                return Users.Include(c => c.UserProfileInfo).FirstOrDefaultAsync(u => u.Id == userId);
            }
    
            /// <summary>
            /// Find user by user name
            /// </summary>
            /// <param name="userName"></param>
            /// <returns></returns>
            public override Task<UserApplication> FindByNameAsync(string userName)
            {
                return Users.Include(c => c.UserProfileInfo).FirstOrDefaultAsync(u => u.UserName == userName);
            }
    
            /// <summary>
            /// Create new user
            /// </summary>
            /// <param name="user"></param>
            /// <param name="password"></param>
            /// <returns></returns>
            public override Task<IdentityResult> CreateAsync(UserApplication user, string password)
            {
                return base.CreateAsync(user, password);
            }
        }
    }

    Luego en el método ConfigureServices de la clase Startup

     services.AddIdentity<UserApplication, RoleApplication>(options =>
                {
                    options.Lockout.MaxFailedAccessAttempts = 3;
                    options.Password.RequiredLength = 6;
                    options.Password.RequireNonAlphanumeric = false;
                    options.Password.RequireDigit = false;
                    options.Password.RequireUppercase = false;
                    options.Password.RequireLowercase = false;
                }).AddEntityFrameworkStores<UsersContext>().AddDefaultTokenProviders().AddUserManager<ApplicationUserManager>();
    Como puedes observar, el truco consiste en sobrescribir los métodos del UserManager, empleando la clase de Usuario personalizada y usando los Include necesarios para devolver los datos relacionados. Es un ejemplo y espero te de pistas

     


    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos


    miércoles, 31 de julio de 2019 11:56
    Moderador

Todas las respuestas

  • hola

    >>Necesito tener a la mano datos como Nombres, Apellidos y el Rol con el cuál el usuario accede al sistema.

    Esto se resuelve usando Claims

    How to extend available properties of User.Identity

    como veras al crear la credencial asigna el valor extra como claims usandoAddClaim()

    despues podrias recuperarlo

    >>¿sería recomendable utilizar un objeto Session?

    no, nunca

    saludos


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina

    martes, 30 de julio de 2019 3:21
  • Hola Leandro,

    Estuve revisando el link y veo que se debe agregar el Claim en el método GenerateUserIdentityAsync de la clase IdentityUser. Yo tengo dicha clase customizada de la siguiente manera:

    public class User : IdentityUser<int, UserLogin, UserRole, UserClaim>
    {
    	public User()
    	{
    		Passwords = new List<PasswordHistory>();
    	}
    
    	public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User, int> manager)
    	{
    		// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
    		var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
    		
    
    		// Add custom user claims here
    		userIdentity.AddClaim(new Claim("UserProfileInfo.Names", this.UserProfileInfo.Names));
    
    		return userIdentity;
    	}
    
    	public virtual UserProfileInfo UserProfileInfo { get; set; }
    	public virtual ICollection<PasswordHistory> Passwords { get; set; }
    	
    	public string UsuarioCreacion { get; set; }
    	public DateTime FechaCreacion { get; set; }
    }

    Los nombre y apellidos del usuario se encuentran en otra clase relacionada (propiedad de navegación UserProfileInfo). Sin embargo cuando se ejecuta la línea que agrega el claim:

    userIdentity.AddClaim(new Claim("UserProfileInfo.Names", this.UserProfileInfo.Names));

    Me tira un NullReferenceException. Entiendo que es porque no se tiene disponible los datos para la entidad relacionada (UserProfileInfo). ¿Cómo puedo hacer para acceder a los datos de dicha entidad desde el método GenerateUserIdentityAsync?


    • Editado eduar2083 miércoles, 31 de julio de 2019 3:34
    miércoles, 31 de julio de 2019 3:11
  • Hola, ese método no es el que crea un usuario? Deberías usar el método del manager que se llama GetUserAsync para recuperar el usuario y a partir de ahí genera la cookie. También puedes mostrarnos el código que llama al método GenerateUserIdentityAsync? Deberías establecer las propiedades necesarias, en este caso UserProfileInfo

    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos

    miércoles, 31 de julio de 2019 6:22
    Moderador
  • Hola Sergio,

    El método GenerateUserIdentityAsync no se llama al crear un usuario sino al hacer login del usuario. En mi caso dicho método es llamado cuando se ejecuta la siguiente línea del AccounController

    var signInResult = await _signInManager.PasswordSignInAsync(vm.UserName, vm.Password, vm.RememberMe, shouldLockout: true);

    Y más específicamente es llamado cuando el SignInManager llama al método SignInAsync:

    Pero lo que pretendo no es crear la cookie ya que se está creando correctamente. Lo que pretendo es agregar algunos Claims para guardar datos del usuario que necesito tener a la mano durante toda su sesión en el sistema para no tener que consultar a cada momento la BD e imagino que estos deben crearse al momento que se inicia la sesión y según algunos post del link que me proporciona Leandro, el lugar apropiado sería el método GenerateUserIdentityAsync de la clase IdentityUser (o de su derivada User en mi caso). Sin embargo, a través de este método sólo puedo acceder a los datos de User y no de User.ProfileInfo ya que como dije, la clase UserProfileInfo representa a otra tabla relacionada con User y es aquí donde están los datos que quiero asignarle al Claim.

    Esta es mi clase UserProfileInfo para que vean que se relaciona con User pero no puedo acceder sus datos desde User a través de la propiedad de navegación:

    namespace Domain.Identity.Entities
    {
        public class UserProfileInfo
        {
            public int Id { get; set; }
    
            public string Names { get; set; }
    
            public string FirstName { get; set; }
    
            public string LastName { get; set; }
    
            public bool Sex { get; set; }
    
            public virtual User User { get; set; }
        }
    }

    Me pregunto si tiene algo que ver con la configuración de mi DbContext, específicamente con las propiedades que las tengo así:

    AutoDetectChangesEnabled => true

    LazyLoadingEnabled => true

    ProxyCreationEnabled => false




    • Editado eduar2083 miércoles, 31 de julio de 2019 11:50
    miércoles, 31 de julio de 2019 11:36
  • Hola, 

    entonces deberías usar tu implementación de UserManager. 

    Te propongo un ejemplo usado en un proyecto Asp.Net Core en el que he participado

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Identity;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace Myapp.Infraestructure.ID4.UserManager
    {
        /// <summary>
        /// 
        /// </summary>
        public class ApplicationUserManager : UserManager<UserApplication>
        {
            /// <summary>
            /// 
            /// </summary>
            /// <param name="store"></param>
            /// <param name="optionsAccessor"></param>
            /// <param name="passwordHasher"></param>
            /// <param name="userValidators"></param>
            /// <param name="passwordValidators"></param>
            /// <param name="keyNormalizer"></param>
            /// <param name="errors"></param>
            /// <param name="services"></param>
            /// <param name="logger"></param>
            public ApplicationUserManager(IUserStore<UserApplication> store, IOptions<IdentityOptions> optionsAccessor,
                                            IPasswordHasher<UserApplication> passwordHasher, IEnumerable<IUserValidator<UserApplication>> userValidators,
                                            IEnumerable<IPasswordValidator<UserApplication>> passwordValidators, ILookupNormalizer keyNormalizer,
                                            IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<UserApplication>> logger) : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
    
            {
            }
      
            /// <summary>
            /// Find user by id
            /// </summary>
            /// <param name="userId"></param>
            /// <returns></returns>
            public override Task<UserApplication> FindByIdAsync(string userId)
            {
                return Users.Include(c => c.UserProfileInfo).FirstOrDefaultAsync(u => u.Id == userId);
            }
    
            /// <summary>
            /// Find user by user name
            /// </summary>
            /// <param name="userName"></param>
            /// <returns></returns>
            public override Task<UserApplication> FindByNameAsync(string userName)
            {
                return Users.Include(c => c.UserProfileInfo).FirstOrDefaultAsync(u => u.UserName == userName);
            }
    
            /// <summary>
            /// Create new user
            /// </summary>
            /// <param name="user"></param>
            /// <param name="password"></param>
            /// <returns></returns>
            public override Task<IdentityResult> CreateAsync(UserApplication user, string password)
            {
                return base.CreateAsync(user, password);
            }
        }
    }

    Luego en el método ConfigureServices de la clase Startup

     services.AddIdentity<UserApplication, RoleApplication>(options =>
                {
                    options.Lockout.MaxFailedAccessAttempts = 3;
                    options.Password.RequiredLength = 6;
                    options.Password.RequireNonAlphanumeric = false;
                    options.Password.RequireDigit = false;
                    options.Password.RequireUppercase = false;
                    options.Password.RequireLowercase = false;
                }).AddEntityFrameworkStores<UsersContext>().AddDefaultTokenProviders().AddUserManager<ApplicationUserManager>();
    Como puedes observar, el truco consiste en sobrescribir los métodos del UserManager, empleando la clase de Usuario personalizada y usando los Include necesarios para devolver los datos relacionados. Es un ejemplo y espero te de pistas

     


    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos


    miércoles, 31 de julio de 2019 11:56
    Moderador
  • Excelente!!!

    Sobreescribí el método tal como lo indicaste y ya tengo disponible datos de la entidad relacionada y con eso ya pude crear el Claim.

    Muchas gracias!!!

    miércoles, 31 de julio de 2019 13:15