Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Daniel Rogers 139 posts 717 karma points
    Mar 19, 2024 @ 14:49
    Daniel Rogers
    0

    Two Factor authentication for members

    documentation is lacking the final steps for member access when two factor authentication is enabled.

    Using: Umbraco 13.2.0 Microsoft.aspnetcore.authentication.google version 7.0.13

    I have successfully followed the documentation for the users and add it there but the members area is lacking the actual login procedure for the front end.

    1 have created a page with the qr code to scan with google authenticator. and verified it with the verification process. And this all creates correctly.

    This was all done following this documentation https://docs.umbraco.com/umbraco-cms/v/12.latest/reference/security/two-factor-authentication

    However the documentation falls shot of explaining the process to verify codes when the member is logging in.

    IMember member = _memberService.GetByUsername(model.Username);
    
    // Tests and processes logins where Two factor is not enabled -- works fine
    if (!(await _twoFactorLoginService.IsTwoFactorEnabledAsync(member.Key))) {
    .................
    }
    // Checks that the user name and password are correct if so hide username password and show verification code input  -- works fine
    else if (string.IsNullOrEmpty(model.Code) &&
        await _memberManager.ValidateCredentialsAsync(model.Username, model.Password))
    {
        TempData.Add("LoginCredentials_" + model.UniqueID, "hidden");
        return (IActionResult)RedirectToCurrentUmbracoPage();
    }
    else
    {
    // get the used details
        MemberIdentityUser userIdentity = await _memberManager.FindByNameAsync(model.Username);
        // gets the two auth provider name
        var providerID = _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(_memberService.GetAllMembers().FirstOrDefault(x => x.Username == model.Username).Key);
        var providerName = providerID.Result.First();
    
    if (userIdentity != null)
    {
        var result = await _memberSignInManager.TwoFactorSignInAsync(
        providerName,
        model.Code,
        false,
        true);
        if (result.Succeeded) // always fails
        {
            return Redirect(model.ReturnUrl);
        }
        } 
    }
    

    Always fails at the final step to verify the code.

  • Damian 69 posts 392 karma points
    Mar 19, 2024 @ 18:32
    Damian
    0

    I believe there's a separate switch case for 2FA that is what you're looking for:

            if (result.Succeeded)
            {
            (…)
            }
            if (result.RequiresTwoFactor)
            {
    // complete your 2FA process here
            }
    
  • Daniel Rogers 139 posts 717 karma points
    Mar 19, 2024 @ 22:29
    Daniel Rogers
    0

    Sorry Damian but what have you tested to get your result.

    If you notice I've already determined two factor is required and are trying to verify the code.

    If you are refering to the result of

    _memberSignInManager.TwoFactorSignInAsync(providerName, model.Code, false, true);
    

    then RequiresTwoFactor returns False

  • Damian 69 posts 392 karma points
    Mar 20, 2024 @ 14:49
    Damian
    0

    Ah, cheers mate - i misread where you were in the flow and threw out the thought before heading into a meeting. I figured an response might be better than a silent thread! I see that the linked documentation is short of exhaustive.

    The last time I implemented 2FA for membership was on v9 so it's been a little while. I don't have a running copy on this machine but do still have code.

    Looking at Microsoft.AspNetCore.Identity.SignInResult there are other returns that give additional reasoning as to why false == result.Succeeded What does the debugger say when you attach and step through the code?

    Perhaps the genericized snippit (again, for v9 - should that be relevant(?)) that follows will help guide you to success:

    public async Task<IActionResult> Verify2FACode([Bind(Prefix = "AwesomePersonGroupVerify2FaCodeModel")]AwesomePersonGroupVerify2FaCodeModel model)
    {
        var user = await _memberSignInManagerExternalLogins.GetTwoFactorAuthenticationUserAsync();
        if (null == user)
        {
            _logger.LogWarning("Membership: (Verify2FACode) - Member not logged in");
            Redirect(_siteNodeMappingService.GetAwesomePersonGroupSection().Url());
        }
    
        if (ModelState.IsValid)
        {
            SignInResult result = await _memberSignInManagerExternalLogins.TwoFactorSignInAsync(model.Provider, model.Code, model.IsPersistent, model.RememberClient);
            var currentMember = await _memberManager.GetCurrentMemberAsync();
    
            if (result.Succeeded)
            {               
                // Ensure member has 2FA auth established
                var foundMemberResult = await _memberManager.FindByNameAsync(currentMember.UserName);
                if  (foundMemberResult?.TwoFactorEnabled == false)
                {
                    var AwesomePersonGroupGroup = _memberService.GetMembersByGroup("AwesomePersonGroup");
                    var AwesomePersonGroupGroupList = _memberService.GetMembersByGroup("AwesomePersonGroup").ToList();
    
                    // Ensure this member 
                    if (AwesomePersonGroupGroupList.Any(x => x.Key.Equals(foundMemberResult.Key)))
                    {
                        _memberService.AssignRole(foundMemberResult.UserName, "AwesomePersonGroup");
                    }
                }
                _logger.LogInformation("Membership: (Verify2FACode) - success - {AwesomePersonGroup}", user.UserName);
                 RedirectToCurrentUmbracoPage();
            }
            else if (result.IsLockedOut)
            {
                _logger.LogWarning("Membership: (Verify2FACode) - AwesomePersonGroup is locked out  - {AwesomePersonGroup}", user.UserName);
                ModelState.AddModelError(nameof(AwesomePersonGroupVerify2FaCodeModel.Code), "AwesomePersonGroup is locked out");
            }
            else if (result.IsNotAllowed)
            {
                _logger.LogWarning("Membership: (Verify2FACode) - AwesomePersonGroup is not allowed - {AwesomePersonGroup}", user.UserName);
                ModelState.AddModelError(nameof(AwesomePersonGroupVerify2FaCodeModel.Code), "AwesomePersonGroup is not allowed");
            }
            else
            {
                _logger.LogWarning("Membership: (Verify2FACode) - Invalid code - {AwesomePersonGroup}", user.UserName);
                ModelState.AddModelError(nameof(AwesomePersonGroupVerify2FaCodeModel.Code), "Invalid code");
            }
        }
        else
        {
            _logger.LogWarning("Membership: (Verify2FACode) - Invalid ModelState - {AwesomePersonGroup}", user.UserName);
        }
    
        //We need to set this, to ensure we show the 2fa login page
        var providerNames = await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(user.Key);
        ViewData.SetTwoFactorProviderNames(providerNames);
    
        return CurrentUmbracoPage();
    }
    
  • Daniel Rogers 139 posts 717 karma points
    Mar 21, 2024 @ 06:45
    Daniel Rogers
    100

    Hi Damian

    Thanks for the help so for anyone else with issue

    Here is how the code is

    // test if two factor requiered
    if (!(await _twoFactorLoginService.IsTwoFactorEnabledAsync(member.Key)))
    {
    ...... process login if not required
    }
    else if (string.IsNullOrEmpty(model.Code)
    {
    ........ test username and password or valid, change view to show code entry
    }
    else
    {
    // code I need to make it work
    // get the user identity
    MemberIdentityUser userIdentity = await _memberManager.FindByNameAsync(model.Username);
    // get the providerNames allocated against the user (should only be one so could simplify here
    IEnumerable<string> providerNames =
        await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(userIdentity.Key);
    
    // try logging in
    Microsoft.AspNetCore.Identity.SignInResult result = await _memberSignInManager.PasswordSignInAsync(model.Username, model.Password, true, false);
    if (result.RequiresTwoFactor) // should return this resault
    {
    // process the verification code
     result = await _memberSignInManager.TwoFactorSignInAsync(
            providerNames.First(),
            model.Code,
            false,
            true);
        if (result.Succeeded) // all going well with valid code should be able to redirect to members page and are now logged in.
            return Redirect(model.ReturnUrl); 
    }
    }
    

    An easier solution may be

    Microsoft.AspNetCore.Identity.SignInResult result = await _memberSignInManager.PasswordSignInAsync(model.Username, model.Password, true, false);
    if (resault.Succeeded)
    {
    ........ redirect to members page and are now logged in.
    }
    else if (result.RequiresTwoFactor)
    {
        if (string.IsNullOrEmpty(model.Code)
        {
        ........ test username and password or valid, change view to show code entry
        }
    else
    {
    result = await _memberSignInManager.TwoFactorSignInAsync(
                providerNames.First(),
                model.Code,
                false,
                true);
            if (result.Succeeded) // all going well with valid code should be able to redirect to members page and are now logged in.
                return Redirect(model.ReturnUrl); 
    }
    }
    
  • Daniel Rogers 139 posts 717 karma points
    Mar 21, 2024 @ 12:48
    Daniel Rogers
    0

    Looks like I may have jumped the gun.

    Yes all is logging in and with Two Facter Authentication and the surgested alternative version of code above works perfect as well

    Except:

    The member has some critical values

    Member Name at the top 1. Login = [email protected] 2. Email = [email protected] 3. Password = ????? 4. Two Factor Authentication = true

    Now this logins in fine with Two factor

    but if we change the Login value to a say "Joe Bloggs"

    we then need to use "Joe Bloggs" in the username field Perfect thats what we want but

    SignInResult loginResult = await _memberSignInManager.PasswordSignInAsync(model.Username, model.Password, false, true);
    

    Results in a Succeeded but should be a RequiresTwoFactor

    return the Login to "[email protected]" and two factor works again

    Anybody come across this

Please Sign in or register to post replies

Write your reply to:

Draft