I have a ASP MVC 5 site using the new identity framework with 3rd party authentication using entity framework as it comes out of the box. I have a logged in user and they have 3 claims (the standard name nameidentifier and authentication provider that seem to be provided by the default ClaimsIdentityFactory).

When a user performs some operation I then add a claim for them using this:

userManager.AddClaim(userId, new Claim(claimType,claimValue));

If I look in the db in the AspNetUserClaims table then i see a row for my new claim. Great.

However when my user then visits some other page, I check for this new claim, but I never find it.

My initial problem I thought was that the logged in user never has their cookie updated to contain the new claim, so I logged them out thinking that when they logged back in the framework would read the claims and create the new claims for the user, and then they would be in the cookie. But I must be missing something as even when my user logs in again they don't have the claim.

In fact if I log DB activity when this line is called in the ExternalLoginCallback:

// Sign in the user with this external login provider if the user already has a login
ApplicationUser user = await UserManager.FindAsync(loginInfo.Login);

what I get is this:

iisexpress.exe' (CLR v4.0.30319: /LM/W3SVC/2/ROOT-2-130416316906840516): Loaded 

'EntityFrameworkDynamicProxies-Microsoft.AspNet.Identity.EntityFramework'. 
SELECT TOP (1) 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name]
    FROM [dbo].[AspNetRoles] AS [Extent1]
    WHERE (((UPPER([Extent1].[Name])) = (UPPER(@p__linq__0))) AND ( NOT ((UPPER([Extent1].[Name]) IS NULL) OR (UPPER(@p__linq__0) IS NULL)))) OR ((UPPER([Extent1].[Name]) IS NULL) AND (UPPER(@p__linq__0) IS NULL))


-- p__linq__0: 'TeamManagement' (Type = String, Size = -1)

-- Executing at 10/04/2014 20:28:29 +01:00

-- Completed in 2 ms with result: GlimpseDbDataReader



'iisexpress.exe' (CLR v4.0.30319: /LM/W3SVC/2/ROOT-2-130416316906840516): Loaded 'EntityFrameworkDynamicProxies-Haccapp.Model'. 
SELECT 
    [Extent1].[Discriminator] AS [Discriminator], 
    [Extent1].[Id] AS [Id], 
    [Extent1].[UserName] AS [UserName], 
    [Extent1].[PasswordHash] AS [PasswordHash], 
    [Extent1].[SecurityStamp] AS [SecurityStamp], 
    [Extent1].[PreferredEmailAddress] AS [PreferredEmailAddress]
    FROM [dbo].[AspNetUsers] AS [Extent1]
    WHERE ([Extent1].[Discriminator] IN (N'ApplicationUser',N'IdentityUser')) AND ([Extent1].[Id] = @p0)


-- p0: 'SiteOwner' (Type = String, Size = -1)

-- Executing asynchronously at 10/04/2014 20:28:29 +01:00

GlimpseDbCommand.TimerStrategy is null
-- Completed in 6 ms with result: GlimpseDbDataReader



SELECT 
    [Limit1].[Discriminator] AS [Discriminator], 
    [Limit1].[Id] AS [Id], 
    [Limit1].[UserName] AS [UserName], 
    [Limit1].[PasswordHash] AS [PasswordHash], 
    [Limit1].[SecurityStamp] AS [SecurityStamp], 
    [Limit1].[PreferredEmailAddress] AS [PreferredEmailAddress]
    FROM ( SELECT TOP (1) 
        [Extent2].[Id] AS [Id], 
        [Extent2].[UserName] AS [UserName], 
        [Extent2].[PasswordHash] AS [PasswordHash], 
        [Extent2].[SecurityStamp] AS [SecurityStamp], 
        [Extent2].[PreferredEmailAddress] AS [PreferredEmailAddress], 
        [Extent2].[Discriminator] AS [Discriminator]
        FROM  [dbo].[AspNetUserLogins] AS [Extent1]
        LEFT OUTER JOIN [dbo].[AspNetUsers] AS [Extent2] ON ([Extent2].[Discriminator] IN (N'ApplicationUser',N'IdentityUser')) AND ([Extent1].[UserId] = [Extent2].[Id])
        WHERE ([Extent1].[LoginProvider] = @p__linq__0) AND (@p__linq__0 IS NOT NULL) AND ([Extent1].[ProviderKey] = @p__linq__1) AND (@p__linq__1 IS NOT NULL)
    )  AS [Limit1]


-- p__linq__0: 'Google' (Type = String, Size = -1)

-- p__linq__1: 'https://www.google.com/accounts/o8/id?id=some_account_id' (Type = String, Size = -1)

-- Executing asynchronously at 10/04/2014 20:28:29 +01:00

which contains nothing which attempts to query the database for claims at all, and indeed my user that is returned has 0 claims.

The code then goes on to do this:

ClaimsIdentity identity = await UserManager.CreateIdentityAsync(user, defaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties {IsPersistent = isPersistent}, identity);

and the identity then has the 3 claims.

What do I have to do to enable the claims to be loaded from the database when I load my user? And once I've done that what do I need to do to ensure that any new claims my user accrues during using the site are updated in the users cookie?

I feel like I must be missing something fundamental here.

EDIT

So I have played a bit more with this and have ended up implementing my own ClaimsIdentityFactory like so:

public class FullyLoadingClaimsIdentityFactory : ClaimsIdentityFactory<ApplicationUser>
{
    private readonly ApplicationDb db;

    public FullyLoadingClaimsIdentityFactory(ApplicationDb db)
    {
        this.db = db;
    }

    public override async Task<ClaimsIdentity> CreateAsync(UserManager<ApplicationUser> manager, ApplicationUser user, string authenticationType)
    {
        var currentClaims = db.UserClaims.Where(x => x.User.Id == user.Id).ToList();
        var claimsIdentity = await base.CreateAsync(manager, user, authenticationType);
        claimsIdentity.AddClaims(currentClaims.Select(c=>new Claim(c.ClaimType,c.ClaimValue)));
        return claimsIdentity;
    }
}

This explicitly now queries the database to load the user claims. I had to add a property to my class that derives from IdentityDbContext so that I could expose the frameworks IdentityUserClaim objects:

 public IDbSet<IdentityUserClaim> UserClaims { get; set; }

and now when I look at my currentClaims they do correctly contain the claims that are inthe table. Hurrah!

Except when I then call:

var claimsIdentity = await base.CreateAsync(manager, user, authenticationType);

it also loads my claims, that it wouldn't load before.

in fact if I change to this:

var claimsIdentity = await base.CreateAsync(manager, user, authenticationType);
Trace.WriteLine(claimsIdentity.Claims.Count()); <-- traces 3
var currentClaims = db.UserClaims.Where(x => x.User.Id == user.Id).ToList();
claimsIdentity = await base.CreateAsync(manager, user, authenticationType);
Trace.WriteLine(claimsIdentity.Claims.Count());  <-- traces 6!

then I don't get the claims in the DB (of which there are 3) loaded when I first create the claimsIdentity but the second time I create the identity after I have manually queried the DB the claims are there.

Why might that be? I'm feeling now like its EF related but am not sure why...

有帮助吗?

解决方案

Yes, this was a bug which should be fixed if you upgrade to v2.0. Basically you want to ensure that LazyLoading is enabled, more info here

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top