MVC5 (VS2012) Identity CreateIdentityAsync - Value cannot be null
Asked Answered
P

7

42

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.

Photofinishing answered 20/2, 2014 at 19:47 Comment(1)
I used the built in usermanager... tried using resharper to decompile it but nothing readbale came out the other end.Photofinishing
O
95

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.

Oxpecker answered 29/4, 2014 at 1:51 Comment(7)
I needed to do this after creating my users via EF and not using the management methods.Rochellrochella
Worked great for me as well.Inexplicable
This is great. I've been following WebApi Basic Authentication sample on CodePlex and this was the missing piece.Transmogrify
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.Visitant
However this did work, I cant thank you enough, I have been trolling for hours on this issue.Visitant
This works for me. I've been trying to create a seed for dev users.Callaghan
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);Mugger
R
14

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.

Raquel answered 9/4, 2014 at 20:7 Comment(1)
Fixed my problem. Giving security stamp a value magically made it work.Visitant
V
6

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;
                });
Vaporimeter answered 10/4, 2020 at 4:8 Comment(0)
C
2

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>();
Canard answered 25/3, 2014 at 9:58 Comment(1)
This has fixed it for meDeoxidize
A
1

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);
}
Aspersion answered 20/2, 2014 at 20:8 Comment(5)
updated post with your feedback, no longer errors but doesn't authenticate / show as logged inPhotofinishing
@Photofinishing Are those methods in the UserStore? That's where they go, not in the UserManagerAspersion
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 upPhotofinishing
didn't read your post clear enough, (missed the "or"). I tried the override UserManagerPhotofinishing
@Photofinishing You can use the default UserManager and implement your own UserStore. Either way you will need a custom UserStoreAspersion
V
0

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

Vehement answered 24/2, 2014 at 16:31 Comment(0)
W
0

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

   //...

}
Wollastonite answered 29/11, 2015 at 12:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.