48

(Using VS2013 RTW, ASP.NET MVC5)

I've seen lots of documentation on how to add properties to the ApplicationUser class (and table) when using ASP.NET identity. But I haven't seen any documentation on how to have a separate table with content that maps to the ApplicationUser table via a foreign key.

I've successfully derived my own Microsoft.AspNet.Identity.EntityFramework.IdentityDbContext, and now my own "UserPreferences" table coexists in harmony with the various "AspNet*" tables in my SQL Server database. But I'm not clear on the best way to get the current user's ID so as to write a new row to the "UserPreferences" table. Note that the project is ASP.NET MVC, but I've added (and am working inside of) a Web API controller.

I have a working solution, which is:

            var uman = new Microsoft.AspNet.Identity.UserManager<Microsoft.AspNet.Identity.EntityFramework.IdentityUser>(new Microsoft.AspNet.Identity.EntityFramework.UserStore<Microsoft.AspNet.Identity.EntityFramework.IdentityUser>(new App1.Data.App1DbContext()));

            var uident = User.Identity;

            var userobject = uman.FindByNameAsync(uident.Name);

            var userid = userobject.Result.Id;
            // (Perform new row creation including the userid we just looked up

Consider that the AspNetUsers table (as defined by the Identity framework) consists of these fields:

-id (PK, nvarchar(128) - seems to contain a GUID, not sure why not an autoincrement integer, but I assume there are reasons for this)
-Username (nvarchar(max))
-PasswordHash (nvarchar(max))
-SecurityStamp (nvarchar(max))

I think that the id field (not the username) is the correct field to reference in this table. (I was thinking that a user may change his/her username, and someone else could then assume the other user's username, which is why both fields are there likely.) So then, I need to get the current user's ID for storage in the "UserPreferences" table, which is what the above code does. But it seems inefficient to have to do this lookup.

An important point is that in the context of a System.Web.Mvc.Controller, I can do:

User.Identity.GetUserId()
(Runtime type of User.Identity: System.Security.Principal.GenericIdentity)

But in the context of a System.Web.Http.ApiController (Web API), I cannot because that method does not exist (runtime type of User.Identity: System.Security.Claims.ClaimsIdentity) which is why I must rely instead on:

User.Identity.Name

and do the extra lookup to convert Name to ID. Does anyone have any suggestions for how this can be improved? Am I approaching the task of writing user data to separate tables in the correct way?

GEOCHET
  • 21,119
  • 15
  • 74
  • 98
BenjiFB
  • 4,545
  • 10
  • 46
  • 53
  • 1
    Note: According to this post: http://stackoverflow.com/questions/9768872/can-i-access-iidentity-from-web-api "ApiController has a Request property of type System.Net.Http.HttpRequestMessage; this holds details about the current request naturally (it also has a setter for unit testing purposes). HttpRequestMessage has a Properties dictionary; you will find the value of the key MS_UserPrincipal holds your IPrincipal object." But "MS_UserPrincipal" did not exist. I could find the principal elsewhere in the dictionary, but it returned the same object as above which didn't include the ID... – BenjiFB Oct 21 '13 at 22:06

5 Answers5

59

You should be able to get user id on both MVC controller and web api controller by same extension method in identity 1.0 RTW package.

Here is the extensions from identity package:

namespace Microsoft.AspNet.Identity
{
    public static class IdentityExtensions
    {
        public static string FindFirstValue(this ClaimsIdentity identity, string claimType);
        public static string GetUserId(this IIdentity identity);
        public static string GetUserName(this IIdentity identity);
    }
}

The IIdentity is the base interface for all identity types. You may need to add "using Microsoft.AspNet.Identity" in order to see the extension method.

BTW: regarding adding a foreign table for user, why not using ApplicationUser and add navigation property to UserPreference to let EF to handle their relationship? That will be easier.

Hongye Sun
  • 3,868
  • 1
  • 25
  • 18
  • 1
    Thanks Hongye, that extension method did just what I need! >why not using ApplicationUser and add navigation property to UserPreference to let EF to handle their relationship? That is to be able to navigate from the Users to the UserPreferences, right? That's a good idea. I'll do that. Also, is it bad practice that I'm storing GUIDs (which are long) as foreign key values? Example: 307b5c04-254a-4c56-b2bb-a87185883ae7. I'm usd to storing only an integer value. This will take more space in the DB. – BenjiFB Oct 22 '13 at 00:32
  • 2
    Note that you might need to cast in order to use FindFirstValue: `((ClaimsIdentity)User.Identity).FindFirstValue("FirstName")` for example. – Christopher Oct 08 '14 at 17:24
47
ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, UserID));

ClaimTypes.NameIdentifier is the claim for the function User.Identity.GetUserId()

shA.t
  • 16,580
  • 5
  • 54
  • 111
Oswaldo Alvarez
  • 4,772
  • 1
  • 22
  • 20
16

I'm using claim base approach:

private ApplicationUser GetCurrentUser(ApplicationDbContext context)
{
  var identity = User.Identity as ClaimsIdentity;
  Claim identityClaim = identity.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);

  return context.Users.FirstOrDefault(u => u.Id == identityClaim.Value);
}
Mastenka
  • 315
  • 3
  • 19
3

Short description for the best answer:

  1. Install-Package Microsoft.AspNet.Identity.Core -Version 2.2.1
  2. var userId = User.Identity.GetUserId();
kkost
  • 3,640
  • 5
  • 41
  • 72
0

Tried @Mastenka answer and it gave me nothing. I checked ClaimsIdentity and there were claims type "UserName", so as a result, I get username by using "UserName" as ClaimsType. Hope someone will give more info about it. It looks strange that "ClaimTypes.NameIdentifier" had no effect. (ApiController, ASP MVC API)

var userName = ((ClaimsIdentity)RequestContext.Principal.Identity).Claims.FirstOrDefault(cl => cl.Type == "UserName")?.Value;
IHAFURR
  • 111
  • 4