MVC5 (VS2012) Identity CreateIdentityAsync - Value cannot be null

梦想与她 提交于 2019-11-27 08:34:23

I had the same error in the past but only when I created user with Entity Framework Migration Tool. When creating a user and signing withing the website, I had not error.

My error was that I was not providing a SecurityStamp with migration.

SecurityStamp = Guid.NewGuid().ToString()

This property set, everything worked.

I had a similar problem. The Solution was to set the SecurityStamp-Property of the User-Entity.

Background: The customer want´s to have Admin-/Superuser-Accounts with passwords in the database and a bunch of additional users - who shall be able to log in without password - in an XML file...

So I inherit from the Entity Framework UserStore, override FindByIdAsync and FindByNameAsync, search the XML file for the user and return a new User-Entity. (if no user was found by the default implementation)

I had the same Exception as Jon when creating a ClaimsIdentity.

After some digging I found that my newly created User-Entities did not have a SecurityStamp. And the asp.net default UserManager expects a SecurityStamp and wants to set it as a Claim in the ClaimsIdentity.

After setting a value to that property - I used a string that contains a prefix and the username - everything works fine for me.

I did the same thing as @user3347549.

It took me a while to figure out where the error was actually coming from, kudos to dotPeek for that!

I'm using my own implementation of UserManager and UserStore, because I wanted Guid types (uniqueidentifier in MSSQL) as keys, and not string (albeit they are just placeholders for Guids)

Thanks to this link and specifically this answer, which I've included for reference in case the link goes away, by HaoK (@Hao Kung here on SO):

You should seed the security stamp with something random, like a new guid work.

I implemented my own ClaimsIdentityFactory (which looks exactly the same from what I gather in dotPeek) and just altered one line in the CreateAsync method

public class ClaimsIdentityFactory<TUser, TKey> : IClaimsIdentityFactory<TUser, TKey>
    where TUser : class, IUser<TKey>
    where TKey : IEquatable<TKey>
{
    /// <summary>
    /// Claim type used for role claims
    /// </summary>
    public string RoleClaimType { get; set; }

    /// <summary>
    /// Claim type used for the user name
    /// </summary>
    public string UserNameClaimType { get; set; }

    /// <summary>
    /// Claim type used for the user id
    /// </summary>
    public string UserIdClaimType { get; set; }

    /// <summary>
    /// Claim type used for the user security stamp
    /// </summary>
    public string SecurityStampClaimType { get; set; }

    /// <summary>
    /// Constructor
    /// </summary>
    public ClaimsIdentityFactory()
    {
        RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
        UserIdClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
        UserNameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
        SecurityStampClaimType = "AspNet.Identity.SecurityStamp";
    }

    /// <summary>
    /// Create a ClaimsIdentity from a user
    /// </summary>
    /// <param name="manager">
    /// </param>
    /// <param name="user">
    /// </param>
    /// <param name="authenticationType">
    /// </param>
    /// <returns>
    /// </returns>
    public virtual async Task<ClaimsIdentity> CreateAsync(UserManager<TUser, TKey> manager, TUser user, string authenticationType)
    {
        if (manager == null)
            throw new ArgumentNullException("manager");
        if (user == null)
            throw new ArgumentNullException("user");

        var id = new ClaimsIdentity(authenticationType, UserNameClaimType, RoleClaimType);
        id.AddClaim(new Claim(UserIdClaimType, ConvertIdToString(user.Id), "http://www.w3.org/2001/XMLSchema#string"));
        id.AddClaim(new Claim(UserNameClaimType, user.UserName, "http://www.w3.org/2001/XMLSchema#string"));
        id.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"));
        if (manager.SupportsUserSecurityStamp)
        {
            ClaimsIdentity claimsIdentity1 = id;
            string securityStampClaimType = SecurityStampClaimType;
            ClaimsIdentity claimsIdentity2 = claimsIdentity1;
            string str = await manager.GetSecurityStampAsync(user.Id).ConfigureAwait(false);
            Claim claim = new Claim(securityStampClaimType, str ?? Guid.NewGuid().ToString());
            claimsIdentity2.AddClaim(claim);
        }
        if (manager.SupportsUserRole)
        {
            IList<string> roles = await manager.GetRolesAsync(user.Id).ConfigureAwait(false);
            foreach (string str in roles)
                id.AddClaim(new Claim(RoleClaimType, str, "http://www.w3.org/2001/XMLSchema#string"));
        }
        if (manager.SupportsUserClaim)
            id.AddClaims(await manager.GetClaimsAsync(user.Id).ConfigureAwait(false));
        return id;
    }

    /// <summary>
    /// Convert the key to a string, by default just calls .ToString()
    /// </summary>
    /// <param name="key">
    /// </param>
    /// <returns>
    /// </returns>
    protected virtual string ConvertIdToString(TKey key)
    {
        if ((object)key == null)
            throw new ArgumentNullException("key");
        else
            return key.ToString();
    }
}

The line I altered was from

Claim claim = new Claim(securityStampClaimType, str);

to

Claim claim = new Claim(securityStampClaimType, str ?? Guid.NewGuid().ToString());

I have yet to figure out what this means, but at least it works for now and I can continue testing my application. I'm assuming this error appears because I haven't fully implemented some part of the Identity stack. To use this new factory just type this in the UserManager constructor:

ClaimsIdentityFactory = new ClaimsIdentityFactory<TUser, Guid>();

The default UserManager will attempt to get the claims and add/remove claims even if you have not implemented them. If you don't need claims, the solution I've found is to implement your own UserManager or implement "do nothing" methods in your UserStore.

public Task AddClaimAsync(TUser user, Claim claim)
{
    return Task.FromResult<int>(0);
}

public Task<IList<Claim>> GetClaimsAsync(TUser user)
{
    return Task.FromResult<IList<Claim>>(new List<Claim>());
}

public Task RemoveClaimAsync(TUser user, Claim claim)
{
    return Task.FromResult<int>(0);
}

I had to implement ClaimsIdentityFactory and set the UserManager.ClaimsIdentityFactory property i.e. in AccountController class.

In my case it was something totally different. It was a matter of Owin startup code ordering

My buggy code:

public void ConfigureAuth(IAppBuilder app)
{

   //...

   app.CreatePerOwinContext(ApplicationDbContext.Create);
   app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
   app.CreatePerOwinContext<AppSignInManager>(AppSignInManager.Create);
   app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);

   //...

}

It turns out, AppSignInManager was trying to init AppUserManager which is always null because it hasn't been added to Owin yet.

By simply swapping them together, everything worked like a charm

public void ConfigureAuth(IAppBuilder app)
{

   //...

   app.CreatePerOwinContext(ApplicationDbContext.Create);
   app.CreatePerOwinContext<AppSignInManager>(AppSignInManager.Create);
   app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
   app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);

   //...

}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!