MVC5 (VS2012) Identity CreateIdentityAsync - Value cannot be null

27,284

Solution 1

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.

Solution 2

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.

Solution 3

I faced this issue when the password is less of them 6 characters. Anything bigger than that and I don't get this error.

6 characters are the minimal default setting to it.

For sure you can change this behaviour.

services.Configure<IdentityOptions>(options =>
                {
                    options.Password.RequiredLength = 6;
                    options.Password.RequireUppercase = false;
                    options.Password.RequireLowercase = false;
                    options.Password.RequireDigit = false;
                    options.Password.RequireNonAlphanumeric = false;
                });

Solution 4

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>();

Solution 5

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);
}
Share:
27,284
Jon
Author by

Jon

C# Developer in the London. Loves C# and being challenged with new and interesting projects. My github account is: https://github.com/twistedtwig

Updated on July 09, 2022

Comments

  • Jon
    Jon almost 2 years

    I am trying to setup OAuth for a an MVC5 site (in VS2012).

    I am using Fluent NHibernate. I have setup my own Userstore and pass in a repository object to access NHibernate session object. I pass my store into the default aspnet usermanager provider. This eventually worked for local registration and logging in. I am not trying to setup connecting / registering with Facebook.

    It gets a successful account. Adds a user in the user table, adds a record in the logins table and then blows up. I have not implements claims in the user store, or put a claims collection in the user object. (not sure if this is actually required, I was stripping everything back that might be going wrong to find the source of the issue).

    The line that blows up is, (in the account controller):

    var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);            
    

    within this method:

    private async Task SignInAsync(IdentityUser user, bool isPersistent)
    

    this is the end of the stack trace

    [ArgumentNullException: Value cannot be null.
    Parameter name: value]
       System.Security.Claims.Claim..ctor(String type, String value, String valueType, String issuer, String originalIssuer, ClaimsIdentity subject, String propertyKey, String propertyValue) +14108789
       System.Security.Claims.Claim..ctor(String type, String value, String valueType) +62
       Microsoft.AspNet.Identity.<CreateAsync>d__0.MoveNext() +481
       System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +144
       System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +84
       System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() +49
       Web.Controllers.<SignInAsync>d__42.MoveNext() in d:\Google Drive\Development\GoalManagement\Web\Controllers\AccountController.cs:375
       System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +144
       System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +84
       Web.Controllers.<ExternalLoginConfirmation>d__35.MoveNext() in d:\Google Drive\Development\GoalManagement\Web\Controllers\AccountController.cs:311
       System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +144
       System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +84
    
    public class IdentityUser : IUser
    {
        public IdentityUser()
        {
            Logins = new List<IdentityUserLogin>();
        }
    
        public string Id { get; set; }
        public string UserName { get; set; }
        public string PasswordHash { get; set; }
        public string SecurityStamp { get; set; }
        public IList<IdentityUserLogin> Logins { get; set; }
    
    }
    
    public class IdentityUserLogin
    {
        public string LoginProvider { get; set; }
        public string ProviderKey { get; set; }
    }
    

    I can include my userstore code if wanted: I didn't put it in as it's a large file and might detract from the issue.

    I am not sure why it is even trying to create the claim object and why it is blowing up. As I only have VS2012 I have been patching it all together from examples online mainly.


    As suggested by @Shoe I inherited from UserManager:

    public class NHibernateAspnetUserManager<TUser> : UserManager<TUser> where TUser : IdentityUser
    {
        public NHibernateAspnetUserManager(IUserStore<TUser> store) : base(store)
        {
        }        
    
        public override Task<ClaimsIdentity> CreateIdentityAsync(TUser user, string authenticationType)
        {
            ClaimsIdentity identity = new ClaimsIdentity();
            return Task.FromResult(identity);
        }
    }
    

    It now no longer throws an error but doesn't actually authenticate me how ever many times I use the Facebook register / login.


    To summarize. With @Shoe's info I tried both overriding UserManager.CreateIdentityAsync with:

    public override Task<ClaimsIdentity> CreateIdentityAsync(TUser user, string authenticationType)
        {
            var identity = new ClaimsIdentity();
            identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
            return Task.FromResult(identity);
        }
    

    and also trying to implement IUserClaimStore with the returning default (empty list).

    The first will not through an error but does not end up authenticated. The later will still through the odd claim "System.Security.Claims.Claim..ctor" error

    EDIT

    Found out why the ctor error was occurring. The user object was coming back without the ID so the default UserManager was getting upset. Fixed that and used the default UserManager which now no longer throws an error, but still doesn't log the user in. The identity object it returns looks good from what I can tell.

  • Jon
    Jon over 10 years
    updated post with your feedback, no longer errors but doesn't authenticate / show as logged in
  • jamesSampica
    jamesSampica over 10 years
    @Jon Are those methods in the UserStore? That's where they go, not in the UserManager
  • Jon
    Jon over 10 years
    I do not have those methods in my userstore, I do not implement the IUserClaimStore, I thought that if it wasn't implemented it wouldn't be blowing up
  • Jon
    Jon over 10 years
    didn't read your post clear enough, (missed the "or"). I tried the override UserManager
  • jamesSampica
    jamesSampica over 10 years
    @Jon You can use the default UserManager and implement your own UserStore. Either way you will need a custom UserStore
  • jao
    jao almost 10 years
    This has fixed it for me
  • Jamie Keeling
    Jamie Keeling almost 10 years
    I needed to do this after creating my users via EF and not using the management methods.
  • Gustyn
    Gustyn over 9 years
    Worked great for me as well.
  • kaveman
    kaveman over 9 years
    This is great. I've been following WebApi Basic Authentication sample on CodePlex and this was the missing piece.
  • Zapnologica
    Zapnologica about 9 years
    Shouldn't Identity populate the needed fields for you? I am using EF, I simply overrode the Key to be of type int, and I am getting this error as well.
  • Zapnologica
    Zapnologica about 9 years
    However this did work, I cant thank you enough, I have been trolling for hours on this issue.
  • Zapnologica
    Zapnologica about 9 years
    Fixed my problem. Giving security stamp a value magically made it work.
  • QuantumHive
    QuantumHive over 8 years
    This works for me. I've been trying to create a seed for dev users.
  • Edwin
    Edwin almost 7 years
    I had a username and wanted to sign in. Had the same problem and this helped me: var user = userManager.FindByNameAsync("some_username").Result; user.SecurityStamp = Guid.NewGuid().ToString(); signInManager.SignIn(user, false, false);