From 65504096763835f540a8f42258dbbb760926ca2d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 14 Apr 2026 19:04:35 +0000
Subject: [PATCH] Extend IIdentityProvider with Logout and
CreateForbiddenResponse; update HttpServer forbidden page flow
Agent-Logs-Url: https://github.com/webexpress-framework/WebExpress.WebCore/sessions/42eb3cb9-7106-40a7-a098-76c8b8ae1498
Co-authored-by: ReneSchwarzer <31061438+ReneSchwarzer@users.noreply.github.com>
---
.../Data/MockIdentityProvider.cs | 33 ++++++++++++++++
src/WebExpress.WebCore/HttpServer.cs | 18 +++++++++
.../WebIdentity/IIdentityManager.cs | 20 ++++++++++
.../WebIdentity/IIdentityProvider.cs | 29 ++++++++++++++
.../WebIdentity/IdentityManager.cs | 39 +++++++++++++++++++
5 files changed, 139 insertions(+)
diff --git a/src/WebExpress.WebCore.Test/Data/MockIdentityProvider.cs b/src/WebExpress.WebCore.Test/Data/MockIdentityProvider.cs
index e39fb9d..da689ac 100644
--- a/src/WebExpress.WebCore.Test/Data/MockIdentityProvider.cs
+++ b/src/WebExpress.WebCore.Test/Data/MockIdentityProvider.cs
@@ -56,6 +56,17 @@ public IIdentity Authenticate(IRequest request)
return null; // not needed for this test
}
+ ///
+ /// Logs out the specified request by clearing any authentication state.
+ ///
+ ///
+ /// The request whose authentication state should be cleared. Cannot be null.
+ ///
+ public void Logout(IRequest request)
+ {
+ // not needed for this test
+ }
+
///
/// Displays a login dialog using the specified request and identity information.
///
@@ -77,5 +88,27 @@ public IResponse CreateAuthenticationPrompt(IRequest request, IEndpointContext i
{
return null;
}
+
+ ///
+ /// Creates a forbidden response page for the specified request when the authenticated
+ /// user lacks the required permissions to access the requested resource.
+ ///
+ ///
+ /// The request for which access was denied. Cannot be null.
+ ///
+ ///
+ /// The endpoint that the user attempted to access.
+ ///
+ ///
+ /// The authenticated identity that lacks sufficient permissions.
+ ///
+ ///
+ /// A response representing the forbidden page if this provider can handle the forbidden
+ /// scenario; otherwise, null.
+ ///
+ public IResponse CreateForbiddenResponse(IRequest request, IEndpointContext initiator, IIdentity identity)
+ {
+ return null;
+ }
}
}
diff --git a/src/WebExpress.WebCore/HttpServer.cs b/src/WebExpress.WebCore/HttpServer.cs
index 13ca08c..401a703 100644
--- a/src/WebExpress.WebCore/HttpServer.cs
+++ b/src/WebExpress.WebCore/HttpServer.cs
@@ -657,6 +657,24 @@ public async Task ProcessRequestAsync(IHttpContext httpContext)
// check again
if (!_componentHub.IdentityManager.CheckAccess(identity, searchResult.EndpointContext))
{
+ // if the user is authenticated but lacks the required permissions, show the forbidden page
+ if (identity is not null)
+ {
+ var forbiddenResponse = _componentHub.IdentityManager.CreateForbiddenResponse
+ (
+ httpContext.Request,
+ searchResult.EndpointContext,
+ identity
+ );
+
+ if (forbiddenResponse is not null)
+ {
+ await responseSender.SendAsync(httpContext, forbiddenResponse);
+ return;
+ }
+ }
+
+ // if the user is not authenticated, show the login prompt
var loginResponse = _componentHub.IdentityManager.CreateAuthenticationPrompt
(
httpContext.Request,
diff --git a/src/WebExpress.WebCore/WebIdentity/IIdentityManager.cs b/src/WebExpress.WebCore/WebIdentity/IIdentityManager.cs
index b736b44..5d1674b 100644
--- a/src/WebExpress.WebCore/WebIdentity/IIdentityManager.cs
+++ b/src/WebExpress.WebCore/WebIdentity/IIdentityManager.cs
@@ -41,6 +41,26 @@ public interface IIdentityManager : IComponentManager
///
IResponse CreateAuthenticationPrompt(IRequest request, IEndpointContext initiator, IIdentity identity = null);
+ ///
+ /// Creates a forbidden response page for the specified request when the authenticated
+ /// user lacks the required permissions to access the requested resource.
+ ///
+ ///
+ /// The request for which access was denied. Cannot be null.
+ ///
+ ///
+ /// The endpoint that the user attempted to access. Used to determine the origin and
+ /// context of the authorization failure.
+ ///
+ ///
+ /// The authenticated identity that lacks sufficient permissions. Cannot be null.
+ ///
+ ///
+ /// A response representing the forbidden page if a registered identity provider can handle the
+ /// forbidden scenario; otherwise, null.
+ ///
+ IResponse CreateForbiddenResponse(IRequest request, IEndpointContext initiator, IIdentity identity);
+
///
/// Attempts to authenticate the specified request within the given application context.
///
diff --git a/src/WebExpress.WebCore/WebIdentity/IIdentityProvider.cs b/src/WebExpress.WebCore/WebIdentity/IIdentityProvider.cs
index e568759..bcc27ed 100644
--- a/src/WebExpress.WebCore/WebIdentity/IIdentityProvider.cs
+++ b/src/WebExpress.WebCore/WebIdentity/IIdentityProvider.cs
@@ -31,6 +31,15 @@ public interface IIdentityProvider
///
IIdentity Authenticate(IRequest request);
+ ///
+ /// Logs out the specified request by clearing any authentication state
+ /// managed by this identity provider.
+ ///
+ ///
+ /// The request whose authentication state should be cleared. Cannot be null.
+ ///
+ void Logout(IRequest request);
+
///
/// Displays a login dialog using the specified request and identity information.
///
@@ -49,5 +58,25 @@ public interface IIdentityProvider
/// relevant status information.
///
IResponse CreateAuthenticationPrompt(IRequest request, IEndpointContext initiator, IIdentity identity);
+
+ ///
+ /// Creates a forbidden response page for the specified request when the authenticated
+ /// user lacks the required permissions to access the requested resource.
+ ///
+ ///
+ /// The request for which access was denied. Cannot be null.
+ ///
+ ///
+ /// The endpoint that the user attempted to access. Used to determine the origin and
+ /// context of the authorization failure.
+ ///
+ ///
+ /// The authenticated identity that lacks sufficient permissions. Cannot be null.
+ ///
+ ///
+ /// A response representing the forbidden page if this provider can handle the forbidden
+ /// scenario; otherwise, null.
+ ///
+ IResponse CreateForbiddenResponse(IRequest request, IEndpointContext initiator, IIdentity identity);
}
}
diff --git a/src/WebExpress.WebCore/WebIdentity/IdentityManager.cs b/src/WebExpress.WebCore/WebIdentity/IdentityManager.cs
index 05d10e1..afcedf3 100644
--- a/src/WebExpress.WebCore/WebIdentity/IdentityManager.cs
+++ b/src/WebExpress.WebCore/WebIdentity/IdentityManager.cs
@@ -404,6 +404,36 @@ public IResponse CreateAuthenticationPrompt(IRequest request, IEndpointContext i
return null;
}
+ ///
+ /// Creates a forbidden response page for the specified request when the authenticated
+ /// user lacks the required permissions to access the requested resource.
+ ///
+ /// The request for which access was denied. Cannot be null.
+ /// The endpoint that the user attempted to access.
+ /// The authenticated identity that lacks sufficient permissions.
+ ///
+ /// A response representing the forbidden page if a registered identity provider can handle the
+ /// forbidden scenario; otherwise, null.
+ ///
+ public IResponse CreateForbiddenResponse(IRequest request, IEndpointContext initiator, IIdentity identity)
+ {
+ if (_identityProviders.TryGetValue(initiator?.ApplicationContext, out var list))
+ {
+ foreach (var provider in list)
+ {
+ var response = provider.CreateForbiddenResponse(request, initiator, identity);
+
+ if (response is not null)
+ {
+ // the first provider that can show a forbidden page wins
+ return response;
+ }
+ }
+ }
+
+ return null;
+ }
+
///
/// Attempts to authenticate the specified request within the given application context.
///
@@ -454,6 +484,15 @@ public bool Login(IRequest request, IIdentity identity)
/// The request.
public void Logout(IRequest request)
{
+ // notify all registered identity providers so they can clear their own state
+ foreach (var list in _identityProviders.Values)
+ {
+ foreach (var provider in list)
+ {
+ provider.Logout(request);
+ }
+ }
+
var session = _componentHub.SessionManager.GetSession(request);
session.RemoveProperty();
}