How to extend available properties of User.Identity
Asked Answered
M

6

139

I'm using MVC5 Identity 2.0 for users to log into my website, where the authentication details are stored in an SQL database. Asp.net Identity has been implemented in a standard way as can be found in many online tutorials.

The ApplicationUser class in IdentityModels has been extended to include some custom properties, such as an integer OrganizationId. The idea is that many users can be created and assigned to a common Organization for database relationship purposes.

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

How can I retrieve the OrganizationId property of the currently logged in user from within a controller? Is this available via a method once a user is logged in or do I always have the retrieve the OrganizationId from the database, based on the UserId, every time a controller method executes?

Reading around on the web I have seen I need to use the following to get the logged in UserId etc.

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

However, OrganizationId is not a property available in User.Identity. Do I need to extend User.Identity to include the OrganizationId property? If so, how do I go about this.

The reason I need the OrganizationId so often is that many table queries are reliant on the OrganizationId to retrieve data relevant to the Organization that's associated to the logged in user.

Mcardle answered 5/2, 2015 at 2:48 Comment(3)
Does my answer here help you at all?Hooves
Pretty much the same answer from me here: https://mcmap.net/q/168181/-mvc-identity-2-using-formsauthenticationticket - if you need this information regularly in the life of the request, you can place it on the cookie as a claim.Antoneantonella
Thanks @Shoe both of your answer worked. In addition to your answers, I had to add a claim to be stored in the cookie. In the IdentityModels class I had to add userIdentity.AddClaim(new Claim("MyApp:OrganizationId", OrganizationId.ToString())); to the public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager) method.Mcardle
H
233

Whenever you want to extend the properties of User.Identity with any additional properties like the question above, add these properties to the ApplicationUser class first like so:

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

Then what you need is to create an extension method like so (I create mine in an new Extensions folder):

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

When you create the Identity in the ApplicationUser class, just add the Claim -> OrganizationId like so:

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

Once you added the claim and have your extension method in place, to make it available as a property on your User.Identity, add a using statement on the page/file you want to access it:

in my case: using App.Extensions; within a Controller and @using. App.Extensions withing a .cshtml View file.

EDIT:

What you can also do to avoid adding a using statement in every View is to go to the Views folder, and locate the Web.config file in there. Now look for the <namespaces> tag and add your extension namespace there like so:

<add namespace="App.Extensions" />

Save your file and you're done. Now every View will know of your extensions.

You can access the Extension Method:

var orgId = User.Identity.GetOrganizationId();
Happening answered 10/7, 2015 at 11:49 Comment(19)
Hi pawel, i have tried the same, but getting error that Application user doesn't contain a definition of OrganisationIdTrochlear
@RachitGupta When does it happen? When you trying to add the claim or when you trying to access its value later in the code? If it's when you're adding a claim then make sure your ApplicationUser has the property defined... If it's later in the code then don't forget to add the using statement to where you created the extension method like: using App.Extensions;Happening
Got error in userIdentity.AddClaim line. I have created the IdentityExtensions class in IdentityModels.cs file only. Can that be a source of problem?Trochlear
Nope, if it's when you add the claim it happens way before identityExtensions come into play (that's when you read the value back)... Make sure your ApplicationUser has the property you're trying to add as claim: in this example it was public long OrganizationId { get; set; }Happening
Thanks. Now its done, but it is not showing as property of user. When i create a new user, it shows, but when i try to set it afterwards, it doesn't show up in intellisense. What i want is to set that property automatically for the first time user signs up, but later allow him to modify it.Trochlear
This property is added to the IPrincipal User automatically as a Claim. You do not access it from the User object you create (or retrieve from db) but from the User that is available on the Controller class.Happening
Hi Pawel, thank you for your answer. I have the problem, that User.Identity.GetOrganizationId() returns null. The method is properly called, but it returns null. Do you have an idea what could be the problem?Roselynroseman
the value of the property is taken from DB. Have you checked if it is assigned here? use rIdentity.AddClaim(new Claim("OrganizationId", this.OrganizationId)); what is the user.OrganizationId value? If the Claim is null it means it was never assigned.Happening
How should I this with Asp.Net Identity 3?Tighten
Thanks, it worked. I think this is the best practise for custom variables in Asp Net Idendity 2. I dont know why Asp.Net community doesn't provide such an example in default articles on their websites.Louella
I can't seem to do the step that adds the Claim using this method. What overload of the Claim constructor is it supposed to be using? The only ones I see that take two arguments use (BinaryRead, ClaimsIdentity) and (string, string).Ervinervine
@ChristoferOhlsson I guess you'd have to .ToString() the property when creating a Claim. I have updated the answer to reflect that.Happening
@Happening thanks. I thought of a ToString, but the answer not having one confused me. But wait, was the whole purpose of this to just have a ToString() get method for these properties? What about allowing the client to update the values?Ervinervine
@ChristoferOhlsson the code above creates Claims with the values taken from DB, although you could run some logic that will create some Claims in runtime based on web.config values etc. If you want Claims to be "editable" you can change, add, remove Claims in runtime, but remember that you will have to reissue the cookie every time you do that. Make sure you put into Claims only what you need since all the Claims are stored in the cookie/token increasing its size.Happening
I think you just have to re-login in your appZetana
@pawel Hi how can I add claims, if I have DB first scenario? I originally started and expanded the AspUsersTable in Identity 2.2, now I am stuck with DB first scenario, can you please also share some guidance why not to add the property directly to the aspUsersTableWesting
@transformer Adding claims should be the same in DB first. It would be much easier to see your code to actually have a chance to spot anythying that might be wrong. In my opinion there is nothing inherently wrong with adding properties to the AspnetUsers table if these are properties of the User. You are also not limited to adding Claims that are properties of the AspNetUsers table. I have additional Claims table but also create some Claims on the fly based on some .config values. I'm not going to argue that it is the right way but sometimes needs doing. Could you post a question with code?Happening
I believe you are abusing Claims for storing custom properties. Claims designed to provide a more granular authentication than just roles. In case you want to add custom properties, you can simply extend ApplicationUser object. Very useful article: johnatten.com/2014/06/22/…Encarnalize
Had to restart the application for the code change to take effect.Flagler
J
17

