Using UserManager.FindAsync with a custom UserStore

Akbari picture Akbari · Jul 26, 2015 · Viewed 7.1k times · Source

I have implemented a custom UserStore, it implements IUserStore<DatabaseLogin, int> and IUserPasswordStore<DatabaseLogin, int>.

My Login action method is as below:

if (ModelState.IsValid)
{
    if (Authentication.Login(user.Username, user.Password))
    {
        DatabaseLogin x = await UserManager.FindAsync(user.Username, user.Password);
        DatabaseLogin Login = Authentication.FindByName(user.Username);
        if (Login != null)
        {
            ClaimsIdentity ident = await UserManager.CreateIdentityAsync(Login,
                DefaultAuthenticationTypes.ApplicationCookie);
            AuthManager.SignOut();
            AuthManager.SignIn(new AuthenticationProperties
            {
                IsPersistent = false
            }, ident);
            return RedirectToAction("Index", "Home");
        }
    }
    else
    {
        ModelState.AddModelError("", "Invalid Login");
    }
}
return View();

In the custom authentication class that I wrote, Authentication, I have a Login method that works fine, also FindByName method returns an app user. But if I try to SignIn with that login, the user isn't recognized as authenticated and HttpContext.User.Identity is always null, so I imagine that I have to try UserManager.FindAsync.

This method calls FindByNameAsync and GetPasswordHashAsync, and it always return null.

public Task<DatabaseLogin> FindByNameAsync(string userName)
{
    if (string.IsNullOrEmpty(userName))
        throw new ArgumentNullException("userName");
    return Task.FromResult<DatabaseLogin>(Authentication.FindByName(userName));
}

public Task<string> GetPasswordHashAsync(DatabaseLogin user)
{
    if (user == null)
        throw new ArgumentNullException("user");
    return Task.FromResult<string>(user.Password);
}

And the Authentication.FindByName

public static DatabaseLogin FindByName(string name)
{
    string GetUserQuery = string.Format(
        "USE db;SELECT principal_id AS id, name as userName, create_date AS CreateDate, modify_date AS modifyDate FROM sys.database_principals WHERE type='S' AND authentication_type = 1 AND name = '{0}'"
        , name);
    DatabaseLogin user;
    using (var db = new EFDbContext())
    {
        user = db.Database.SqlQuery<DatabaseLogin>(GetUserQuery).FirstOrDefault();
    }
    user.Password = Convert.ToBase64String(Encoding.ASCII.GetBytes("pass"));
    return user;
}

As you can see I'm using database users, I'm not sure how I can retrieve a hashed password for them. For now, I'm just storing the Base65 of the correct password!

I have no idea where I'm going wrong, any guidance is welcome.

Answer

Akbari picture Akbari · Dec 18, 2015

Short answer: nothing's wrong. User is authenticated in other action methods, but apparently not in the current action method.

This is the process that I followed, maybe it will help you debug your app.

After reading the source code, FindAsync first calls FindByNameAsync, followed by CheckPasswordAsync which references VerifyPasswordAsync. So it should be fine If I could override VerifyPasswordAsync.

I created a custom password hasher that implements IPasswordHasher, and registered it in the create method of my UserManager like this:

manager.PasswordHasher = new DbPasswordHasher();

So by now, I can get my user from UserManager.FindAsync, but it turned out that it doesn't matter where you get the user since HttpContext.User.Identity is still null! My mistake was that I didn't notice the user isn't authenticated in the current action, in other action methods it works as expected!