I would like to add a second authentication factor to a Blazor application created with RadZen. Despite the known uncertainties, it was decided that the second factor should be sent by e-mail.
So I created a new page to enter the second factor and then extended the RadZen login function to handle the second factor:
public async Task<IActionResult> Login(string userName, string password, string redirectUrl)
{
if (env.EnvironmentName == "Development" && userName == "admin" && password == "admin")
{
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, "admin"),
new Claim(ClaimTypes.Email, "admin")
};
roleManager.Roles.ToList().ForEach(r => claims.Add(new Claim(ClaimTypes.Role, r.Name)));
await signInManager.SignInWithClaimsAsync(new ApplicationUser { UserName = userName, Email = userName }, isPersistent: false, claims);
return Redirect($"~/{redirectUrl}");
}
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{
var user = await userManager.FindByNameAsync(userName);
if (user == null)
{
return RedirectWithError("Invalid user or password", redirectUrl);
}
if (!user.EmailConfirmed)
{
return RedirectWithError("User email not confirmed", redirectUrl);
}
var result = await signInManager.PasswordSignInAsync(userName, password, false, true);
if (result.Succeeded)
{
return Redirect($"~/{redirectUrl}");
}
else if (result.RequiresTwoFactor)
{
return await SendSecondFactorRequest(user, redirectUrl);
}
}
return RedirectWithError("Invalid user or password", redirectUrl);
}
private async Task<IActionResult> SendSecondFactorRequest(ApplicationUser user, string redirectUrl)
{
if (!user.TwoFactorEnabled)
{
return Redirect($"~/{redirectUrl}");
}
var token = await userManager.GenerateTwoFactorTokenAsync(user, TokenOptions.DefaultEmailProvider);
this.mailService.SendToken(user.Email, token);
return Redirect($"~/login-confirmation?Redirecturl={redirectUrl};User={user.Name}");
}
This generates the token and sends it by mail before calling the input page for the token.
After the user enters the token, the following function is executed and fails.
public async Task<IActionResult> LoginVerification(string user, string key, string redirectUrl)
{
var result = await signInManager.TwoFactorSignInAsync(TokenOptions.DefaultEmailProvider, key, false, false)
if (result.Succeeded)
{
var autentic = this.securetyService.IsAuthenticated();
return Ok(redirectUrl);
}
return StatusCode(420, "Ungültiger Benutzername oder Verifikationsschlüssel.");
}
If I work with the userManager's VerifyTwoFactorTokenAsync() function instead, it confirms that the correct token has been entered but the RadZen login status check via SecurityService.IsAuthenticated() then returns false, so the app doesn't realize that the user is logged in.
public async Task<IActionResult> LoginVerification(string user, string key, string redirectUrl)
{
if ((!string.IsNullOrEmpty(user)) && (!string.IsNullOrEmpty(key)))
{
var userInfo = await userManager.FindByNameAsync(user);
var result = await userManager.VerifyTwoFactorTokenAsync(userInfo, TokenOptions.DefaultEmailProvider, key);
if (result)
{
var autentic = this.securetyService.IsAuthenticated();
return Ok(redirectUrl);
}
}
return StatusCode(420, "Ungültiger Benutzername oder Verifikationsschlüssel.");
}
RadZen Login check:
protected override async System.Threading.Tasks.Task OnInitializedAsync()
{
Globals.PropertyChanged += OnPropertyChanged;
await Security.InitializeAsync(AuthenticationStateProvider);
if (!Security.IsAuthenticated())
{
UriHelper.NavigateTo("Login", true);
}
else
{
await Load();
}
}
Obviously I'm doing something wrong, but unfortunately I don't see what. Can someone tell me what I need to change to accept the second factor?
ringhat