diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs index 79c60bc230..09ff520766 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.MembersIdentity.cs @@ -65,6 +65,7 @@ public static partial class UmbracoBuilderExtensions services.ConfigureOptions(); services.ConfigureOptions(); + services.AddScoped(); services.AddUnique(); diff --git a/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs b/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs index 9aa073483a..66714be9e6 100644 --- a/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs +++ b/src/Umbraco.Web.Common/Security/ConfigureMemberCookieOptions.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; @@ -47,6 +48,14 @@ public sealed class ConfigureMemberCookieOptions : IConfigureNamedOptions + { + // We need to resolve the BackOfficeSecurityStampValidator per request as a requirement (even in aspnetcore they do this) + MemberSecurityStampValidator securityStampValidator = + ctx.HttpContext.RequestServices.GetRequiredService(); + + await securityStampValidator.ValidateAsync(ctx); + }, OnRedirectToAccessDenied = ctx => { ctx.Response.StatusCode = StatusCodes.Status403Forbidden; diff --git a/src/Umbraco.Web.Common/Security/MemberSecurityStampValidator.cs b/src/Umbraco.Web.Common/Security/MemberSecurityStampValidator.cs new file mode 100644 index 0000000000..3623626112 --- /dev/null +++ b/src/Umbraco.Web.Common/Security/MemberSecurityStampValidator.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Security; + +namespace Umbraco.Cms.Web.Common.Security; + +/// +/// A security stamp validator for the back office +/// +public class MemberSecurityStampValidator : SecurityStampValidator +{ + public MemberSecurityStampValidator( + IOptions options, + MemberSignInManager signInManager, ISystemClock clock, ILoggerFactory logger) + : base(options, signInManager, clock, logger) + { + } + + public override Task ValidateAsync(CookieValidatePrincipalContext context) + { + return base.ValidateAsync(context); + } +} diff --git a/src/Umbraco.Web.Common/Security/MemberSecurityStampValidatorOptions.cs b/src/Umbraco.Web.Common/Security/MemberSecurityStampValidatorOptions.cs new file mode 100644 index 0000000000..38189b2d6a --- /dev/null +++ b/src/Umbraco.Web.Common/Security/MemberSecurityStampValidatorOptions.cs @@ -0,0 +1,7 @@ +using Microsoft.AspNetCore.Identity; + +namespace Umbraco.Cms.Web.Common.Security; + +public class MemberSecurityStampValidatorOptions : SecurityStampValidatorOptions +{ +} diff --git a/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs b/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs index 63defed2de..bdcd723020 100644 --- a/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs +++ b/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs @@ -238,6 +238,14 @@ public abstract class UmbracoSignInManager : SignInManager /// public override async Task SignOutAsync() { + // Update the security stamp to sign out everywhere. + TUser? user = await UserManager.GetUserAsync(Context.User); + + if (user is not null) + { + await UserManager.UpdateSecurityStampAsync(user); + } + // override to replace IdentityConstants.ApplicationScheme with custom auth types // code taken from aspnetcore: https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/SignInManager.cs await Context.SignOutAsync(AuthenticationType);