diff --git a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Authentication.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Authentication.cs index c75d9eec..0b0e7dee 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Authentication.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Authentication.cs @@ -492,8 +492,11 @@ namespace OpenIddict.Server { var options = (OpenIddictServerOptions) context.Options; + // Note: as this stage, the request associated with the context may be null if an error + // occurred very early in the pipeline (e.g an invalid HTTP verb was used by the caller). + // Remove the authorization request from the distributed cache. - if (options.EnableRequestCaching && !string.IsNullOrEmpty(context.Request.RequestId)) + if (options.EnableRequestCaching && !string.IsNullOrEmpty(context.Request?.RequestId)) { // Note: the cache key is always prefixed with a specific marker // to avoid collisions with the other types of cached requests. diff --git a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Session.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Session.cs index 2bd4c543..2cf47826 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Session.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.Session.cs @@ -210,8 +210,11 @@ namespace OpenIddict.Server { var options = (OpenIddictServerOptions) context.Options; + // Note: as this stage, the request associated with the context may be null if an error + // occurred very early in the pipeline (e.g an invalid HTTP verb was used by the caller). + // Remove the logout request from the distributed cache. - if (options.EnableRequestCaching && !string.IsNullOrEmpty(context.Request.RequestId)) + if (options.EnableRequestCaching && !string.IsNullOrEmpty(context.Request?.RequestId)) { // Note: the cache key is always prefixed with a specific marker // to avoid collisions with the other types of cached requests. diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Authentication.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Authentication.cs index b09a00d1..95eb70aa 100644 --- a/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Authentication.cs +++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Authentication.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Client; using AspNet.Security.OpenIdConnect.Primitives; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; using Moq; @@ -979,6 +980,29 @@ namespace OpenIddict.Server.Tests It.IsAny()), Times.Once()); } + [Fact] + public async Task ApplyAuthorizationResponse_SupportsNullRequests() + { + // Note: when an invalid HTTP verb is used, the OpenID Connect server handler refuses to extract the request + // and immediately returns an error. In this specific case, ApplyAuthorizationResponseContext.Request is null + // and this test ensures ApplyAuthorizationResponse can safely handle cases where the request is unavailable. + + // Arrange + var server = CreateAuthorizationServer(builder => + { + builder.EnableRequestCaching(); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.SendAsync(HttpMethods.Put, AuthorizationEndpoint, new OpenIdConnectRequest()); + + // Assert + Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error); + Assert.Equal("The specified HTTP method is not valid.", response.ErrorDescription); + } + [Fact] public async Task ApplyAuthorizationResponse_ErroredRequestIsNotHandledLocallyWhenStatusCodeMiddlewareIsEnabled() { diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Session.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Session.cs index 340aaab7..13f700fc 100644 --- a/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Session.cs +++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.Session.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Client; using AspNet.Security.OpenIdConnect.Primitives; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; using Moq; @@ -187,6 +188,29 @@ namespace OpenIddict.Server.Tests Assert.Equal("af0ifjsldkj", response.State); } + [Fact] + public async Task ApplyLogoutResponse_SupportsNullRequests() + { + // Note: when an invalid HTTP verb is used, the OpenID Connect server handler refuses to extract the request + // and immediately returns an error. In this specific case, ApplyLogoutResponseContext.Request is null + // and this test ensures ApplyLogoutResponse can safely handle cases where the request is unavailable. + + // Arrange + var server = CreateAuthorizationServer(builder => + { + builder.EnableRequestCaching(); + }); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act + var response = await client.SendAsync(HttpMethods.Put, LogoutEndpoint, new OpenIdConnectRequest()); + + // Assert + Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error); + Assert.Equal("The specified HTTP method is not valid.", response.ErrorDescription); + } + [Fact] public async Task ApplyLogoutResponse_ErroredRequestIsNotHandledLocallyWhenStatusCodeMiddlewareIsEnabled() {