V14: User token revocation and session sign-out (#15511)

* Fix token revocation endpoints registration for .well-known

* Add logout and revocation endpoints to OpenIddict permissions

* Register urls for post_logout_redirect_uri

* Create /signout endpoint
This commit is contained in:
Elitsa Marinovska
2024-01-02 13:42:39 +01:00
committed by GitHub
parent 674c48ab9a
commit f3cb8fe117
3 changed files with 46 additions and 11 deletions

View File

@@ -40,8 +40,8 @@ public static class UmbracoBuilderAuthExtensions
Paths.MemberApi.LogoutEndpoint.TrimStart(Constants.CharArrays.ForwardSlash),
Paths.BackOfficeApi.LogoutEndpoint.TrimStart(Constants.CharArrays.ForwardSlash))
.SetRevocationEndpointUris(
Paths.BackOfficeApi.RevokeEndpoint.TrimStart(Constants.CharArrays.ForwardSlash),
Paths.MemberApi.RevokeEndpoint.TrimStart(Constants.CharArrays.ForwardSlash));
Paths.MemberApi.RevokeEndpoint.TrimStart(Constants.CharArrays.ForwardSlash),
Paths.BackOfficeApi.RevokeEndpoint.TrimStart(Constants.CharArrays.ForwardSlash));
// Enable authorization code flow with PKCE
options

View File

@@ -5,17 +5,18 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Web.BackOffice.Security;
using Umbraco.Extensions;
using Umbraco.Cms.Api.Management.Routing;
using SignInResult = Microsoft.AspNetCore.Mvc.SignInResult;
using IdentitySignInResult = Microsoft.AspNetCore.Identity.SignInResult;
using SignInResult = Microsoft.AspNetCore.Mvc.SignInResult;
namespace Umbraco.Cms.Api.Management.Controllers.Security;
@@ -29,17 +30,20 @@ public class BackOfficeController : SecurityControllerBase
private readonly IBackOfficeSignInManager _backOfficeSignInManager;
private readonly IBackOfficeUserManager _backOfficeUserManager;
private readonly IOptions<SecuritySettings> _securitySettings;
private readonly ILogger<BackOfficeController> _logger;
public BackOfficeController(
IHttpContextAccessor httpContextAccessor,
IBackOfficeSignInManager backOfficeSignInManager,
IBackOfficeUserManager backOfficeUserManager,
IOptions<SecuritySettings> securitySettings)
IOptions<SecuritySettings> securitySettings,
ILogger<BackOfficeController> logger)
{
_httpContextAccessor = httpContextAccessor;
_backOfficeSignInManager = backOfficeSignInManager;
_backOfficeUserManager = backOfficeUserManager;
_securitySettings = securitySettings;
_logger = logger;
}
// FIXME: this is a temporary solution to get the new backoffice auth rolling.
@@ -92,6 +96,33 @@ public class BackOfficeController : SecurityControllerBase
: await AuthorizeExternal(request);
}
[HttpGet("signout")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> Signout()
{
var userName = await GetUserNameFromAuthCookie();
await _backOfficeSignInManager.SignOutAsync();
_logger.LogInformation("User {UserName} from IP address {RemoteIpAddress} has logged out",
userName ?? "UNKNOWN", HttpContext.Connection.RemoteIpAddress);
// Returning a SignOutResult will ask OpenIddict to redirect the user agent
// to the post_logout_redirect_uri specified by the client application.
return SignOut(Constants.Security.NewBackOfficeAuthenticationType, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
/// <summary>
/// Retrieve the user principal stored in the authentication cookie.
/// </summary>
private async Task<string?> GetUserNameFromAuthCookie()
{
AuthenticateResult cookieAuthResult = await HttpContext.AuthenticateAsync(Constants.Security.NewBackOfficeAuthenticationType);
return cookieAuthResult.Succeeded
? cookieAuthResult.Principal?.Identity?.Name
: null;
}
private async Task<IActionResult> AuthorizeInternal(OpenIddictRequest request)
{
// TODO: ensure we handle sign-in notifications for internal logins.
@@ -101,11 +132,7 @@ public class BackOfficeController : SecurityControllerBase
// for future reference, notifications are already handled for the external login flow by
// by calling BackOfficeSignInManager.ExternalLoginSignInAsync
// retrieve the user principal stored in the authentication cookie.
AuthenticateResult cookieAuthResult = await HttpContext.AuthenticateAsync(Constants.Security.NewBackOfficeAuthenticationType);
var userName = cookieAuthResult.Succeeded
? cookieAuthResult.Principal?.Identity?.Name
: null;
var userName = await GetUserNameFromAuthCookie();
if (userName != null)
{

View File

@@ -104,15 +104,23 @@ public class BackOfficeApplicationManager : OpenIdDictApplicationManagerBase, IB
CallbackUrlFor(_backOfficeHost ?? backOfficeUrl, _authorizeCallbackPathName ?? "/umbraco")
},
Type = OpenIddictConstants.ClientTypes.Public,
PostLogoutRedirectUris =
{
CallbackUrlFor(_backOfficeHost ?? backOfficeUrl, _authorizeCallbackPathName ?? "/umbraco/login"),
// FIXME: remove when we no longer use Umbraco.Web.UI project
CallbackUrlFor(_backOfficeHost ?? backOfficeUrl, _authorizeCallbackPathName ?? "/umbraco")
},
Permissions =
{
OpenIddictConstants.Permissions.Endpoints.Authorization,
OpenIddictConstants.Permissions.Endpoints.Token,
OpenIddictConstants.Permissions.Endpoints.Logout,
OpenIddictConstants.Permissions.Endpoints.Revocation,
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode,
OpenIddictConstants.Permissions.GrantTypes.RefreshToken,
OpenIddictConstants.Permissions.ResponseTypes.Code
}
};
private static Uri CallbackUrlFor(Uri url, string relativePath) => new Uri( $"{url.GetLeftPart(UriPartial.Authority)}/{relativePath.TrimStart(Constants.CharArrays.ForwardSlash)}");
private static Uri CallbackUrlFor(Uri url, string relativePath) => new Uri($"{url.GetLeftPart(UriPartial.Authority)}/{relativePath.TrimStart(Constants.CharArrays.ForwardSlash)}");
}