From 20684ca40a2a57edc791fa8dbe9266b9d87a7945 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Mon, 23 Dec 2024 11:33:16 +0100 Subject: [PATCH 1/6] added admin login url customization --- src/HopFrame.Web/Models/HopFrameWebModuleConfig.cs | 5 +++++ .../Pages/Administration/AdminDashboard.razor | 10 ++++++++-- src/HopFrame.Web/Pages/Administration/AdminLogin.razor | 2 +- .../Pages/Administration/AdminPageList.razor | 4 +++- src/HopFrame.Web/ServiceCollectionExtensions.cs | 4 +++- 5 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 src/HopFrame.Web/Models/HopFrameWebModuleConfig.cs diff --git a/src/HopFrame.Web/Models/HopFrameWebModuleConfig.cs b/src/HopFrame.Web/Models/HopFrameWebModuleConfig.cs new file mode 100644 index 0000000..226b144 --- /dev/null +++ b/src/HopFrame.Web/Models/HopFrameWebModuleConfig.cs @@ -0,0 +1,5 @@ +namespace HopFrame.Web.Models; + +public class HopFrameWebModuleConfig { + public string AdminLoginPageUri { get; set; } = "/administration/login"; +} \ No newline at end of file diff --git a/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor b/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor index fe7afb1..30acfcb 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor @@ -8,11 +8,12 @@ @using HopFrame.Security.Authorization @using HopFrame.Web.Admin.Providers @using HopFrame.Web.Components +@using HopFrame.Web.Models @using Microsoft.AspNetCore.Components.Web @using Microsoft.Extensions.Options @layout AdminLayout - + Admin Dashboard @@ -38,11 +39,16 @@ @inject NavigationManager Navigator @inject IAdminPagesProvider Pages @inject IOptions Options +@inject HopFrameWebModuleConfig Config @code { public void NavigateTo(string url) { - Navigator.NavigateTo("administration/" + url, true); + Navigator.NavigateTo("/administration/" + url, true); + } + + public string ConstructRedirectUri() { + return Config.AdminLoginPageUri + "?redirect=/administration"; } } diff --git a/src/HopFrame.Web/Pages/Administration/AdminLogin.razor b/src/HopFrame.Web/Pages/Administration/AdminLogin.razor index 8e0f1e1..a6d1566 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminLogin.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminLogin.razor @@ -65,6 +65,6 @@ return; } - Navigator.NavigateTo(string.IsNullOrEmpty(RedirectAfter) ? DefaultRedirect : "/administration/" + RedirectAfter, true); + Navigator.NavigateTo(string.IsNullOrEmpty(RedirectAfter) ? DefaultRedirect : RedirectAfter, true); } } \ No newline at end of file diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor index 1086918..780d7ac 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor @@ -16,6 +16,7 @@ @using HopFrame.Security.Claims @using HopFrame.Web.Admin @using HopFrame.Web.Components +@using HopFrame.Web.Models @_pageData.Title @@ -107,6 +108,7 @@ @inject IPermissionRepository Permissions @inject SweetAlertService Alerts @inject NavigationManager Navigator +@inject HopFrameWebModuleConfig Config @code { [Parameter] @@ -251,6 +253,6 @@ } private string GenerateRedirectString() { - return "/administration/login?redirect=" + _pageData?.Url; + return Config.AdminLoginPageUri + "?redirect=/administration/" + _pageData?.Url; } } \ No newline at end of file diff --git a/src/HopFrame.Web/ServiceCollectionExtensions.cs b/src/HopFrame.Web/ServiceCollectionExtensions.cs index 4b6232a..f87dc4b 100644 --- a/src/HopFrame.Web/ServiceCollectionExtensions.cs +++ b/src/HopFrame.Web/ServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using CurrieTechnologies.Razor.SweetAlert2; using HopFrame.Database; using HopFrame.Security.Authentication; using HopFrame.Web.Admin; +using HopFrame.Web.Models; using HopFrame.Web.Services; using HopFrame.Web.Services.Implementation; using Microsoft.AspNetCore.Builder; @@ -12,12 +13,13 @@ using Microsoft.Extensions.DependencyInjection; namespace HopFrame.Web; public static class ServiceCollectionExtensions { - public static IServiceCollection AddHopFrame(this IServiceCollection services, ConfigurationManager configuration) where TDbContext : HopDbContextBase { + public static IServiceCollection AddHopFrame(this IServiceCollection services, ConfigurationManager configuration, HopFrameWebModuleConfig config = null) where TDbContext : HopDbContextBase { services.AddHttpClient(); services.AddHopFrameRepositories(); services.AddScoped(); services.AddTransient(); services.AddAdminContext(); + services.AddSingleton(config ?? new HopFrameWebModuleConfig()); // Component library's services.AddSweetAlert2(); -- 2.49.1 From df68b6dbf897b4fb5c8ae87fefab68a28a8888aa Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Mon, 23 Dec 2024 11:55:56 +0100 Subject: [PATCH 2/6] properly combined OpenId callback uri --- .../Authentication/OpenID/Implementation/OpenIdAccessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs b/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs index 3dd1a82..4ed050c 100644 --- a/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs +++ b/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs @@ -38,7 +38,7 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions ConstructAuthUri(string defaultCallback, string state = null) { var protocol = accessor.HttpContext!.Request.IsHttps ? "https" : "http"; - var callback = options.Value.Callback ?? $"{protocol}://{accessor.HttpContext!.Request.Host.Value}/{defaultCallback}"; + var callback = options.Value.Callback ?? Path.Combine($"{protocol}://{accessor.HttpContext!.Request.Host.Value}", defaultCallback); var configuration = await LoadConfiguration(); return $"{configuration.AuthorizationEndpoint}?response_type=code&client_id={options.Value.ClientId}&redirect_uri={callback}&scope=openid%20profile%20email%20offline_access&state={state}"; -- 2.49.1 From 73d89a241f16abbff89bb6a29912faf47530bfd7 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Mon, 23 Dec 2024 12:03:45 +0100 Subject: [PATCH 3/6] Fixed pipeline --- tests/HopFrame.Tests.Web/Pages/AdminLoginTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/HopFrame.Tests.Web/Pages/AdminLoginTests.cs b/tests/HopFrame.Tests.Web/Pages/AdminLoginTests.cs index 0e2909a..f729726 100644 --- a/tests/HopFrame.Tests.Web/Pages/AdminLoginTests.cs +++ b/tests/HopFrame.Tests.Web/Pages/AdminLoginTests.cs @@ -76,7 +76,7 @@ public class AdminLoginTests : TestContext { var password = component.Find("""input[type="password"]"""); var submit = component.Find("button"); - component.Instance.RedirectAfter = "testRedirect"; + component.Instance.RedirectAfter = "/administration/testRedirect"; // Act email.Change("test@example.com"); -- 2.49.1 From 3031dda710f50aeba2a0966f624cf11c8a03858b Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Mon, 23 Dec 2024 12:17:54 +0100 Subject: [PATCH 4/6] Removed implicit callback definition --- HopFrame.sln.DotSettings.user | 1 + src/HopFrame.Api/Controller/OpenIdController.cs | 4 ++-- .../Extensions/ServiceCollectionExtensions.cs | 5 ++++- .../Authentication/OpenID/IOpenIdAccessor.cs | 6 ++++-- .../OpenID/Implementation/OpenIdAccessor.cs | 8 ++++---- .../HopFrame.Testing.Api/Controllers/TestController.cs | 6 +++--- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/HopFrame.sln.DotSettings.user b/HopFrame.sln.DotSettings.user index ae702c9..01d3df3 100644 --- a/HopFrame.sln.DotSettings.user +++ b/HopFrame.sln.DotSettings.user @@ -80,6 +80,7 @@ + \ No newline at end of file diff --git a/src/HopFrame.Api/Controller/OpenIdController.cs b/src/HopFrame.Api/Controller/OpenIdController.cs index 50e8822..2892d98 100644 --- a/src/HopFrame.Api/Controller/OpenIdController.cs +++ b/src/HopFrame.Api/Controller/OpenIdController.cs @@ -14,7 +14,7 @@ public class OpenIdController(IOpenIdAccessor accessor, IOptions [HttpGet("redirect")] public async Task RedirectToProvider([FromQuery] string redirectAfter, [FromQuery] int performRedirect = 1) { - var uri = await accessor.ConstructAuthUri(DefaultCallback, redirectAfter); + var uri = await accessor.ConstructAuthUri(redirectAfter); if (performRedirect == 1) { return Redirect(uri); @@ -29,7 +29,7 @@ public class OpenIdController(IOpenIdAccessor accessor, IOptions return BadRequest("Authorization code is missing"); } - var token = await accessor.RequestToken(code, DefaultCallback); + var token = await accessor.RequestToken(code); if (token is null) { return Forbid("Authorization code is not valid"); diff --git a/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs b/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs index 19436eb..21ae878 100644 --- a/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs +++ b/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using HopFrame.Api.Logic; using HopFrame.Api.Logic.Implementation; using HopFrame.Database; using HopFrame.Security.Authentication; +using HopFrame.Security.Authentication.OpenID; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -25,8 +26,10 @@ public static class ServiceCollectionExtensions { if (!defaultAuthenticationSection.Exists() || configuration.GetValue("HopFrame:Authentication:DefaultAuthentication")) controllers.Add(typeof(AuthController)); - if (configuration.GetValue("HopFrame:Authentication:OpenID:Enabled")) + if (configuration.GetValue("HopFrame:Authentication:OpenID:Enabled")) { + IOpenIdAccessor.DefaultCallback = OpenIdController.DefaultCallback; controllers.Add(typeof(OpenIdController)); + } AddHopFrameNoEndpoints(services, configuration); services.AddMvcCore().UseSpecificControllers(controllers.ToArray()); diff --git a/src/HopFrame.Security/Authentication/OpenID/IOpenIdAccessor.cs b/src/HopFrame.Security/Authentication/OpenID/IOpenIdAccessor.cs index 09dc54c..8045b70 100644 --- a/src/HopFrame.Security/Authentication/OpenID/IOpenIdAccessor.cs +++ b/src/HopFrame.Security/Authentication/OpenID/IOpenIdAccessor.cs @@ -3,9 +3,11 @@ using HopFrame.Security.Authentication.OpenID.Models; namespace HopFrame.Security.Authentication.OpenID; public interface IOpenIdAccessor { + public static string DefaultCallback; + Task LoadConfiguration(); - Task RequestToken(string code, string defaultCallback); - Task ConstructAuthUri(string defaultCallback, string state = null); + Task RequestToken(string code); + Task ConstructAuthUri(string state = null); Task InspectToken(string token); Task RefreshAccessToken(string refreshToken); } \ No newline at end of file diff --git a/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs b/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs index 4ed050c..31fe0c0 100644 --- a/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs +++ b/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs @@ -32,13 +32,13 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions RequestToken(string code, string defaultCallback) { + public async Task RequestToken(string code) { if (options.Value.Cache.Enabled && options.Value.Cache.Auth.Enabled && cache.TryGetValue(AuthCodeCacheKey + code, out object cachedToken)) { return cachedToken as OpenIdToken; } var protocol = accessor.HttpContext!.Request.IsHttps ? "https" : "http"; - var callback = options.Value.Callback ?? Path.Combine($"{protocol}://{accessor.HttpContext!.Request.Host.Value}", defaultCallback); + var callback = options.Value.Callback ?? Path.Combine($"{protocol}://{accessor.HttpContext!.Request.Host.Value}", IOpenIdAccessor.DefaultCallback); var configuration = await LoadConfiguration(); @@ -65,9 +65,9 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions ConstructAuthUri(string defaultCallback, string state = null) { + public async Task ConstructAuthUri(string state = null) { var protocol = accessor.HttpContext!.Request.IsHttps ? "https" : "http"; - var callback = options.Value.Callback ?? Path.Combine($"{protocol}://{accessor.HttpContext!.Request.Host.Value}", defaultCallback); + var callback = options.Value.Callback ?? Path.Combine($"{protocol}://{accessor.HttpContext!.Request.Host.Value}", IOpenIdAccessor.DefaultCallback); var configuration = await LoadConfiguration(); return $"{configuration.AuthorizationEndpoint}?response_type=code&client_id={options.Value.ClientId}&redirect_uri={callback}&scope=openid%20profile%20email%20offline_access&state={state}"; diff --git a/testing/HopFrame.Testing.Api/Controllers/TestController.cs b/testing/HopFrame.Testing.Api/Controllers/TestController.cs index 3a3affe..156dc86 100644 --- a/testing/HopFrame.Testing.Api/Controllers/TestController.cs +++ b/testing/HopFrame.Testing.Api/Controllers/TestController.cs @@ -2,6 +2,7 @@ using HopFrame.Api.Logic; using HopFrame.Api.Models; using HopFrame.Database.Models; using HopFrame.Database.Repositories; +using HopFrame.Security.Authentication.OpenID; using HopFrame.Security.Authorization; using HopFrame.Security.Claims; using HopFrame.Testing.Api.Models; @@ -68,9 +69,8 @@ public class TestController(ITokenContext userContext, DatabaseContext context, } [HttpGet("url")] - public async Task>> GetUrl() { - var protocol = Request.IsHttps ? "https" : "http"; - return Ok($"{protocol}://{Request.Host.Value}/auth/callback"); + public ActionResult GetUrl() { + return Ok(IOpenIdAccessor.DefaultCallback ?? "Not set"); } } \ No newline at end of file -- 2.49.1 From 849ad649a8e1f85111ab780c815e21824fed38ae Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Mon, 23 Dec 2024 12:32:12 +0100 Subject: [PATCH 5/6] Fixed path combining issues + added cookie helper function --- .../Controller/OpenIdController.cs | 19 +++------------ .../Authentication/OpenID/IOpenIdAccessor.cs | 1 + .../OpenID/Implementation/OpenIdAccessor.cs | 23 ++++++++++++++++--- .../Services/Implementation/AuthService.cs | 6 +---- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/HopFrame.Api/Controller/OpenIdController.cs b/src/HopFrame.Api/Controller/OpenIdController.cs index 2892d98..37912a7 100644 --- a/src/HopFrame.Api/Controller/OpenIdController.cs +++ b/src/HopFrame.Api/Controller/OpenIdController.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Options; namespace HopFrame.Api.Controller; [ApiController, Route("api/v1/openid")] -public class OpenIdController(IOpenIdAccessor accessor, IOptions options) : ControllerBase { +public class OpenIdController(IOpenIdAccessor accessor) : ControllerBase { public const string DefaultCallback = "api/v1/openid/callback"; [HttpGet("redirect")] @@ -35,16 +35,7 @@ public class OpenIdController(IOpenIdAccessor accessor, IOptions return Forbid("Authorization code is not valid"); } - Response.Cookies.Append(ITokenContext.AccessTokenType, token.AccessToken, new CookieOptions { - MaxAge = TimeSpan.FromSeconds(token.ExpiresIn), - HttpOnly = false, - Secure = true - }); - Response.Cookies.Append(ITokenContext.RefreshTokenType, token.RefreshToken, new CookieOptions { - MaxAge = options.Value.RefreshToken.ConstructTimeSpan, - HttpOnly = false, - Secure = true - }); + accessor.SetAuthenticationCookies(token); if (string.IsNullOrEmpty(state)) { return Ok(new SingleValueResult(token.AccessToken)); @@ -65,11 +56,7 @@ public class OpenIdController(IOpenIdAccessor accessor, IOptions if (token is null) return NotFound("Refresh token not valid"); - Response.Cookies.Append(ITokenContext.AccessTokenType, token.AccessToken, new CookieOptions { - MaxAge = TimeSpan.FromSeconds(token.ExpiresIn), - HttpOnly = false, - Secure = true - }); + accessor.SetAuthenticationCookies(token); return Ok(new SingleValueResult(token.AccessToken)); } diff --git a/src/HopFrame.Security/Authentication/OpenID/IOpenIdAccessor.cs b/src/HopFrame.Security/Authentication/OpenID/IOpenIdAccessor.cs index 8045b70..4dc41c4 100644 --- a/src/HopFrame.Security/Authentication/OpenID/IOpenIdAccessor.cs +++ b/src/HopFrame.Security/Authentication/OpenID/IOpenIdAccessor.cs @@ -10,4 +10,5 @@ public interface IOpenIdAccessor { Task ConstructAuthUri(string state = null); Task InspectToken(string token); Task RefreshAccessToken(string refreshToken); + void SetAuthenticationCookies(OpenIdToken token); } \ No newline at end of file diff --git a/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs b/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs index 31fe0c0..bbe2b8c 100644 --- a/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs +++ b/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs @@ -1,6 +1,7 @@ using System.Text.Json; using HopFrame.Security.Authentication.OpenID.Models; using HopFrame.Security.Authentication.OpenID.Options; +using HopFrame.Security.Claims; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; @@ -18,7 +19,7 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions ConstructAuthUri(string state = null) { var protocol = accessor.HttpContext!.Request.IsHttps ? "https" : "http"; - var callback = options.Value.Callback ?? Path.Combine($"{protocol}://{accessor.HttpContext!.Request.Host.Value}", IOpenIdAccessor.DefaultCallback); + var callback = options.Value.Callback ?? Path.Combine($"{protocol}://{accessor.HttpContext!.Request.Host.Value}", IOpenIdAccessor.DefaultCallback).Replace("\\", "/"); var configuration = await LoadConfiguration(); return $"{configuration.AuthorizationEndpoint}?response_type=code&client_id={options.Value.ClientId}&redirect_uri={callback}&scope=openid%20profile%20email%20offline_access&state={state}"; @@ -120,4 +121,20 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions(await response.Content.ReadAsStreamAsync()); } + + public void SetAuthenticationCookies(OpenIdToken token) { + if (token.AccessToken is not null) + accessor.HttpContext!.Response.Cookies.Append(ITokenContext.AccessTokenType, token.AccessToken, new CookieOptions { + MaxAge = TimeSpan.FromSeconds(token.ExpiresIn), + HttpOnly = false, + Secure = true + }); + + if (token.RefreshToken is not null) + accessor.HttpContext!.Response.Cookies.Append(ITokenContext.RefreshTokenType, token.RefreshToken, new CookieOptions { + MaxAge = options.Value.RefreshToken.ConstructTimeSpan, + HttpOnly = false, + Secure = true + }); + } } \ No newline at end of file diff --git a/src/HopFrame.Web/Services/Implementation/AuthService.cs b/src/HopFrame.Web/Services/Implementation/AuthService.cs index 5c95a8f..ddb10d5 100644 --- a/src/HopFrame.Web/Services/Implementation/AuthService.cs +++ b/src/HopFrame.Web/Services/Implementation/AuthService.cs @@ -108,11 +108,7 @@ internal class AuthService( }); } - httpAccessor.HttpContext?.Response.Cookies.Append(ITokenContext.AccessTokenType, openIdToken.AccessToken, new CookieOptions { - MaxAge = TimeSpan.FromSeconds(openIdToken.ExpiresIn), - HttpOnly = false, - Secure = true - }); + accessor.SetAuthenticationCookies(openIdToken); return new() { Owner = user, CreatedAt = DateTime.Now, -- 2.49.1 From 0b9766f7dba94e89f62366e8c412d5dfcc84e652 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Mon, 23 Dec 2024 12:38:30 +0100 Subject: [PATCH 6/6] Added logout function + increased default openid config caching time --- src/HopFrame.Api/Controller/OpenIdController.cs | 6 +----- .../Authentication/OpenID/IOpenIdAccessor.cs | 1 + .../Authentication/OpenID/Implementation/OpenIdAccessor.cs | 5 +++++ .../Authentication/OpenID/Options/OpenIdOptions.cs | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/HopFrame.Api/Controller/OpenIdController.cs b/src/HopFrame.Api/Controller/OpenIdController.cs index 37912a7..733821d 100644 --- a/src/HopFrame.Api/Controller/OpenIdController.cs +++ b/src/HopFrame.Api/Controller/OpenIdController.cs @@ -1,10 +1,7 @@ using HopFrame.Api.Models; using HopFrame.Security.Authentication.OpenID; -using HopFrame.Security.Authentication.OpenID.Options; using HopFrame.Security.Claims; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; namespace HopFrame.Api.Controller; @@ -63,8 +60,7 @@ public class OpenIdController(IOpenIdAccessor accessor) : ControllerBase { [HttpDelete("logout")] public IActionResult Logout() { - Response.Cookies.Delete(ITokenContext.RefreshTokenType); - Response.Cookies.Delete(ITokenContext.AccessTokenType); + accessor.Logout(); return Ok(); } diff --git a/src/HopFrame.Security/Authentication/OpenID/IOpenIdAccessor.cs b/src/HopFrame.Security/Authentication/OpenID/IOpenIdAccessor.cs index 4dc41c4..91ec80d 100644 --- a/src/HopFrame.Security/Authentication/OpenID/IOpenIdAccessor.cs +++ b/src/HopFrame.Security/Authentication/OpenID/IOpenIdAccessor.cs @@ -11,4 +11,5 @@ public interface IOpenIdAccessor { Task InspectToken(string token); Task RefreshAccessToken(string refreshToken); void SetAuthenticationCookies(OpenIdToken token); + void Logout(); } \ No newline at end of file diff --git a/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs b/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs index bbe2b8c..2839d10 100644 --- a/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs +++ b/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs @@ -137,4 +137,9 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions