Search code examples
c#asp.net-coreidentityserver4asp.net-core-identity

identitysever and AspNetUsers


I have a set up where I have

services.AddIdentity<AppUser, AppRole>() 
    .AddEntityFrameworkStores<ApplicationDbContext>() 
    .AddUserManager<userManage>()
    .AddDefaultTokenProviders();

where AppUser and AppRole is used, but it seems to fail because of that. I keep getting

ArgumentNullException: Value cannot be null. Parameter name: type for the claims at System.Security.Claims.Claim..ctor

after

Microsoft.AspNetCore.Identity.IdentityUserClaim1.ToClaim()

Whole log at the bottom

Everything was working before I introduced the extension for the IdentityUser and IdentityRole

for the IDS4 set up I have:

        services.AddIdentityServer(options => {
                options.UserInteraction.LoginUrl = "/Account/Login";
            })
           .AddSigningCredential(new X509Certificate2(Path.Combine(".", "certs"
                                                                 , "IdentityServer4Auth.pfx")))
           .AddAspNetIdentity<AppUser>()
           .AddConfigurationStore(options => {
                options.ConfigureDbContext = builder =>
                    builder.UseSqlServer(connection_string, sql => sql.MigrationsAssembly(migrations_assembly));
            })
           .AddOperationalStore(options => {
                //options.DefaultSchema = "token";
                options.ConfigureDbContext = builder =>
                    builder.UseSqlServer(connection_string, sql => sql.MigrationsAssembly(migrations_assembly));
            })
           .AddInMemoryApiResources(Config.GetApiResources())
           .AddInMemoryIdentityResources(Config.GetIdentityResources())
           .AddJwtBearerClientAuthentication()
           .AddProfileService<IdentityProfileService>();

that was working fine, but the switch from

         services.AddIdentity<ApplicationUser, IdentityRole>(options =>
        {  })
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();
        services.AddDistributedMemoryCache();

to

        services.AddIdentity<AppUser, AppRole>()
           .AddEntityFrameworkStores<ApplicationDbContext>()
           .AddUserManager<userManage>()
           .AddDefaultTokenProviders();

and that kills it now. I know it works as far as the AppUser, AppRole, and userManage are set up because they the same set up used in many of my apps, but as soon as it is mix with the IDS4 it is now failing. When it was working i had extended the IdentityUser

public class ApplicationUser : IdentityUser {}

The was working too, it was when I mixed the 2 apps together, so both had the AppUser, AppRole, and userManage, is when it went bad. I will put the models below

Side note too, I have followed the logs, and what is killing me here is the query run in the DB is correct. When I run it I see no nulls for any type values. I have even scrubbed the DB for any nulls in any of the claim areas, like the roles or user level just to be safe. I have put a break point on the area that causes the fault,

    var signin_result = await _signInManager.PasswordSignInAsync(_user, test, model.RememberMe, false);

which when I look at the _user I see that it has the security stamp and all values correctly filled

it is on the user claims

SELECT [uc].[Id], [uc].[ClaimType], [uc].[ClaimValue], [uc].[UserId]
FROM [AspNetUserClaims] AS [uc]
WHERE [uc].[UserId] = @__user_Id_0

but I don't see where the issue is when that returns nothing with a null

the log

