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.
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.
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();
}
// 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);
}
}
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.
Always fails at the final step to verify the code.
I believe there's a separate switch case for 2FA that is what you're looking for:
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
then RequiresTwoFactor returns False
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 whyfalse == 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:
Hi Damian
Thanks for the help so for anyone else with issue
Here is how the code is
An easier solution may be
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
Results in a Succeeded but should be a RequiresTwoFactor
return the Login to "[email protected]" and two factor works again
Anybody come across this
is working on a reply...