Cómo ampliar las propiedades disponibles de User.Identity

130

Estoy usando MVC5 Identity 2.0 para que los usuarios inicien sesión en mi sitio web, donde los detalles de autenticación se almacenan en una base de datos SQL. Asp.net Identity se ha implementado de manera estándar, como se puede encontrar en muchos tutoriales en línea.

La clase ApplicationUser en IdentityModels se ha ampliado para incluir algunas propiedades personalizadas, como un OrganizationId entero. La idea es que se pueden crear y asignar muchos usuarios a una organización común para fines de relación de bases de datos.

public class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> 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
            return userIdentity;
        }

        //Extended Properties
        public DateTime? BirthDate { get; set; }
        public long? OrganizationId { get; set; }

        //Key Mappings
        [ForeignKey("OrganizationId")]
        public virtual Organization Organization { get; set; }
    }

¿Cómo puedo recuperar la propiedad OrganizationId del usuario conectado actualmente desde un controlador? ¿Está disponible a través de un método una vez que un usuario ha iniciado sesión o siempre tengo que recuperar el OrganizationId de la base de datos, basado en el UserId, cada vez que se ejecuta un método de controlador?

Al leer en la web que he visto, necesito usar lo siguiente para iniciar sesión en el ID de usuario, etc.

using Microsoft.AspNet.Identity;
...
User.Identity.GetUserId();

Sin embargo, OrganizationId no es una propiedad disponible en User.Identity. ¿Necesito extender User.Identity para incluir la propiedad OrganizationId? Si es así, ¿cómo hago para esto?

La razón por la que necesito el OrganizationId con tanta frecuencia es que muchas consultas de tabla dependen del OrganizationId para recuperar datos relevantes para la Organización que está asociada al usuario conectado.

RobHurd
fuente
3
¿Mi respuesta aquí te ayuda en algo?
Zapato
1
Más o menos la misma respuesta de mí aquí: stackoverflow.com/a/28138594/809357 : si necesita esta información regularmente durante la vigencia de la solicitud, puede colocarla en la cookie como un reclamo.
trailmax
1
Gracias @Shoe, ambas respuestas funcionaron. Además de sus respuestas, tuve que agregar un reclamo para ser almacenado en la cookie. En la clase IdentityModels tuve que agregar userIdentity.AddClaim (new Claim ("MyApp: OrganizationId", OrganizationId.ToString ())); al método público Async Task <ClaimsIdentity> GenerateUserIdentityAsync (UserManager <ApplicationUser> manager) .
RobHurd

Respuestas:

219

Siempre que desee ampliar las propiedades de User.Identity con propiedades adicionales como la pregunta anterior, agregue estas propiedades a la clase ApplicationUser primero de la siguiente manera:

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> 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
        return userIdentity;
    }

    // Your Extended Properties
    public long? OrganizationId { get; set; }
}

Entonces, lo que necesita es crear un método de extensión como este (creo el mío en una nueva carpeta Extensiones):

namespace App.Extensions
{
    public static class IdentityExtensions
    {
        public static string GetOrganizationId(this IIdentity identity)
        {
            var claim = ((ClaimsIdentity)identity).FindFirst("OrganizationId");
            // Test for null to avoid issues during local testing
            return (claim != null) ? claim.Value : string.Empty;
        }
    }
}

Cuando cree la Identidad en la clase ApplicationUser, simplemente agregue el Reclamo -> OrganizationId así:

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> 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 => this.OrganizationId is a value stored in database against the user
        userIdentity.AddClaim(new Claim("OrganizationId", this.OrganizationId.ToString()));

        return userIdentity;
    }

Una vez que haya agregado el reclamo y tenga su método de extensión, para que esté disponible como una propiedad en su Usuario. Identidad, agregue una declaración de uso en la página / archivo al que desea acceder :

en mi caso: using App.Extensions;dentro de un controlador y @using. App.Extensionsdentro de un archivo de vista .cshtml.

EDITAR:

Lo que también puede hacer para evitar agregar una declaración de uso en cada Vista es ir a la carpeta Vistas y ubicar el archivo Web.config allí. Ahora busque la <namespaces>etiqueta y agregue su espacio de nombre de extensión de esta manera:

<add namespace="App.Extensions" />

Guarde su archivo y ya está. Ahora cada Vista sabrá de sus extensiones.

Puede acceder al Método de extensión:

var orgId = User.Identity.GetOrganizationId();
Pawel
fuente
Hola Pawel, he intentado lo mismo, pero recibo un error de que el usuario de la aplicación no contiene una definición de OrganisationId
es una trampa el
@RachitGupta ¿Cuándo sucede? ¿Cuándo intenta agregar el reclamo o cuando intenta acceder a su valor más adelante en el código? Si es cuando está agregando un reclamo, asegúrese de que su ApplicationUser tenga la propiedad definida ... Si está más adelante en el código, no olvide agregar la declaración de uso al lugar donde creó el método de extensión como: uso de extensiones de aplicación. ;
Pawel
Se produjo un error en la línea userIdentity.AddClaim. He creado la clase IdentityExtensions solo en el archivo IdentityModels.cs. ¿Puede ser eso una fuente de problemas?
Es una trampa
No, si es cuando agregas el reclamo, sucede mucho antes de que las IdentityExtensions entren en juego (es cuando lees el valor) ... Asegúrate de que tu ApplicationUser tenga la propiedad que estás tratando de agregar como reclamo: en este ejemplo fue ID de organización largo público {get; conjunto; }
Pawel
66
Gracias funcionó. Creo que esta es la mejor práctica para las variables personalizadas en Asp Net Idendity 2. No sé por qué la comunidad Asp.Net no proporciona ese ejemplo en los artículos predeterminados en sus sitios web.
oneNiceFriend
17

Estaba buscando la misma solución y Pawel me dio el 99% de la respuesta. Lo único que faltaba que necesitaba para que se mostrara la Extensión era agregar el siguiente Código Razor en la página cshtml (ver):

@using programname.Models.Extensions

Estaba buscando el Nombre, para mostrar en la esquina superior derecha de mi NavBar después de que el usuario inició sesión.

Pensé en publicar esto en caso de que ayude a alguien más, así que aquí está mi código:

Creé una nueva carpeta llamada Extensiones (bajo mi carpeta de modelos) y creé la nueva clase como Pawel especificó anteriormente: IdentityExtensions.cs

using System.Security.Claims;
using System.Security.Principal;

namespace ProgramName.Models.Extensions
{
    public static class IdentityExtensions
    {
        public static string GetUserFirstname(this IIdentity identity)
        {
            var claim = ((ClaimsIdentity)identity).FindFirst("FirstName");
            // Test for null to avoid issues during local testing
            return (claim != null) ? claim.Value : string.Empty;
        }
    }
}

IdentityModels.cs :

public class ApplicationUser : IdentityUser
{

    //Extended Properties
    public string FirstName { get; internal set; }
    public string Surname { get; internal set; }
    public bool isAuthorized { get; set; }
    public bool isActive { get; set; }

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> 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("FirstName", this.FirstName));

        return userIdentity;
    }
}

Luego en mi _LoginPartial.cshtml(Bajo Views/Sharedcarpetas) agregué@using.ProgramName.Models.Extensions

Luego agregué el cambio a la siguiente línea de código que iba a usar el Nombre de usuario después de iniciar sesión:

@Html.ActionLink("Hello " + User.Identity.GetUserFirstname() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })

Quizás esto ayude a alguien más en el futuro.

AxleWack
fuente
11

Echa un vistazo a esta gran publicación de blog de John Atten: ASP.NET Identity 2.0: Personalización de usuarios y roles

Tiene gran información paso a paso sobre todo el proceso. Ve a leerlo :)

Estos son algunos de los conceptos básicos.

Extienda la clase predeterminada ApplicationUser agregando nuevas propiedades (es decir, Dirección, Ciudad, Estado, etc.):

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> 
    GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        var userIdentity = await manager.CreateIdentityAsync(this,  DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }

    // Use a sensible display name for views:
    [Display(Name = "Postal Code")]
    public string PostalCode { get; set; }

    // Concatenate the address info for display in tables and such:
    public string DisplayAddress
    {
        get
        {
            string dspAddress = string.IsNullOrWhiteSpace(this.Address) ? "" : this.Address;
            string dspCity = string.IsNullOrWhiteSpace(this.City) ? "" : this.City;
            string dspState = string.IsNullOrWhiteSpace(this.State) ? "" : this.State;
            string dspPostalCode = string.IsNullOrWhiteSpace(this.PostalCode) ? "" : this.PostalCode;

            return string.Format("{0} {1} {2} {3}", dspAddress, dspCity, dspState, dspPostalCode);
        }
    }

Luego agrega sus nuevas propiedades a su RegisterViewModel.

    // Add the new address properties:
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }

