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..
}
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?
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.
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.
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.
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.
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
.
Does anyone see what is going on? I am at a loss at this moment.
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
The issue here is now how is the scope off and I have the values wanted in the wrong place
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.