diff --git a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs index 5db8fcbf..0a3ae921 100644 --- a/src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs +++ b/src/OpenIddict.Server/Internal/OpenIddictServerProvider.cs @@ -4,9 +4,11 @@ * the license and the contributors participating to this project. */ +using System; using System.ComponentModel; using System.Diagnostics; using System.Linq; +using System.Text; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Extensions; using AspNet.Security.OpenIdConnect.Primitives; @@ -75,6 +77,20 @@ namespace OpenIddict.Server context.Request.IsTokenRequest(), "The request should be an authorization or token request."); + // While null/unauthenticated identities can be validly represented and are allowed by + // the OpenID Connect server handler, this most likely indicates that the developer + // has not correctly set the authentication type associated with the claims identity, + // which may later cause issues when validating opaque access tokens, as the resulting + // principal would be considered unauthenticated by the ASP.NET Core authorization stack. + if (context.Ticket.Principal.Identity == null || !context.Ticket.Principal.Identity.IsAuthenticated) + { + throw new InvalidOperationException(new StringBuilder() + .AppendLine("The specified principal doesn't contain a valid or authenticated identity.") + .Append("Make sure that both 'ClaimsPrincipal.Identity' and 'ClaimsPrincipal.Identity.AuthenticationType' ") + .Append("are not null and that 'ClaimsPrincipal.Identity.IsAuthenticated' returns 'true'.") + .ToString()); + } + if (context.Request.IsTokenRequest() && (context.Request.IsAuthorizationCodeGrantType() || context.Request.IsRefreshTokenGrantType())) { diff --git a/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.cs b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.cs index 4f734af8..b4c5bb03 100644 --- a/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.cs +++ b/test/OpenIddict.Server.Tests/Internal/OpenIddictServerProviderTests.cs @@ -9,6 +9,7 @@ using System.Collections.Immutable; using System.Linq; using System.Reflection; using System.Security.Claims; +using System.Text; using System.Threading; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Client; @@ -114,6 +115,33 @@ namespace OpenIddict.Server.Tests Assert.Equal("value", (string) response["custom_string_parameter"]); } + [Fact] + public async Task ProcessSigninResponse_ThrowsAnExceptionForInvalidIdentity() + { + // Arrange + var server = CreateAuthorizationServer(); + + var client = new OpenIdConnectClient(server.CreateClient()); + + // Act and assert + var exception = await Assert.ThrowsAsync(delegate + { + return client.PostAsync(TokenEndpoint, new OpenIdConnectRequest + { + GrantType = OpenIdConnectConstants.GrantTypes.Password, + Username = "johndoe", + Password = "A3ddj3w", + ["use-null-authentication-type"] = true + }); + }); + + Assert.Equal(new StringBuilder() + .AppendLine("The specified principal doesn't contain a valid or authenticated identity.") + .Append("Make sure that both 'ClaimsPrincipal.Identity' and 'ClaimsPrincipal.Identity.AuthenticationType' ") + .Append("are not null and that 'ClaimsPrincipal.Identity.IsAuthenticated' returns 'true'.") + .ToString(), exception.Message); + } + [Fact] public async Task ProcessSigninResponse_AuthenticationPropertiesAreAutomaticallyRestored() { @@ -1471,7 +1499,10 @@ namespace OpenIddict.Server.Tests return Task.CompletedTask; } - var identity = new ClaimsIdentity(OpenIddictServerDefaults.AuthenticationScheme); + var identity = !request.HasParameter("use-null-authentication-type") ? + new ClaimsIdentity(OpenIddictServerDefaults.AuthenticationScheme) : + new ClaimsIdentity(); + identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Magnifique"); var ticket = new AuthenticationTicket(