Luego actualice la Vista de registro para incluir las nuevas propiedades.

    <div class="form-group">
        @Html.LabelFor(m => m.Address, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Address, new { @class = "form-control" })
        </div>
    </div>

Luego actualice el método Register () en AccountController con las nuevas propiedades.

    // Add the Address properties:
    user.Address = model.Address;
    user.City = model.City;
    user.State = model.State;
    user.PostalCode = model.PostalCode;
Dhaust
fuente
16
Este es un buen ejemplo, pero no responde a la pregunta, ¿cómo se obtienen esas nuevas propiedades de User.Identity?
Dejan Bogatinovski
55
Votación negativa porque la respuesta no muestra cómo se pueden recuperar las propiedades personalizadas de User.Identity.
maulik13
3

Para cualquiera que encuentre esta pregunta en busca de cómo acceder a propiedades personalizadas en ASP.NET Core 2.1, es mucho más fácil: tendrá un UserManager, por ejemplo, en _LoginPartial.cshtml, y luego simplemente puede hacerlo (suponiendo que "ScreenName" es un propiedad que ha agregado a su propio AppUser que hereda de IdentityUser):

@using Microsoft.AspNetCore.Identity

@using <namespaceWhereYouHaveYourAppUser>

@inject SignInManager<AppUser> SignInManager
@inject UserManager<AppUser> UserManager

@if (SignInManager.IsSignedIn(User)) {
    <form asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })" 
          method="post" id="logoutForm" 
          class="form-inline my-2 my-lg-0">

        <ul class="nav navbar-nav ml-auto">
            <li class="nav-item">
                <a class="nav-link" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">
                    Hello @((await UserManager.GetUserAsync(User)).ScreenName)!
                    <!-- Original code, shows Email-Address: @UserManager.GetUserName(User)! -->
                </a>
            </li>
            <li class="nav-item">
                <button type="submit" class="btn btn-link nav-item navbar-link nav-link">Logout</button>
            </li>
        </ul>

    </form>
} else {
    <ul class="navbar-nav ml-auto">
        <li class="nav-item"><a class="nav-link" asp-area="Identity" asp-page="/Account/Register">Register</a></li>
        <li class="nav-item"><a class="nav-link" asp-area="Identity" asp-page="/Account/Login">Login</a></li>
    </ul>
}
Jashan
fuente
2
Cabe señalar que GetUserAsync(User)consultará la base de datos para recuperar el OrganizationId. Por el contrario, la solución aceptada incluirá el OrganizationId en los reclamos (por ejemplo, cookie). La ventaja de extraer esta información de la base de datos es que las personas pueden moverse entre las organizaciones sin necesidad de cerrar sesión / iniciar sesión. Por supuesto, la desventaja es que requiere una consulta de base de datos adicional.
Matt
1

Dhaust ofrece una buena manera de agregar la propiedad a la clase ApplicationUser. Mirando el código OP, parece que pueden haber hecho esto o estaban en camino de hacerlo. La pregunta pregunta

¿Cómo puedo recuperar la propiedad OrganizationId del usuario conectado actualmente desde un controlador? Sin embargo, OrganizationId no es una propiedad disponible en User.Identity. ¿Necesito extender User.Identity para incluir la propiedad OrganizationId?

Pawel ofrece una forma de agregar un método de extensión que requiere el uso de declaraciones o agregar el espacio de nombres al archivo web.config.

Sin embargo, la pregunta le pregunta si "necesita" extender User.Identity para incluir la nueva propiedad. Hay una forma alternativa de acceder a la propiedad sin extender User.Identity. Si siguió el método Dhaust, puede usar el siguiente código en su controlador para acceder a la nueva propiedad.

ApplicationDbContext db = new ApplicationDbContext();
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(db));
var currentUser = manager.FindById(User.Identity.GetUserId());
var myNewProperty = currentUser.OrganizationId;
codeMethod
fuente
0

También agregué o extendí columnas adicionales en mi tabla AspNetUsers. Cuando solo quería ver estos datos, encontré muchos ejemplos como el código anterior con "Extensiones", etc. Esto realmente me sorprendió de que tuviera que escribir todas esas líneas de código solo para obtener un par de valores de los usuarios actuales.

Resulta que puede consultar la tabla AspNetUsers como cualquier otra tabla:

 ApplicationDbContext db = new ApplicationDbContext();
 var user = db.Users.Where(x => x.UserName == User.Identity.Name).FirstOrDefault();
Anthony Griggs
fuente