Member login via API controller works in "API level" but not in "partial view level"
I want to let website users give their email and then send them an one time code. This code is saved on a "oneTimeCode" property on member node that is created on the fly. Then user fills in the one time code in a another form field an submits.
It seems that the member is correctly logged in in the API controller code, SignInAsync(), but when the partial view afterwards checks if member is logged in then it is not.
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Web.Common.Controllers;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using System.Security.Claims;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Infrastructure.Scoping;
using MartinRud.MemberAuthorization.Services;
using MartinRud.MemberAuthorization.Models;
namespace MartinRud.MemberAuthorization.Controllers
{
[Route("api/[controller]")]
public class AuthMembersController : UmbracoApiController
{
private readonly IMemberService _memberService;
private readonly IEmailSender _emailSender;
private readonly IScopeProvider _scopeProvider;
private readonly IConfiguration _configuration;
private readonly ILogger<AuthMembersController> _logger;
public AuthMembersController(IMemberService memberService, IEmailSender emailSender, IScopeProvider scopeProvider, IConfiguration configuration, ILogger<AuthMembersController> logger)
{
_memberService = memberService;
_emailSender = emailSender;
_scopeProvider = scopeProvider;
_configuration = configuration;
_logger = logger;
}
[HttpPost("request-code")]
public IActionResult RequestCode([FromBody] RequestCodeModel model)
{
_logger.LogInformation($"Requesting code for email: {model.Email}");
var member = _memberService.GetByEmail(model.Email);
if (member == null)
{
_logger.LogInformation("Member not found, creating new member.");
member = _memberService.CreateMemberWithIdentity(model.Email, model.Email, model.Email, "Member");
_memberService.Save(member);
// Add new members to groups specified in appSettings
var memberGroupIds = _configuration.GetSection("SiteCustom:AuthMembers:AddNewMembersToGroups").Get<int[]>();
if (memberGroupIds != null)
{
foreach (var groupId in memberGroupIds)
{
_memberService.AssignRole(member.Id, groupId.ToString());
}
}
}
var code = GenerateOneTimeCode();
member.SetValue("oneTimeCode", code); // Save the code to the custom property
_memberService.Save(member);
// Send email using your email service
_emailSender.SendEmailAsync(model.Email, "Your One-Time Code", $"Your code is: {code}");
_logger.LogInformation("One-time code generated and sent.");
return Ok(new { message = "Code sent successfully." });
}
[HttpPost("validate")]
public async Task<IActionResult> Validate([FromBody] ValidateCodeModel model)
{
_logger.LogInformation($"Validating code for email: {model.Email}");
try
{
var members = _memberService.GetAllMembers();
var member = members.FirstOrDefault(m => string.Equals(m.Email, model.Email, StringComparison.OrdinalIgnoreCase));
if (member == null)
{
_logger.LogWarning("Member not found.");
return Unauthorized(new { message = "Member not found." });
}
if (!ValidateCode(member, model.Code))
{
_logger.LogWarning("Invalid code.");
return Unauthorized(new { message = "Invalid code." });
}
_logger.LogInformation("Code validated successfully.");
// Create the claims principal for the member
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, member.Key.ToString()),
new Claim(ClaimTypes.Name, member.Username)
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
// Sign in the member
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(1)
});
_logger.LogInformation("Member signed in successfully.");
return Ok(new { message = "Member signed in successfully." });
}
catch (Exception ex)
{
// Log the exception for debugging purposes
_logger.LogError(ex, "An error occurred during code validation.");
return StatusCode(500, new { message = "An error occurred during code validation." });
}
}
[HttpGet("status")]
public IActionResult Status()
{
var user = HttpContext.User;
bool isAuthenticated = user.Identity != null && user.Identity.IsAuthenticated;
var username = user.Identity?.Name ?? string.Empty;
return Ok(new
{
IsAuthenticated = isAuthenticated,
Username = username
});
}
private string GenerateOneTimeCode()
{
using (var rng = new RNGCryptoServiceProvider())
{
var bytes = new byte[6];
rng.GetBytes(bytes);
return BitConverter.ToString(bytes).Replace("-", "").Substring(0, 6);
}
}
private bool ValidateCode(IMember member, string code)
{
var storedCode = member.GetValue<string>("oneTimeCode");
return storedCode == code;
}
}
}
login.ts
document.addEventListener("DOMContentLoaded", () => {
console.log("DOMContentLoaded event fired");
const requestCodeBtn = document.getElementById('request-code-btn');
const validateCodeBtn = document.getElementById('validate-code-btn');
if (requestCodeBtn) {
console.log("Adding click event listener to requestCodeBtn");
requestCodeBtn.addEventListener('click', requestCode);
}
if (validateCodeBtn) {
console.log("Adding click event listener to validateCodeBtn");
validateCodeBtn.addEventListener('click', validateCode);
}
checkStatus();
});
async function checkStatus(): Promise<void> {
console.log("checkStatus function called");
try {
const response = await fetch('/api/authmembers/status');
console.log("Response from /api/authmembers/status:", response);
const data = await response.json();
console.log("Data from /api/authmembers/status:", data);
const statusMessage = document.getElementById('status-message');
const emailContainer = document.getElementById('email-container');
const codeContainer = document.getElementById('code-container');
if (statusMessage) {
if (data.isAuthenticated) {
statusMessage.textContent = `Welcome back, ${data.username}! You are logged in.`;
if (emailContainer) emailContainer.style.display = 'none';
if (codeContainer) codeContainer.style.display = 'none';
} else {
statusMessage.textContent = 'You are not logged in.';
}
}
} catch (error) {
console.error('Error fetching status:', error);
}
}
async function requestCode(): Promise<void> {
const emailInput = document.getElementById('email') as HTMLInputElement | null;
if (!emailInput) return;
const email = emailInput.value;
console.log("requestCode function called with email:", email);
if (!email) {
const messageElement = document.getElementById('message');
if (messageElement) messageElement.textContent = 'Please enter your email.';
return;
}
try {
const response = await fetch('/api/authmembers/request-code', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
});
console.log("Response from /api/authmembers/request-code:", response);
const data = await response.json();
const emailContainer = document.getElementById('email-container');
const codeContainer = document.getElementById('code-container');
const messageElement = document.getElementById('message');
if (response.ok) {
if (emailContainer) emailContainer.style.display = 'none';
if (codeContainer) codeContainer.style.display = 'block';
if (messageElement) messageElement.textContent = data.message;
} else {
if (messageElement) messageElement.textContent = `Error: ${data.message}`;
}
} catch (error) {
console.error('Error in requestCode:', error);
const messageElement = document.getElementById('message');
if (messageElement) messageElement.textContent = `Error: ${error instanceof Error ? error.message : 'An unknown error occurred.'}`;
}
}
async function validateCode(): Promise<void> {
const emailInput = document.getElementById('email') as HTMLInputElement | null;
const codeInput = document.getElementById('code') as HTMLInputElement | null;
if (!emailInput || !codeInput) return;
const email = emailInput.value;
const code = codeInput.value;
console.log("validateCode function called with email:", email, "and code:", code);
if (!email || !code) {
const messageElement = document.getElementById('message');
if (messageElement) messageElement.textContent = 'Please enter both email and code.';
return;
}
try {
const response = await fetch('/api/authmembers/validate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, code })
});
console.log("Response from /api/authmembers/validate:", response);
const data = await response.json();
const messageElement = document.getElementById('message');
if (response.ok) {
if (messageElement) messageElement.textContent = data.message;
window.location.reload(); // Redirect to a logged-in page or refresh the page
} else {
if (messageElement) messageElement.textContent = `Error: ${data.message}`;
}
} catch (error) {
console.error('Error in validateCode:', error);
const messageElement = document.getElementById('message');
if (messageElement) messageElement.textContent = `Error: ${error instanceof Error ? error.message : 'An unknown error occurred.'}`;
}
}
Member login via API controller works in "API level" but not in "partial view level"
I want to let website users give their email and then send them an one time code. This code is saved on a "oneTimeCode" property on member node that is created on the fly. Then user fills in the one time code in a another form field an submits.
It seems that the member is correctly logged in in the API controller code, SignInAsync(), but when the partial view afterwards checks if member is logged in then it is not.
What have I missed?
Login.cshtml
AuthControllerMembers.cs
login.ts
is working on a reply...