Search code examples
c#asp.net-coreasp.net-core-mvcasp.net-identity-2

ASP.NET 5 Identity - custom SignInManager


I have a MVC 6 project (vNext) and I am playing around with the ASP.NET Identity. In my case I don't want to use the build-in stuff which uses the EF (SignInManager, UserManager, UserStore). I have an external database and I just want to make a username/password lookup and return a valid cookie. So I started writing my own classes.

public class MyUser
{
    public string Id { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public string PasswordHash { get; set; }
}

public class MyUserStore : IUserStore<MyUser>, IUserPasswordStore<MyUser>
{
    ...
}

In the MyUserStore class I am using hard-coded list of users as my store (only for test purposes). And I overrode some methods just to return the data from the hard-coded store.

public class MyUserManager : UserManager<MyUser>
{
    public MyUserManager(
        IUserStore<MyUser> store,
        IOptions<IdentityOptions> optionsAccessor,
        IPasswordHasher<MyUser> passwordHasher,
        IEnumerable<IUserValidator<MyUser>> userValidators,
        IEnumerable<IPasswordValidator<MyUser>> passwordValidators,
        ILookupNormalizer keyNormalizer,
        IdentityErrorDescriber errors,
        IEnumerable<IUserTokenProvider<MyUser>> tokenProviders,
        ILoggerFactory logger,
        IHttpContextAccessor contextAccessor) :
        base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, tokenProviders, logger, contextAccessor)
    {
    }
}

Here I made the methods CheckPasswordAsync and VerifyPasswordAsync to return true and PasswordVerificationResult.Success respectively just for the test.

public class MyClaimsPrincipleFactory : IUserClaimsPrincipalFactory<MyUser>
{
    public Task<ClaimsPrincipal> CreateAsync(MyUser user)
    {
        return Task.Factory.StartNew(() =>
        {
            var identity = new ClaimsIdentity();
            identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
            var principle = new ClaimsPrincipal(identity);

            return principle;
        });
    }
}

public class MySignInManager : SignInManager<MyUser>
{
    public MySignInManager(MyUserManager userManager, IHttpContextAccessor contextAccessor, IUserClaimsPrincipalFactory<MyUser> claimsFactory, IOptions<IdentityOptions> optionsAccessor = null, ILoggerFactory logger = null)
            : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger)
    {
    }

    public override Task<SignInResult> PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout)
    {
        // here goes the external username and password look up

        if (userName.ToLower() == "username" && password.ToLower() == "password")
        {
            return base.PasswordSignInAsync(userName, password, isPersistent, shouldLockout);
        }
        else
        {
            return Task.FromResult(SignInResult.Failed);
        }
    }
}

And everything is hooked up in the Startup class as follows:

services.AddIdentity<MyUser, MyRole>()
            .AddUserStore<MyUserStore>()
            .AddUserManager<MyUserManager>()
            .AddDefaultTokenProviders();

And because I didn't manage to create the MySignInManager object in the Startup code in order to add it into the DI (for later injection in the controllers and views), I am creating it in the MyAccountController.

public MyAccountController(IHttpContextAccessor httpContextAccessor, UserManager<MyUser> userManager, IOptions<IdentityOptions> optionsAccessor, ILoggerFactory logger)
{
    SignInManager = new MySignInManager(userManager as MyUserManager, httpContextAccessor, new MyClaimsPrincipleFactory(), optionsAccessor, logger);
}

In my MyLogin action in the MyAccount controller I am calling PasswordSignInAsync and I can see that I am getting the cookie with the encoded claims in it (from the MyClaimsPrincipleFactory). When I try to call some other action with the AuthorizeAttribute on it I can see that the cookie is in the request header but I am unauthorized (more precisely, because I didn't remove the built-in default ASP.NET Identity Authentication from the visual studio sample template, I am redirected to the Account/Login instead).

Is this the right way of customizing ASP.NET Identity and what am I missing here?


Solution

  • The issue here was that I didn't provide the AuthenticationType of my ClaimsIdentity object. This blog post helped me.

    To have IsAuthenticated set to true, you need to specify an authentication type in the ctor:

    var id = new ClaimsIdentity(claims, “Custom”);