Comment étendre les propriétés disponibles de User.Identity

130

J'utilise MVC5 Identity 2.0 pour que les utilisateurs se connectent à mon site Web, où les détails d'authentification sont stockés dans une base de données SQL. Asp.net Identity a été implémenté de manière standard, comme on peut le trouver dans de nombreux tutoriels en ligne.

La classe ApplicationUser dans IdentityModels a été étendue pour inclure certaines propriétés personnalisées, telles qu'un entier OrganizationId. L'idée est que de nombreux utilisateurs peuvent être créés et affectés à une organisation commune à des fins de relation de base de données.

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; }
    }

Comment puis-je récupérer la propriété OrganizationId de l'utilisateur actuellement connecté à partir d'un contrôleur? Est-ce disponible via une méthode une fois qu'un utilisateur est connecté ou dois-je toujours récupérer le OrganizationId de la base de données, basé sur le UserId, chaque fois qu'une méthode de contrôleur s'exécute?

En lisant sur le Web, j'ai vu que je devais utiliser ce qui suit pour obtenir l'ID utilisateur connecté, etc.

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

Cependant, OrganizationId n'est pas une propriété disponible dans User.Identity. Dois-je étendre User.Identity pour inclure la propriété OrganizationId? Si oui, comment dois-je procéder.

La raison pour laquelle j'ai si souvent besoin de OrganizationId est que de nombreuses requêtes de table dépendent de OrganizationId pour récupérer des données pertinentes pour l'organisation associée à l'utilisateur connecté.

RobHurd
la source
3
Ma réponse ici vous aide-t-elle du tout?
Chaussure du
1
À peu près la même réponse de ma part ici: stackoverflow.com/a/28138594/809357 - si vous avez besoin de ces informations régulièrement dans la vie de la demande, vous pouvez les placer sur le cookie en tant que réclamation.
trailmax
1
Merci @Shoe, votre réponse a fonctionné. En plus de vos réponses, j'ai dû ajouter une réclamation pour être stockée dans le cookie. Dans la classe IdentityModels, j'ai dû ajouter userIdentity.AddClaim (new Claim ("MyApp: OrganizationId", OrganizationId.ToString ())); à la méthode publique async Task <ClaimsIdentity> GenerateUserIdentityAsync (UserManager <ApplicationUser> manager) .
RobHurd

Réponses:

219

Chaque fois que vous souhaitez étendre les propriétés de User.Identity avec des propriétés supplémentaires comme la question ci-dessus, ajoutez d'abord ces propriétés à la classe ApplicationUser comme suit:

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; }
}

Ensuite, ce dont vous avez besoin est de créer une méthode d'extension comme celle-ci (je crée la mienne dans un nouveau dossier Extensions):

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;
        }
    }
}

Lorsque vous créez l'identité dans la classe ApplicationUser, ajoutez simplement la revendication -> OrganizationId comme ceci:

    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;
    }

Une fois que vous avez ajouté la revendication et que votre méthode d'extension est en place, pour la rendre disponible en tant que propriété sur votre User.Identity, ajoutez une instruction using sur la page / le fichier auquel vous souhaitez y accéder :

dans mon cas: using App.Extensions;dans un Controller et avec @using. App.Extensionsun fichier View .cshtml.

ÉDITER:

Pour éviter d'ajouter une instruction using dans chaque vue, vous pouvez également accéder au dossier Views et y localiser le fichier Web.config. Recherchez maintenant la <namespaces>balise et ajoutez-y votre espace de noms d'extension comme suit:

<add namespace="App.Extensions" />

Enregistrez votre fichier et vous avez terminé. Désormais, chaque vue connaîtra vos extensions.

Vous pouvez accéder à la méthode d'extension:

var orgId = User.Identity.GetOrganizationId();
Pawel
la source
Salut pawel, j'ai essayé la même chose, mais obtenir une erreur indiquant que l'utilisateur de l'application ne contient pas de définition de OrganisationId
C'est un piège
@RachitGupta Quand cela se produit-il? Lorsque vous essayez d'ajouter la revendication ou lorsque vous essayez d'accéder à sa valeur plus tard dans le code? Si c'est lorsque vous ajoutez une revendication, assurez-vous que votre ApplicationUser a défini la propriété ... Si c'est plus tard dans le code, n'oubliez pas d'ajouter l'instruction using à l'endroit où vous avez créé la méthode d'extension comme: using App.Extensions ;
Pawel
Erreur dans la ligne userIdentity.AddClaim. J'ai créé la classe IdentityExtensions dans le fichier IdentityModels.cs uniquement. Cela peut-il être une source de problème?
C'est un piège
Non, si c'est lorsque vous ajoutez la revendication, cela se produit bien avant que identityExtensions n'entre en jeu (c'est alors que vous relisez la valeur) ... Assurez-vous que votre ApplicationUser a la propriété que vous essayez d'ajouter comme revendication: dans cet exemple, c'était public long OrganizationId {get; ensemble; }
Pawel
6
Merci, cela a fonctionné. Je pense que c'est la meilleure pratique pour les variables personnalisées dans Asp Net Idendity 2. Je ne sais pas pourquoi la communauté Asp.Net ne fournit pas un tel exemple dans les articles par défaut sur leurs sites Web.
oneNiceFriend
17

Je cherchais la même solution et Pawel m'a donné 99% de la réponse. La seule chose qui manquait dont j'avais besoin pour que l'extension s'affiche était l'ajout du code Razor suivant dans la page cshtml (vue):

@using programname.Models.Extensions

Je cherchais le FirstName, à afficher en haut à droite de ma NavBar après la connexion de l'utilisateur.

Je pensais que je publierais ceci au cas où cela aiderait quelqu'un d'autre, alors voici mon code:

J'ai créé un nouveau dossier appelé Extensions (sous mon dossier Modèles) et créé la nouvelle classe comme Pawel spécifié ci-dessus: 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;
    }
}