2018-10-08 13:17:47.164 -07:00 [Information] Entity Framework Core "2.1.4-rtm-31024" initialized '"ApplicationDbContext"' using provider '"Microsoft.EntityFrameworkCore.SqlServer"' with options: "SensitiveDataLoggingEnabled "
2018-10-08 13:17:47.189 -07:00 [Information] Executed DbCommand ("1"ms) [Parameters=["@__user_Id_0='11325643' (Size = 450)"], CommandType='Text', CommandTimeout='30']"
""SELECT [uc].[Id], [uc].[ClaimType], [uc].[ClaimValue], [uc].[UserId]
FROM [AspNetUserClaims] AS [uc]
WHERE [uc].[UserId] = @__user_Id_0"
2018-10-08 13:17:47.370 -07:00 [Error] An exception occurred in the database while iterating the results of a query for context type '"test.app.Data.ApplicationDbContext"'."
""System.ArgumentNullException: Value cannot be null.
Parameter name: type
at System.Security.Claims.Claim..ctor(String type, String value, String valueType, String issuer, String originalIssuer, ClaimsIdentity subject, String propertyKey, String propertyValue)
at System.Security.Claims.Claim..ctor(String type, String value)
at Microsoft.AspNetCore.Identity.IdentityUserClaim`1.ToClaim()
at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ProjectionShaper.TypedProjectionShaper`3.Shape(QueryContext queryContext, ValueBuffer& valueBuffer)
at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ProjectionShaper.TypedProjectionShaper`3.Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.IShaper<TOut>.Shape(QueryContext queryContext, ValueBuffer& valueBuffer)
at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.BufferlessMoveNext(DbContext _, Boolean buffer, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.MoveNext(CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext(CancellationToken cancellationToken)"
System.ArgumentNullException: Value cannot be null.
Parameter name: type
at System.Security.Claims.Claim..ctor(String type, String value, String valueType, String issuer, String originalIssuer, ClaimsIdentity subject, String propertyKey, String propertyValue)
at System.Security.Claims.Claim..ctor(String type, String value)
at Microsoft.AspNetCore.Identity.IdentityUserClaim`1.ToClaim()
at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ProjectionShaper.TypedProjectionShaper`3.Shape(QueryContext queryContext, ValueBuffer& valueBuffer)
at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ProjectionShaper.TypedProjectionShaper`3.Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.IShaper<TOut>.Shape(QueryContext queryContext, ValueBuffer& valueBuffer)
at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.BufferlessMoveNext(DbContext _, Boolean buffer, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.MoveNext(CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext(CancellationToken cancellationToken)
2018-10-08 13:17:48.680 -07:00 [Information] Executed action "WSU.Sso.Controllers.AccountController.Login (test.app)" in 2022.4589ms

The Models, and set up (note in the none IDS4 app these are working just fine)

public class AppUser : IdentityUser {}
public class AppRole : IdentityRole {}
public class AppUserClaim : IdentityUserClaim<string> {}
public class AppUserRole : IdentityUserRole<string> {}
public class AppRoleClaims : IdentityRoleClaim<string> {}
public partial class CoreDbContext : IdentityDbContext
<
    AppUser, // TUser
    AppRole, // TRole
    string, // TKey
    AppUserClaim, // TUserClaim
    AppUserRole, // TUserRole,
    IdentityUserLogin<string>, // TUserLogin
    AppRoleClaims, // TRoleClaim
    IdentityUserToken<string> // TUserToken
>//, ICoreDbContext
{
    //etc.. 
}

Update

I believe the issue is here, https://github.com/IdentityServer/IdentityServer4.AspNetIdentity/blob/dev/src/UserClaimsFactory.cs where UserManager<TUser> userManager needs to be userManage because I extended that. I am guessing I need to role my own it seems?

Update 2

Turns out, after removing the userManage extending UserManager<TUser> that it still fails with

System.Security.Claims.Claim..ctor(string type, string value, string valueType, string issuer, string originalIssuer, ClaimsIdentity subject, string propertyKey, string propertyValue) System.Security.Claims.Claim..ctor(string type, string value) Microsoft.AspNetCore.Identity.IdentityUserClaim.ToClaim()

There was hope that simplifying it would be the anwser so i didn't have to rewrite the .AddAspNetIdentity<AppUser>() but that didn't work. It is clear that it is not something that comes from the DB that is null, but must be something added that it's value is null. I can't tell what, just that it must be between the Microsoft.AspNetCore.Identity.IdentityUserClaim<TKey>.ToClaim() and the IdentityServer4.AspNetIdentity.UserClaimsFactory<TUser>.CreateAsync(TUser user) in UserClaimsFactory.cs ... What I can say for sure is that in https://github.com/IdentityServer/IdentityServer4.AspNetIdentity/blob/dev/src/UserClaimsFactory.cs all of the parts that is is trying to set is verifaied in the DB as not null.

It is also worth nothing that the IDS4 was working before I added the the custom role. I am not 100% sure that is not still in play here.

Side note

So I am left wondering, as i rule things out, if it is the name of the ID that is the issue here. In the update the tables for AspNetUser has the table set as user_id not Id (Microsoft Owin Security - Claims Constructor Value cannot be null is what is leading me to think about this). With that said, I believe there is nothing wrong with the set up, here is what I have in my OnModelCreating(ModelBuilder builder),

builder.Entity<AppUser>().Property(p => p.Id).HasColumnName("user_id");

We have not had any issue up to this point on using it, but I'm running down all differences in the user that would be an issue root.

Update 3

after stepping through public class IdentityUserClaim<TKey> where TKey : IEquatable<TKey> and putting a break point on Claim ToClaim() the type is the null. Which is a duh on the log, but I can't seem to back track in the call stack where that roots from. My question sits at, if the DB query returns a proper set of types and values, then why is there a null type and value set to process first? The very first time that breakpoint is hit, the type is null.

UPDATE 4 major stop

after hitting every break point I am now stuck and wounder if i am hitting a bug here. I get in to the UserManager and follow the stack and can see that the main claims are there and ok. enter image description here

And then the next part is to run the claims store which executes the query above, and at that point is when it fails. I can't see why. I run the query in SQL Manager and it is ok, not one NULL. enter image description here

Does anyone see what is going on? I am at a loss at this moment.

UPDATE 5 - narrowing down

So failing to notice the locals when i hit the claim being set as null, I see that the values I want are in the scope of this

enter image description here

The issue here is now how is the scope off and I have the values wanted in the wrong place


Solution

  • The issue took a bit to walk around, but it is clear now why there was a failing. AppUserClaim is the real issue. It was hard to tell, and over looking the locals at that step, is why I had to go the long way around here.

    The extended IdentityUserClaim<string> I had was

    public class AppUserClaim : IdentityUserClaim<string>
    {
        public int Id { get; set; }
    
        /// <summary>
        /// Gets or sets the primary key of the user associated with this claim.
        /// </summary>
        public virtual string UserId { get; set; }
    
        public virtual AppUser user { get; set; }
    
        /// <summary>
        /// Gets or sets the claim type for this claim.
        /// </summary>
        public virtual string ClaimType { get; set; }
    
        /// <summary>
        /// Gets or sets the claim value for this claim.
        /// </summary>
        public virtual string ClaimValue { get; set; }
    
    }
    

    But the properties of ClaimType and ClaimValue where not needing to be overwrote. Once I had done that, it set the values where they where out of scope for the methods. Changing the model to be

    public class AppUserClaim : IdentityUserClaim<string>  {
        public virtual AppUser user { get; set; } // note this is the reason why i had to extend
    
    }
    

    Solves the issue. It did't appear in the other apps because claims where set from the SSO, so that method was always skipped.

    Lessons for me to remember, always make sure you look at the locals when stepping through. It took me stepping away for a day before I saw the duh moment.