V13: Fix members while using basic auth. (#18206)

* Flow additional identities to new principal

* Add extension to more easily get member identity

* Ensure the member is used instead of the backoffice user in `MemberManager`

* Update snippet

* Fix the comment that I broke

* Update src/Umbraco.Web.Common/Extensions/MemberClaimsPrincipalExtensions.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
Mole
2025-02-03 19:48:08 +01:00
committed by GitHub
parent e7411244fd
commit b4a9dc0770
4 changed files with 47 additions and 14 deletions

View File

@@ -5,7 +5,7 @@
@using Umbraco.Extensions
@{
var isLoggedIn = Context.User?.Identity?.IsAuthenticated ?? false;
var isLoggedIn = Context.User.GetMemberIdentity()?.IsAuthenticated ?? false;
var logoutModel = new PostRedirectModel();
// You can modify this to redirect to a different URL instead of the current one
logoutModel.RedirectUrl = null;
@@ -15,7 +15,7 @@
{
<div class="login-status">
<p>Welcome back <strong>@Context?.User?.Identity?.Name</strong>!</p>
<p>Welcome back <strong>@Context.User?.GetMemberIdentity()?.Name</strong>!</p>
@using (Html.BeginUmbracoForm<UmbLoginStatusController>("HandleLogout", new { RedirectUrl = logoutModel.RedirectUrl }))
{

View File

@@ -62,9 +62,16 @@ public static class HttpContextExtensions
// Update the HttpContext's user with the authenticated user's principal to ensure
// that subsequent requests within the same context will recognize the user
// as authenticated.
if (result.Succeeded)
if (result is { Succeeded: true, Principal.Identity: not null })
{
httpContext.User = result.Principal;
// We need to get existing identities that are not the backoffice kind and flow them to the new identity
// Otherwise we can't log in as both a member and a backoffice user
// For instance if you've enabled basic auth.
ClaimsPrincipal? authenticatedPrincipal = result.Principal;
IEnumerable<ClaimsIdentity> existingIdentities = httpContext.User.Identities.Where(x => x.IsAuthenticated && x.AuthenticationType != authenticatedPrincipal.Identity.AuthenticationType);
authenticatedPrincipal.AddIdentities(existingIdentities);
httpContext.User = authenticatedPrincipal;
}
return result;

View File

@@ -0,0 +1,18 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
namespace Umbraco.Extensions;
public static class MemberClaimsPrincipalExtensions
{
/// <summary>
/// Tries to get specifically the member identity from the ClaimsPrincipal
/// </summary>
/// <remarks>
/// The identity returned is the one with default authentication type.
/// </remarks>
/// <param name="principal">The principal to find the identity in.</param>
/// <returns>The default authenticated authentication type identity.</returns>
public static ClaimsIdentity? GetMemberIdentity(this ClaimsPrincipal principal)
=> principal.Identities.FirstOrDefault(x => x.AuthenticationType == IdentityConstants.ApplicationScheme);
}

View File

@@ -1,4 +1,5 @@
using System.Globalization;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
@@ -124,8 +125,11 @@ public class MemberManager : UmbracoUserManager<MemberIdentityUser, MemberPasswo
/// <inheritdoc />
public virtual bool IsLoggedIn()
{
HttpContext? httpContext = _httpContextAccessor.HttpContext;
return httpContext?.User.Identity?.IsAuthenticated ?? false;
// We have to try and specifically find the member identity, it's entirely possible for there to be both backoffice and member.
ClaimsIdentity? memberIdentity = _httpContextAccessor.HttpContext?.User.GetMemberIdentity();
return memberIdentity is not null &&
memberIdentity.IsAuthenticated;
}
/// <inheritdoc />
@@ -181,23 +185,27 @@ public class MemberManager : UmbracoUserManager<MemberIdentityUser, MemberPasswo
/// <inheritdoc />
public virtual async Task<MemberIdentityUser?> GetCurrentMemberAsync()
{
if (_currentMember == null)
if (_currentMember is not null)
{
if (!IsLoggedIn())
{
return null;
}
_currentMember = await GetUserAsync(_httpContextAccessor.HttpContext?.User!);
return _currentMember;
}
if (IsLoggedIn() is false)
{
return null;
}
// Create a principal the represents the member security context.
var memberPrincipal = new ClaimsPrincipal(_httpContextAccessor.HttpContext?.User.GetMemberIdentity()!);
_currentMember = await GetUserAsync(memberPrincipal);
return _currentMember;
}
public virtual IPublishedContent? AsPublishedMember(MemberIdentityUser user) => _store.GetPublishedMember(user);
/// <summary>
/// This will check if the member has access to this path
/// This will check if the member has access to this path.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>