A successful sign in is not being maintained - Custom SignInManager - Libsodium - .NET 5

294 views Asked by At

I have an existing database with some login information where the passwords are stored using LibSodium

Using a custom-built SignInManager<> I want to verify passwords and sign customers in. Later, after a successful sign-in, I plan to transition passwords to a native .NET password hasher.

Custom SignInManager

I use new and Iden to replace the standard SignInResult with an enum with extra return values (like SignInResult.NotFound)

public class CustomSignInManager : SignInManager<CustomAccount>
{

    // REMOVED FOR BREVITY

    public new async Task<Iden.SignInResult> PasswordSignInAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure)
    {
        if (userName == null) throw new ArgumentNullException(nameof(userName));
        if (password == null) throw new ArgumentNullException(nameof(password));

        var user = await UserManager.FindByEmailAsync(userName);

        if (user == null) return Iden.SignInResult.NotFound;
            
        if (user.Status != AccountStatus.Verified) return Iden.SignInResult.NotAllowed;

        if (user.EncryptionType == AccountEncryptionType.LibSodium)
            if (Sodium.PasswordHash.ScryptHashStringVerify(user.EncryptedPassword, password))
                return Iden.SignInResult.Success;
            
        return (await base.PasswordSignInAsync(userName, password, isPersistent, lockoutOnFailure)).Convert();
                //.Convert() => converts from Microsoft.AspNetCore.Identity.SignInResult to my enum
    }
}

Sign In Call

public async Task<IActionResult> OnPostAsync(string? returnUrl = null)
{
    _userHistory = new LoginHistory(_config, Request);
    ReturnUrl = returnUrl ?? Url.Content("~/");

    if (ModelState.IsValid)
    {
        switch (await _signInManager.PasswordSignInAsync(Email, Password, RememberMe, lockoutOnFailure: true))
        {
            case Iden.SignInResult.Success:
                // REMOVED FOR BREVITY
                return LocalRedirect(ReturnUrl);

            // REMOVED FROM BREVITY
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

Startup => ConfigureServices

services
    .AddIdentity<CustomerAccount, IdentityRole>(options => {

    })
    .AddEntityFrameworkStores<SiteContext>()
    .AddUserStore<CustomerAccountStore>()
    .AddSignInManager<CustomerSignInManager>()
    .AddDefaultTokenProviders();

Even though I get a SignInResult.Success after the "Sign In Call", I can't see no set-cookie in the response. Plus, on the next page _signInManager.IsSignedIn(User) is false!

I thought it might have been because the domain requires signing in (windows user auth) to gain access (which messed with User.Identity?.IsAuthenticated!)

1

There are 1 answers

0
glenn223 On BEST ANSWER

First of all ‍♂️, I'm a numpty.

Before returning my Success response I needed to call await SignInAsync(user, isPersistent);. It's within this method that it does the necessary work to save the data (cookie) that I needed.

FYI

There's also SignInOrTwoFactorAsync for those with TFA (soon to be me, if I stop wasting time on stupidity that is)

My new code
if (Sodium.PasswordHash.ScryptHashStringVerify(user.EncryptedPassword, password))
{
    // TODO: TFA setup and switch to SignInOrTwoFactorAsync
    await SignInAsync(user, isPersistent);

    return Iden.SignInResult.Success;
}