Puis dans mes _LoginPartial.cshtml(sous Views/Shareddossiers) j'ai ajouté@using.ProgramName.Models.Extensions

J'ai ensuite ajouté le changement à la ligne de code suivante qui allait utiliser le prénom de l'utilisateur après la connexion:

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

Peut-être que cela aide quelqu'un d'autre sur toute la ligne.

AxleWack
la source
11

Consultez cet excellent article de blog de John Atten: ASP.NET Identity 2.0: Personnalisation des utilisateurs et des rôles

Il contient des informations détaillées sur l'ensemble du processus. Allez le lire :)

Voici quelques notions de base.

Étendez la classe ApplicationUser par défaut en ajoutant de nouvelles propriétés (par exemple, adresse, ville, état, 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);
        }
    }

Ensuite, vous ajoutez vos nouvelles propriétés à votre RegisterViewModel.

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

Mettez ensuite à jour la vue de registre pour inclure les nouvelles propriétés.

    <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>

Mettez ensuite à jour la méthode Register () sur AccountController avec les nouvelles propriétés.

    // Add the Address properties:
    user.Address = model.Address;
    user.City = model.City;
    user.State = model.State;
    user.PostalCode = model.PostalCode;
Décharge
la source
16
C'est un bon exemple mais il ne répond pas à la question qui est de savoir comment obtenir ces nouvelles propriétés de User.Identity.
Dejan Bogatinovski
5
Évalué car la réponse n'indique pas comment les propriétés personnalisées peuvent être extraites de User.Identity.
maulik13
3

Pour tous ceux qui trouvent cette question à la recherche de la manière d'accéder aux propriétés personnalisées dans ASP.NET Core 2.1 - c'est beaucoup plus simple: vous aurez un UserManager, par exemple dans _LoginPartial.cshtml, et vous pourrez simplement le faire (en supposant que "ScreenName" est un propriété que vous avez ajoutée à votre propre AppUser qui hérite 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
la source
2
Il convient de noter que GetUserAsync(User)va interroger la base de données pour récupérer le OrganizationId. En revanche, la solution acceptée inclura le OrganizationId dans les revendications (par exemple, cookie). L’avantage d’extraire ces informations de la base de données est que les personnes peuvent être déplacées d’une organisation à l’autre sans qu’elles se déconnectent / se connectent. Bien sûr, l'inconvénient est qu'il nécessite une requête de base de données supplémentaire.
Matt
1

Dhaust offre un bon moyen d'ajouter la propriété à la classe ApplicationUser. En regardant le code OP, il semble qu'ils aient peut-être fait cela ou étaient sur la bonne voie pour le faire. La question se pose

Comment puis-je récupérer la propriété OrganizationId de l'utilisateur actuellement connecté à partir d'un contrôleur? Cependant, OrganizationId n'est pas une propriété disponible dans User.Identity. Dois-je étendre User.Identity pour inclure la propriété OrganizationId?

Pawel propose un moyen d'ajouter une méthode d'extension qui nécessite l'utilisation d'instructions ou l'ajout de l'espace de noms au fichier web.config.

Cependant, la question vous demande si vous «devez» étendre User.Identity pour inclure la nouvelle propriété. Il existe un autre moyen d'accéder à la propriété sans étendre User.Identity. Si vous avez suivi la méthode Dhaust, vous pouvez ensuite utiliser le code suivant dans votre contrôleur pour accéder à la nouvelle propriété.

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
la source
1

J'avais également ajouté ou étendu des colonnes supplémentaires dans ma table AspNetUsers. Quand je voulais simplement voir ces données, j'ai trouvé de nombreux exemples comme le code ci-dessus avec "Extensions" etc ... Cela m'a vraiment étonné que vous deviez écrire toutes ces lignes de code juste pour obtenir quelques valeurs des utilisateurs actuels.

Il s'avère que vous pouvez interroger la table AspNetUsers comme n'importe quelle autre table:

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