diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
index 82c39261..bee1f43c 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
@@ -98,7 +98,12 @@ namespace OpenIddict.Server
BeautifyUserCode.Descriptor,
AttachAccessTokenProperties.Descriptor,
- AttachDeviceCodeProperties.Descriptor)
+ AttachDeviceCodeProperties.Descriptor,
+
+ /*
+ * Sign-out processing:
+ */
+ ValidateSignOutDemand.Descriptor)
.AddRange(Authentication.DefaultHandlers)
.AddRange(Device.DefaultHandlers)
@@ -4052,5 +4057,43 @@ namespace OpenIddict.Server
return default;
}
}
+
+ ///
+ /// Contains the logic responsible of ensuring that the sign-out demand
+ /// is compatible with the type of the endpoint that handled the request.
+ ///
+ public class ValidateSignOutDemand : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(int.MinValue + 100_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public ValueTask HandleAsync([NotNull] ProcessSignOutContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (context.EndpointType != OpenIddictServerEndpointType.Logout)
+ {
+ throw new InvalidOperationException("An OpenID Connect response cannot be returned from this endpoint.");
+ }
+
+ return default;
+ }
+ }
}
}
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
index f8e4ec23..90e4a71f 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
@@ -1646,7 +1646,7 @@ namespace OpenIddict.Server.FunctionalTests
[InlineData(null, "custom_description", "custom_uri")]
[InlineData(null, null, "custom_uri")]
[InlineData(null, null, null)]
- public async Task ProcessSignIn_AllowsRejectingAuthorizationRequest(string error, string description, string uri)
+ public async Task ProcessSignIn_AllowsRejectingRequest(string error, string description, string uri)
{
// Arrange
var client = CreateClient(options =>
@@ -1677,44 +1677,6 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal(uri, response.ErrorUri);
}
- [Theory]
- [InlineData("custom_error", null, null)]
- [InlineData("custom_error", "custom_description", null)]
- [InlineData("custom_error", "custom_description", "custom_uri")]
- [InlineData(null, "custom_description", null)]
- [InlineData(null, "custom_description", "custom_uri")]
- [InlineData(null, null, "custom_uri")]
- [InlineData(null, null, null)]
- public async Task ProcessSignIn_AllowsRejectingTokenRequest(string error, string description, string uri)
- {
- // Arrange
- var client = CreateClient(options =>
- {
- options.EnableDegradedMode();
-
- options.AddEventHandler(builder =>
- builder.UseInlineHandler(context =>
- {
- context.Reject(error, description, uri);
-
- return default;
- }));
- });
-
- // Act
- var response = await client.PostAsync("/connect/token", new OpenIddictRequest
- {
- GrantType = GrantTypes.Password,
- Username = "johndoe",
- Password = "A3ddj3w"
- });
-
- // Assert
- Assert.Equal(error ?? Errors.InvalidRequest, response.Error);
- Assert.Equal(description, response.ErrorDescription);
- Assert.Equal(uri, response.ErrorUri);
- }
-
[Fact]
public async Task ProcessSignIn_AllowsHandlingResponse()
{
@@ -2834,6 +2796,95 @@ namespace OpenIddict.Server.FunctionalTests
Mock.Get(manager).Verify(manager => manager.CreateAsync(It.IsAny(), It.IsAny()), Times.Never());
}
+ [Fact]
+ public async Task ProcessSignOut_InvalidEndpointCausesAnException()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+ options.SetConfigurationEndpointUris("/signout");
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.SkipRequest();
+
+ return default;
+ }));
+ });
+
+ // Act and assert
+ var exception = await Assert.ThrowsAsync(delegate
+ {
+ return client.GetAsync("/signout");
+ });
+
+ Assert.Equal("An OpenID Connect response cannot be returned from this endpoint.", exception.Message);
+ }
+
+ [Theory]
+ [InlineData("custom_error", null, null)]
+ [InlineData("custom_error", "custom_description", null)]
+ [InlineData("custom_error", "custom_description", "custom_uri")]
+ [InlineData(null, "custom_description", null)]
+ [InlineData(null, "custom_description", "custom_uri")]
+ [InlineData(null, null, "custom_uri")]
+ [InlineData(null, null, null)]
+ public async Task ProcessSignOut_AllowsRejectingRequest(string error, string description, string uri)
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Reject(error, description, uri);
+
+ return default;
+ }));
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/logout", new OpenIddictRequest());
+
+ // Assert
+ Assert.Equal(error ?? Errors.InvalidRequest, response.Error);
+ Assert.Equal(description, response.ErrorDescription);
+ Assert.Equal(uri, response.ErrorUri);
+ }
+
+ [Fact]
+ public async Task ProcessSignOut_AllowsHandlingResponse()
+ {
+ // Arrange
+ var client = CreateClient(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Transaction.SetProperty("custom_response", new
+ {
+ name = "Bob le Bricoleur"
+ });
+
+ context.HandleRequest();
+
+ return default;
+ }));
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/logout", new OpenIddictRequest());
+
+ // Assert
+ Assert.Equal("Bob le Bricoleur", (string) response["name"]);
+ }
+
protected virtual void ConfigureServices(IServiceCollection services)
{
services.AddOpenIddict()