I was looking for the same solution and Pawel gave me 99% of the answer. The only thing that was missing that I needed for the Extension to display was adding the following Razor Code into the cshtml(view) page:

@using programname.Models.Extensions

I was looking for the FirstName, to display in the top right of my NavBar after the user logged in.

I thought I would post this incase it helps someone else, So here is my code:

I created a new folder called Extensions(Under my Models Folder) and created the new class as Pawel specified above: 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;
    }
}

Then in my _LoginPartial.cshtml(Under Views/Shared Folders) I added @using.ProgramName.Models.Extensions

I then added the change to the folling line of code that was going to use the Users First name after Logging in :

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

Perhaps this helps someone else down the line.

Johnston answered 12/5, 2016 at 10:22 Comment(0)
H
11

Check out this great blog post by John Atten: ASP.NET Identity 2.0: Customizing Users and Roles

It has great step-by-step info on the whole process. Go read it : )

Here are some of the basics.

Extend the default ApplicationUser class by adding new properties (i.e.- Address, City, State, 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);
        }
    }

Then you add your new properties to your RegisterViewModel.

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

Then update the Register View to include the new properties.

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

Then update the Register() method on AccountController with the new properties.

    // Add the Address properties:
    user.Address = model.Address;
    user.City = model.City;
    user.State = model.State;
    user.PostalCode = model.PostalCode;
Hexad answered 5/2, 2015 at 4:21 Comment(2)
This is good example but it doesn't answer the question which is, how do you get those new properties from User.Identity.Ordinate
Downvoted because the answer does not show how the custom properties can be retrieved from User.Identity.Platitude
D
7

For anyone that finds this question looking for how to access custom properties in ASP.NET Core 2.1 - it's much easier: You'll have a UserManager, e.g. in _LoginPartial.cshtml, and then you can simply do (assuming "ScreenName" is a property that you have added to your own AppUser which inherits from 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>
}
Dailey answered 23/7, 2018 at 14:54 Comment(2)
It should be noted that GetUserAsync(User) will query the database to retrieve the OrganizationId. In contrast, the accepted solution will include the OrganizationId in the claims (e.g. cookie). The advantage of pulling this information from the database is that people can be moved between organizations without requiring them to logout/login. Of course, the disadvantage is that it requires an additional database query.Osteophyte
I didn't know about this! Every day we learn something new. Thank you Jashan!Wirehaired
D
3

I also had added on or extended additional columns into my AspNetUsers table. When I wanted to simply view this data I found many examples like the code above with "Extensions" etc... This really amazed me that you had to write all those lines of code just to get a couple values from the current users.

It turns out that you can query the AspNetUsers table like any other table:

 ApplicationDbContext db = new ApplicationDbContext();
 var user = db.Users.Where(x => x.UserName == User.Identity.Name).FirstOrDefault();
Diffractive answered 25/6, 2018 at 6:38 Comment(0)
R
1

Dhaust gives a good way to add the property to the ApplicationUser class. Looking at the OP code it appears they may have done this or were on track to do that. The question asks

How can I retrieve the OrganizationId property of the currently logged in user from within a controller? However, OrganizationId is not a property available in User.Identity. Do I need to extend User.Identity to include the OrganizationId property?

Pawel gives a way to add an extension method that requires using statements or adding the namespace to the web.config file.

However, the question asks if you "need to" extend User.Identity to include the new property. There is an alternative way to access the property without extending User.Identity. If you followed Dhaust method you can then use the following code in your controller to access the new property.

ApplicationDbContext db = new ApplicationDbContext();
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(db));
var currentUser = manager.FindById(User.Identity.GetUserId());
var myNewProperty = currentUser.OrganizationId;
Racial answered 14/12, 2017 at 23:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.