diff --git a/src/OpenIddict.Abstractions/OpenIddictConstants.cs b/src/OpenIddict.Abstractions/OpenIddictConstants.cs
index 2014b032..a3948036 100644
--- a/src/OpenIddict.Abstractions/OpenIddictConstants.cs
+++ b/src/OpenIddict.Abstractions/OpenIddictConstants.cs
@@ -167,6 +167,7 @@ public static class OpenIddictConstants
public const string ClientSecretBasic = "client_secret_basic";
public const string ClientSecretJwt = "client_secret_jwt";
public const string ClientSecretPost = "client_secret_post";
+ public const string None = "none";
public const string PrivateKeyJwt = "private_key_jwt";
}
diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx
index 11c43f17..4ea9d0ae 100644
--- a/src/OpenIddict.Abstractions/OpenIddictResources.resx
+++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx
@@ -1563,6 +1563,12 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
A client identifier must be specified in the client registration or web provider options when using 'response_type=none', the authorization code/hybrid/implicit flows or the device authorization flow.
+
+ At least one client authentication method must be configured when enabling the device authorization, introspection, revocation or token endpoints.
+
+
+ The '{0}' client assertion type must be configured when enabling the '{1}' client authentication method.
+
The security token is missing.
@@ -2082,6 +2088,9 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
The '{0}' claim returned in the specified client assertion doesn't match the expected value.
+
+ The '{0}' client authentication method is not supported.
+
The '{0}' parameter shouldn't be null or empty at this point.
@@ -2724,6 +2733,9 @@ This may indicate that the hashed entry is corrupted or malformed.
The authentication demand was rejected because the public application '{ClientId}' was not allowed to send a client assertion.
+
+ The request was rejected because the '{Method}' client authentication method that was used by the client application is not enabled in the server options.
+
https://documentation.openiddict.com/errors/{0}
diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
index eb0385d9..1f8679a0 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
@@ -1401,7 +1401,7 @@ public class OpenIddictApplicationManager : IOpenIddictApplication
var value = await Store.GetClientSecretAsync(application, cancellationToken);
if (string.IsNullOrEmpty(value))
{
- Logger.LogError(SR.GetResourceString(SR.ID6160), await GetClientIdAsync(application, cancellationToken));
+ Logger.LogInformation(SR.GetResourceString(SR.ID6160), await GetClientIdAsync(application, cancellationToken));
return false;
}
diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs
index fd81da00..35454181 100644
--- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs
+++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs
@@ -47,6 +47,9 @@ public sealed class OpenIddictServerAspNetCoreConfiguration : IConfigureOptions<
// Register the built-in event handlers used by the OpenIddict ASP.NET Core server components.
options.Handlers.AddRange(OpenIddictServerAspNetCoreHandlers.DefaultHandlers);
+
+ // Enable client_secret_basic support by default.
+ options.ClientAuthenticationMethods.Add(ClientAuthenticationMethods.ClientSecretBasic);
}
///
diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Device.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Device.cs
index 5a15c724..fc096e20 100644
--- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Device.cs
+++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Device.cs
@@ -19,6 +19,7 @@ public static partial class OpenIddictServerAspNetCoreHandlers
* Device request extraction:
*/
ExtractPostRequest.Descriptor,
+ ValidateClientAuthenticationMethod.Descriptor,
ExtractBasicAuthenticationCredentials.Descriptor,
/*
diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Exchange.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Exchange.cs
index 88887927..39df0cc8 100644
--- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Exchange.cs
+++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Exchange.cs
@@ -17,6 +17,7 @@ public static partial class OpenIddictServerAspNetCoreHandlers
* Token request extraction:
*/
ExtractPostRequest.Descriptor,
+ ValidateClientAuthenticationMethod.Descriptor,
ExtractBasicAuthenticationCredentials.Descriptor,
/*
diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Introspection.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Introspection.cs
index c380ca74..b6d31198 100644
--- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Introspection.cs
+++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Introspection.cs
@@ -17,6 +17,7 @@ public static partial class OpenIddictServerAspNetCoreHandlers
* Introspection request extraction:
*/
ExtractGetOrPostRequest.Descriptor,
+ ValidateClientAuthenticationMethod.Descriptor,
ExtractBasicAuthenticationCredentials.Descriptor,
/*
diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Revocation.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Revocation.cs
index 58d6aadd..59f03575 100644
--- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Revocation.cs
+++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Revocation.cs
@@ -17,6 +17,7 @@ public static partial class OpenIddictServerAspNetCoreHandlers
* Revocation request extraction:
*/
ExtractPostRequest.Descriptor,
+ ValidateClientAuthenticationMethod.Descriptor,
ExtractBasicAuthenticationCredentials.Descriptor,
/*
diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs
index 4cd0a333..3239a855 100644
--- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs
+++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs
@@ -642,6 +642,75 @@ public static partial class OpenIddictServerAspNetCoreHandlers
}
}
+ ///
+ /// Contains the logic responsible for validating the authentication method used by the client application.
+ /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
+ ///
+ public sealed class ValidateClientAuthenticationMethod : IOpenIddictServerHandler
+ where TContext : BaseValidatingContext
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .AddFilter()
+ .UseSingletonHandler>()
+ .SetOrder(ExtractPostRequest.Descriptor.Order + 1_000)
+ .SetType(OpenIddictServerHandlerType.BuiltIn)
+ .Build();
+
+ ///
+ public ValueTask HandleAsync(TContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
+
+ // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved,
+ // this may indicate that the request was incorrectly processed by another server stack.
+ var request = context.Transaction.GetHttpRequest() ??
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0114));
+
+ // Reject requests that use client_secret_post if support was explicitly disabled in the options.
+ if (!string.IsNullOrEmpty(context.Transaction.Request.ClientSecret) &&
+ !context.Options.ClientAuthenticationMethods.Contains(ClientAuthenticationMethods.ClientSecretPost))
+ {
+ context.Logger.LogInformation(SR.GetResourceString(SR.ID6227), ClientAuthenticationMethods.ClientSecretPost);
+
+ context.Reject(
+ error: Errors.InvalidClient,
+ description: SR.FormatID2174(ClientAuthenticationMethods.ClientSecretPost),
+ uri: SR.FormatID8000(SR.ID2174));
+
+ return default;
+ }
+
+ // Reject requests that use client_secret_basic if support was explicitly disabled in the options.
+ //
+ // Note: the client_secret_jwt authentication method is not supported by OpenIddict out-of-the-box but
+ // is specified here to account for custom implementations that explicitly add client_secret_jwt support.
+ string? header = request.Headers[HeaderNames.Authorization];
+ if (!string.IsNullOrEmpty(header) && header.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase) &&
+ !context.Options.ClientAuthenticationMethods.Contains(ClientAuthenticationMethods.ClientSecretBasic))
+ {
+ context.Logger.LogInformation(SR.GetResourceString(SR.ID6227), ClientAuthenticationMethods.ClientSecretBasic);
+
+ context.Reject(
+ error: Errors.InvalidClient,
+ description: SR.FormatID2174(ClientAuthenticationMethods.ClientSecretBasic),
+ uri: SR.FormatID8000(SR.ID2174));
+
+ return default;
+ }
+
+ return default;
+ }
+ }
+
///
/// Contains the logic responsible for extracting client credentials from the standard HTTP Authorization header.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
@@ -656,7 +725,7 @@ public static partial class OpenIddictServerAspNetCoreHandlers
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.AddFilter()
.UseSingletonHandler>()
- .SetOrder(ExtractPostRequest.Descriptor.Order + 1_000)
+ .SetOrder(ValidateClientAuthenticationMethod.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinConfiguration.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinConfiguration.cs
index c3498696..73a13881 100644
--- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinConfiguration.cs
+++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinConfiguration.cs
@@ -26,6 +26,9 @@ public sealed class OpenIddictServerOwinConfiguration : IConfigureOptions
diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Device.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Device.cs
index 251321b0..112789dd 100644
--- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Device.cs
+++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Device.cs
@@ -19,6 +19,7 @@ public static partial class OpenIddictServerOwinHandlers
* Device request extraction:
*/
ExtractPostRequest.Descriptor,
+ ValidateClientAuthenticationMethod.Descriptor,
ExtractBasicAuthenticationCredentials.Descriptor,
/*
diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Exchange.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Exchange.cs
index 9192c5f0..86ea552d 100644
--- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Exchange.cs
+++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Exchange.cs
@@ -17,6 +17,7 @@ public static partial class OpenIddictServerOwinHandlers
* Token request extraction:
*/
ExtractPostRequest.Descriptor,
+ ValidateClientAuthenticationMethod.Descriptor,
ExtractBasicAuthenticationCredentials.Descriptor,
/*
diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Introspection.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Introspection.cs
index 99dbf53e..a8e63431 100644
--- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Introspection.cs
+++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Introspection.cs
@@ -17,6 +17,7 @@ public static partial class OpenIddictServerOwinHandlers
* Introspection request extraction:
*/
ExtractGetOrPostRequest.Descriptor,
+ ValidateClientAuthenticationMethod.Descriptor,
ExtractBasicAuthenticationCredentials.Descriptor,
/*
diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Revocation.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Revocation.cs
index 8aa04aa3..04facc82 100644
--- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Revocation.cs
+++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Revocation.cs
@@ -17,6 +17,7 @@ public static partial class OpenIddictServerOwinHandlers
* Revocation request extraction:
*/
ExtractPostRequest.Descriptor,
+ ValidateClientAuthenticationMethod.Descriptor,
ExtractBasicAuthenticationCredentials.Descriptor,
/*
diff --git a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs
index 8379b87a..4bb7481e 100644
--- a/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs
+++ b/src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs
@@ -695,6 +695,72 @@ public static partial class OpenIddictServerOwinHandlers
}
}
+ ///
+ /// Contains the logic responsible for validating the authentication method used by the client application.
+ /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
+ ///
+ public sealed class ValidateClientAuthenticationMethod : IOpenIddictServerHandler
+ where TContext : BaseValidatingContext
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .AddFilter()
+ .UseSingletonHandler>()
+ .SetOrder(ExtractPostRequest.Descriptor.Order + 1_000)
+ .SetType(OpenIddictServerHandlerType.BuiltIn)
+ .Build();
+
+ ///
+ public ValueTask HandleAsync(TContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
+
+ // This handler only applies to OWIN requests. If The OWIN request cannot be resolved,
+ // this may indicate that the request was incorrectly processed by another server stack.
+ var request = context.Transaction.GetOwinRequest() ??
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0120));
+
+ // Reject requests that use client_secret_post if support was explicitly disabled in the options.
+ if (!string.IsNullOrEmpty(context.Transaction.Request.ClientSecret) &&
+ !context.Options.ClientAuthenticationMethods.Contains(ClientAuthenticationMethods.ClientSecretPost))
+ {
+ context.Logger.LogInformation(SR.GetResourceString(SR.ID6227), ClientAuthenticationMethods.ClientSecretPost);
+
+ context.Reject(
+ error: Errors.InvalidClient,
+ description: SR.FormatID2174(ClientAuthenticationMethods.ClientSecretPost),
+ uri: SR.FormatID8000(SR.ID2174));
+
+ return default;
+ }
+
+ // Reject requests that use client_secret_basic if support was explicitly disabled in the options.
+ var header = request.Headers[Headers.Authorization];
+ if (!string.IsNullOrEmpty(header) && header.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase) &&
+ !context.Options.ClientAuthenticationMethods.Contains(ClientAuthenticationMethods.ClientSecretBasic))
+ {
+ context.Logger.LogInformation(SR.GetResourceString(SR.ID6227, ClientAuthenticationMethods.ClientSecretBasic));
+
+ context.Reject(
+ error: Errors.InvalidClient,
+ description: SR.FormatID2174(ClientAuthenticationMethods.ClientSecretBasic),
+ uri: SR.FormatID8000(SR.ID2174));
+
+ return default;
+ }
+
+ return default;
+ }
+ }
+
///
/// Contains the logic responsible for extracting client credentials from the standard HTTP Authorization header.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
@@ -709,7 +775,7 @@ public static partial class OpenIddictServerOwinHandlers
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.AddFilter()
.UseSingletonHandler>()
- .SetOrder(ExtractPostRequest.Descriptor.Order + 1_000)
+ .SetOrder(ValidateClientAuthenticationMethod.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
diff --git a/src/OpenIddict.Server/OpenIddictServerConfiguration.cs b/src/OpenIddict.Server/OpenIddictServerConfiguration.cs
index 33fdbc40..639ca36e 100644
--- a/src/OpenIddict.Server/OpenIddictServerConfiguration.cs
+++ b/src/OpenIddict.Server/OpenIddictServerConfiguration.cs
@@ -127,6 +127,30 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions Errors.InvalidClient,
+
+ _ => Errors.InvalidToken
+ },
description: context.ValidTokenTypes.Count switch
{
1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode)
@@ -422,7 +428,13 @@ public static partial class OpenIddictServerHandlers
context.Logger.LogTrace(result.Exception, SR.GetResourceString(SR.ID6000), context.Token);
context.Reject(
- error: Errors.InvalidToken,
+ error: context.ValidTokenTypes.Count switch
+ {
+ 1 when context.ValidTokenTypes.Contains(TokenTypeHints.ClientAssertion)
+ => Errors.InvalidClient,
+
+ _ => Errors.InvalidToken
+ },
description: result.Exception switch
{
SecurityTokenInvalidTypeException => context.ValidTokenTypes.Count switch
@@ -791,7 +803,13 @@ public static partial class OpenIddictServerHandlers
if (context.Principal is null)
{
context.Reject(
- error: Errors.InvalidToken,
+ error: context.ValidTokenTypes.Count switch
+ {
+ 1 when context.ValidTokenTypes.Contains(TokenTypeHints.ClientAssertion)
+ => Errors.InvalidClient,
+
+ _ => Errors.InvalidToken
+ },
description: context.ValidTokenTypes.Count switch
{
1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode)
@@ -874,8 +892,9 @@ public static partial class OpenIddictServerHandlers
context.Reject(
error: context.Principal.GetTokenType() switch
{
- TokenTypeHints.DeviceCode => Errors.ExpiredToken,
- _ => Errors.InvalidToken
+ TokenTypeHints.ClientAssertion => Errors.InvalidClient,
+ TokenTypeHints.DeviceCode => Errors.ExpiredToken,
+ _ => Errors.InvalidToken
},
description: context.Principal.GetTokenType() switch
{
@@ -953,7 +972,12 @@ public static partial class OpenIddictServerHandlers
context.Logger.LogInformation(SR.GetResourceString(SR.ID6002), context.TokenId);
context.Reject(
- error: Errors.InvalidToken,
+ error: context.Principal.GetTokenType() switch
+ {
+ TokenTypeHints.ClientAssertion => Errors.InvalidClient,
+
+ _ => Errors.InvalidToken
+ },
description: context.Principal.GetTokenType() switch
{
TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2010),
@@ -1011,7 +1035,12 @@ public static partial class OpenIddictServerHandlers
context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), context.TokenId);
context.Reject(
- error: Errors.InvalidToken,
+ error: context.Principal.GetTokenType() switch
+ {
+ TokenTypeHints.ClientAssertion => Errors.InvalidClient,
+
+ _ => Errors.InvalidToken
+ },
description: context.Principal.GetTokenType() switch
{
TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2016),
@@ -1123,7 +1152,12 @@ public static partial class OpenIddictServerHandlers
context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), context.AuthorizationId);
context.Reject(
- error: Errors.InvalidToken,
+ error: context.Principal.GetTokenType() switch
+ {
+ TokenTypeHints.ClientAssertion => Errors.InvalidClient,
+
+ _ => Errors.InvalidToken
+ },
description: context.Principal.GetTokenType() switch
{
TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2020),
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
index 618b108a..9753d1f5 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
@@ -361,18 +361,6 @@ public static partial class OpenIddictServerHandlers
return default;
}
- // Ensure the specified client_assertion_type is supported.
- if (!string.IsNullOrEmpty(context.Request.ClientAssertionType) &&
- !string.Equals(context.Request.ClientAssertionType, ClientAssertionTypes.JwtBearer, StringComparison.Ordinal))
- {
- context.Reject(
- error: Errors.InvalidRequest,
- description: SR.FormatID2032(Parameters.ClientAssertionType),
- uri: SR.FormatID8000(SR.ID2032));
-
- return default;
- }
-
// Reject requests that use multiple client authentication methods.
//
// See https://tools.ietf.org/html/rfc6749#section-2.3 for more information.
@@ -389,6 +377,18 @@ public static partial class OpenIddictServerHandlers
return default;
}
+ // Ensure the specified client_assertion_type is supported.
+ if (!string.IsNullOrEmpty(context.Request.ClientAssertionType) &&
+ !context.Options.ClientAssertionTypes.Contains(context.Request.ClientAssertionType))
+ {
+ context.Reject(
+ error: Errors.InvalidClient,
+ description: SR.FormatID2032(Parameters.ClientAssertionType),
+ uri: SR.FormatID8000(SR.ID2032));
+
+ return default;
+ }
+
return default;
}
}
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
index 91facd3e..55a10146 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
@@ -579,7 +579,7 @@ public static partial class OpenIddictServerHandlers
if (context.RejectClientAssertion)
{
context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
+ error: notification.Error ?? Errors.InvalidClient,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
diff --git a/src/OpenIddict.Server/OpenIddictServerOptions.cs b/src/OpenIddict.Server/OpenIddictServerOptions.cs
index 14e92f8d..b13d187e 100644
--- a/src/OpenIddict.Server/OpenIddictServerOptions.cs
+++ b/src/OpenIddict.Server/OpenIddictServerOptions.cs
@@ -305,9 +305,31 @@ public sealed class OpenIddictServerOptions
///
public bool DisableScopeValidation { get; set; }
+ ///
+ /// Gets the OAuth 2.0 client assertion types enabled for this application.
+ ///
+ public HashSet ClientAssertionTypes { get; } = new(StringComparer.Ordinal)
+ {
+ OpenIddictConstants.ClientAssertionTypes.JwtBearer
+ };
+
+ ///
+ /// Gets the OAuth 2.0 client authentication methods enabled for this application.
+ ///
+ public HashSet ClientAuthenticationMethods { get; } = new(StringComparer.Ordinal)
+ {
+ // Note: client_secret_basic is deliberately not added here as it requires
+ // a dedicated event handler (typically provided by the host integration)
+ // to extract the client credentials from the standard Authorization header.
+ //
+ // Both the ASP.NET Core and OWIN hosts support the client_secret_basic
+ // authentication method and automatically add it to this list at runtime.
+ OpenIddictConstants.ClientAuthenticationMethods.ClientSecretPost,
+ OpenIddictConstants.ClientAuthenticationMethods.PrivateKeyJwt
+ };
+
///
/// Gets the OAuth 2.0 code challenge methods enabled for this application.
- /// By default, only the S256 method is allowed (if the code flow is enabled).
///
public HashSet CodeChallengeMethods { get; } = new(StringComparer.Ordinal);
diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Exchange.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Exchange.cs
index 836a01d7..b36b7787 100644
--- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Exchange.cs
+++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Exchange.cs
@@ -15,6 +15,74 @@ namespace OpenIddict.Server.AspNetCore.IntegrationTests;
public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests
{
+ [Fact]
+ public async Task ExtractTokenRequest_ClientSecretFromRequestCausesAnErrorWhenClientSecretPostIsDisabled()
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretPost));
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidClient, response.Error);
+ Assert.Equal(SR.FormatID2174(ClientAuthenticationMethods.ClientSecretPost), response.ErrorDescription);
+ }
+
+ [Fact]
+ public async Task ExtractTokenRequest_ClientSecretFromHeaderCausesAnErrorWhenClientSecretBasicIsDisabled()
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretBasic));
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ var request = context.Transaction.GetHttpRequest()!;
+ request.Headers[HeaderNames.Authorization] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW";
+
+ return default;
+ });
+
+ builder.SetOrder(int.MinValue);
+ });
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidClient, response.Error);
+ Assert.Equal(SR.FormatID2174(ClientAuthenticationMethods.ClientSecretBasic), response.ErrorDescription);
+ }
+
[Fact]
public async Task ExtractTokenRequest_MultipleClientCredentialsCauseAnError()
{
diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Introspection.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Introspection.cs
index 893ac784..5b9d51d7 100644
--- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Introspection.cs
+++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Introspection.cs
@@ -14,6 +14,70 @@ namespace OpenIddict.Server.AspNetCore.IntegrationTests;
public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests
{
+ [Fact]
+ public async Task ExtractIntrospectionRequest_ClientSecretFromRequestCausesAnErrorWhenClientSecretPostIsDisabled()
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretPost));
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
+ Token = "2YotnFZFEjr1zCsicMWpAA"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidClient, response.Error);
+ Assert.Equal(SR.FormatID2174(ClientAuthenticationMethods.ClientSecretPost), response.ErrorDescription);
+ }
+
+ [Fact]
+ public async Task ExtractIntrospectionRequest_ClientSecretFromHeaderCausesAnErrorWhenClientSecretBasicIsDisabled()
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretBasic));
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ var request = context.Transaction.GetHttpRequest()!;
+ request.Headers[HeaderNames.Authorization] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW";
+
+ return default;
+ });
+
+ builder.SetOrder(int.MinValue);
+ });
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ Token = "2YotnFZFEjr1zCsicMWpAA"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidClient, response.Error);
+ Assert.Equal(SR.FormatID2174(ClientAuthenticationMethods.ClientSecretBasic), response.ErrorDescription);
+ }
+
[Fact]
public async Task ExtractIntrospectionRequest_MultipleClientCredentialsCauseAnError()
{
diff --git a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Revocation.cs b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Revocation.cs
index b9c1f5fb..a53bea6a 100644
--- a/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Revocation.cs
+++ b/test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.Revocation.cs
@@ -14,6 +14,70 @@ namespace OpenIddict.Server.AspNetCore.IntegrationTests;
public partial class OpenIddictServerAspNetCoreIntegrationTests : OpenIddictServerIntegrationTests
{
+ [Fact]
+ public async Task ExtractRevocationRequest_ClientSecretFromRequestCausesAnErrorWhenClientSecretPostIsDisabled()
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretPost));
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
+ Token = "2YotnFZFEjr1zCsicMWpAA"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidClient, response.Error);
+ Assert.Equal(SR.FormatID2174(ClientAuthenticationMethods.ClientSecretPost), response.ErrorDescription);
+ }
+
+ [Fact]
+ public async Task ExtractRevocationRequest_ClientSecretFromHeaderCausesAnErrorWhenClientSecretBasicIsDisabled()
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretBasic));
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ var request = context.Transaction.GetHttpRequest()!;
+ request.Headers[HeaderNames.Authorization] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW";
+
+ return default;
+ });
+
+ builder.SetOrder(int.MinValue);
+ });
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ Token = "2YotnFZFEjr1zCsicMWpAA"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidClient, response.Error);
+ Assert.Equal(SR.FormatID2174(ClientAuthenticationMethods.ClientSecretBasic), response.ErrorDescription);
+ }
+
[Fact]
public async Task ExtractRevocationRequest_MultipleClientCredentialsCauseAnError()
{
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Device.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Device.cs
index 3d4b5523..98f63e12 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Device.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Device.cs
@@ -177,19 +177,26 @@ public abstract partial class OpenIddictServerIntegrationTests
public async Task ValidateDeviceRequest_RequestIsRejectedWhenUnsupportedClientAssertionTypeIsSpecified()
{
// Arrange
- await using var server = await CreateServerAsync(options => options.EnableDegradedMode());
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.PrivateKeyJwt));
+ options.Configure(options => options.ClientAssertionTypes.Remove(ClientAssertionTypes.JwtBearer));
+ });
+
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/device", new OpenIddictRequest
{
ClientAssertion = "2YotnFZFEjr1zCsicMWpAA",
- ClientAssertionType = "unknown",
+ ClientAssertionType = ClientAssertionTypes.JwtBearer,
ClientId = "Fabrikam"
});
// Assert
- Assert.Equal(Errors.InvalidRequest, response.Error);
+ Assert.Equal(Errors.InvalidClient, response.Error);
Assert.Equal(SR.FormatID2032(Parameters.ClientAssertionType), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri);
}
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Discovery.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Discovery.cs
index dd24a450..57774301 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Discovery.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Discovery.cs
@@ -409,7 +409,12 @@ public abstract partial class OpenIddictServerIntegrationTests
public async Task HandleConfigurationRequest_SupportedClientAuthenticationMethodsAreIncludedWhenTokenEndpointIsEnabled()
{
// Arrange
- await using var server = await CreateServerAsync();
+ await using var server = await CreateServerAsync(options => options.Configure(options =>
+ {
+ options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretBasic);
+ options.ClientAuthenticationMethods.Add("custom");
+ }));
+
await using var client = await server.CreateClientAsync();
// Act
@@ -418,8 +423,10 @@ public abstract partial class OpenIddictServerIntegrationTests
// Assert
Assert.NotNull(methods);
- Assert.Contains(ClientAuthenticationMethods.ClientSecretBasic, methods);
+ Assert.Equal(3, methods.Length);
Assert.Contains(ClientAuthenticationMethods.ClientSecretPost, methods);
+ Assert.Contains(ClientAuthenticationMethods.PrivateKeyJwt, methods);
+ Assert.Contains("custom", methods);
}
[Fact]
@@ -444,7 +451,12 @@ public abstract partial class OpenIddictServerIntegrationTests
public async Task HandleConfigurationRequest_SupportedClientAuthenticationMethodsAreIncludedWhenIntrospectionEndpointIsEnabled()
{
// Arrange
- await using var server = await CreateServerAsync();
+ await using var server = await CreateServerAsync(options => options.Configure(options =>
+ {
+ options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretBasic);
+ options.ClientAuthenticationMethods.Add("custom");
+ }));
+
await using var client = await server.CreateClientAsync();
// Act
@@ -453,8 +465,10 @@ public abstract partial class OpenIddictServerIntegrationTests
// Assert
Assert.NotNull(methods);
- Assert.Contains(ClientAuthenticationMethods.ClientSecretBasic, methods);
+ Assert.Equal(3, methods.Length);
Assert.Contains(ClientAuthenticationMethods.ClientSecretPost, methods);
+ Assert.Contains(ClientAuthenticationMethods.PrivateKeyJwt, methods);
+ Assert.Contains("custom", methods);
}
[Fact]
@@ -479,7 +493,12 @@ public abstract partial class OpenIddictServerIntegrationTests
public async Task HandleConfigurationRequest_SupportedClientAuthenticationMethodsAreIncludedWhenRevocationEndpointIsEnabled()
{
// Arrange
- await using var server = await CreateServerAsync();
+ await using var server = await CreateServerAsync(options => options.Configure(options =>
+ {
+ options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretBasic);
+ options.ClientAuthenticationMethods.Add("custom");
+ }));
+
await using var client = await server.CreateClientAsync();
// Act
@@ -488,8 +507,10 @@ public abstract partial class OpenIddictServerIntegrationTests
// Assert
Assert.NotNull(methods);
- Assert.Contains(ClientAuthenticationMethods.ClientSecretBasic, methods);
+ Assert.Equal(3, methods.Length);
Assert.Contains(ClientAuthenticationMethods.ClientSecretPost, methods);
+ Assert.Contains(ClientAuthenticationMethods.PrivateKeyJwt, methods);
+ Assert.Contains("custom", methods);
}
[Fact]
@@ -515,7 +536,12 @@ public abstract partial class OpenIddictServerIntegrationTests
public async Task HandleConfigurationRequest_SupportedClientAuthenticationMethodsAreIncludedWhenDeviceEndpointIsEnabled()
{
// Arrange
- await using var server = await CreateServerAsync();
+ await using var server = await CreateServerAsync(options => options.Configure(options =>
+ {
+ options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretBasic);
+ options.ClientAuthenticationMethods.Add("custom");
+ }));
+
await using var client = await server.CreateClientAsync();
// Act
@@ -524,8 +550,10 @@ public abstract partial class OpenIddictServerIntegrationTests
// Assert
Assert.NotNull(methods);
- Assert.Contains(ClientAuthenticationMethods.ClientSecretBasic, methods);
+ Assert.Equal(3, methods.Length);
Assert.Contains(ClientAuthenticationMethods.ClientSecretPost, methods);
+ Assert.Contains(ClientAuthenticationMethods.PrivateKeyJwt, methods);
+ Assert.Contains("custom", methods);
}
[Fact]
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
index f3d2e929..4f7e6db5 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
@@ -1418,20 +1418,27 @@ public abstract partial class OpenIddictServerIntegrationTests
public async Task ValidateTokenRequest_RequestIsRejectedWhenUnsupportedClientAssertionTypeIsSpecified()
{
// Arrange
- await using var server = await CreateServerAsync(options => options.EnableDegradedMode());
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.PrivateKeyJwt));
+ options.Configure(options => options.ClientAssertionTypes.Remove(ClientAssertionTypes.JwtBearer));
+ });
+
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/token", new OpenIddictRequest
{
ClientAssertion = "2YotnFZFEjr1zCsicMWpAA",
- ClientAssertionType = "unknown",
+ ClientAssertionType = ClientAssertionTypes.JwtBearer,
ClientId = "Fabrikam",
GrantType = GrantTypes.ClientCredentials
});
// Assert
- Assert.Equal(Errors.InvalidRequest, response.Error);
+ Assert.Equal(Errors.InvalidClient, response.Error);
Assert.Equal(SR.FormatID2032(Parameters.ClientAssertionType), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri);
}
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs
index 8f47aa50..6875934b 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs
@@ -200,20 +200,27 @@ public abstract partial class OpenIddictServerIntegrationTests
public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenUnsupportedClientAssertionTypeIsSpecified()
{
// Arrange
- await using var server = await CreateServerAsync(options => options.EnableDegradedMode());
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.PrivateKeyJwt));
+ options.Configure(options => options.ClientAssertionTypes.Remove(ClientAssertionTypes.JwtBearer));
+ });
+
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{
ClientAssertion = "2YotnFZFEjr1zCsicMWpAA",
- ClientAssertionType = "unknown",
+ ClientAssertionType = ClientAssertionTypes.JwtBearer,
ClientId = "Fabrikam",
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
- Assert.Equal(Errors.InvalidRequest, response.Error);
+ Assert.Equal(Errors.InvalidClient, response.Error);
Assert.Equal(SR.FormatID2032(Parameters.ClientAssertionType), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri);
}
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs
index aa027893..b87e32db 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs
@@ -199,20 +199,27 @@ public abstract partial class OpenIddictServerIntegrationTests
public async Task ValidateRevocationRequest_RequestIsRejectedWhenUnsupportedClientAssertionTypeIsSpecified()
{
// Arrange
- await using var server = await CreateServerAsync(options => options.EnableDegradedMode());
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.PrivateKeyJwt));
+ options.Configure(options => options.ClientAssertionTypes.Remove(ClientAssertionTypes.JwtBearer));
+ });
+
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
{
ClientAssertion = "2YotnFZFEjr1zCsicMWpAA",
- ClientAssertionType = "unknown",
+ ClientAssertionType = ClientAssertionTypes.JwtBearer,
ClientId = "Fabrikam",
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
- Assert.Equal(Errors.InvalidRequest, response.Error);
+ Assert.Equal(Errors.InvalidClient, response.Error);
Assert.Equal(SR.FormatID2032(Parameters.ClientAssertionType), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2032), response.ErrorUri);
}
diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Exchange.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Exchange.cs
index 7f87161f..9bc2a00d 100644
--- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Exchange.cs
+++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Exchange.cs
@@ -13,6 +13,74 @@ namespace OpenIddict.Server.Owin.IntegrationTests;
public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests
{
+ [Fact]
+ public async Task ExtractTokenRequest_ClientSecretFromRequestCausesAnErrorWhenClientSecretPostIsDisabled()
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretPost));
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidClient, response.Error);
+ Assert.Equal(SR.FormatID2174(ClientAuthenticationMethods.ClientSecretPost), response.ErrorDescription);
+ }
+
+ [Fact]
+ public async Task ExtractTokenRequest_ClientSecretFromHeaderCausesAnErrorWhenClientSecretBasicIsDisabled()
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretBasic));
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ var request = context.Transaction.GetOwinRequest()!;
+ request.Headers["Authorization"] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW";
+
+ return default;
+ });
+
+ builder.SetOrder(int.MinValue);
+ });
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidClient, response.Error);
+ Assert.Equal(SR.FormatID2174(ClientAuthenticationMethods.ClientSecretBasic), response.ErrorDescription);
+ }
+
[Fact]
public async Task ExtractTokenRequest_MultipleClientCredentialsCauseAnError()
{
diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Introspection.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Introspection.cs
index a68e918c..9e3be940 100644
--- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Introspection.cs
+++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Introspection.cs
@@ -13,6 +13,70 @@ namespace OpenIddict.Server.Owin.IntegrationTests;
public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests
{
+ [Fact]
+ public async Task ExtractIntrospectionRequest_ClientSecretFromRequestCausesAnErrorWhenClientSecretPostIsDisabled()
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretPost));
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
+ Token = "2YotnFZFEjr1zCsicMWpAA"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidClient, response.Error);
+ Assert.Equal(SR.FormatID2174(ClientAuthenticationMethods.ClientSecretPost), response.ErrorDescription);
+ }
+
+ [Fact]
+ public async Task ExtractIntrospectionRequest_ClientSecretFromHeaderCausesAnErrorWhenClientSecretBasicIsDisabled()
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretBasic));
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ var request = context.Transaction.GetOwinRequest()!;
+ request.Headers["Authorization"] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW";
+
+ return default;
+ });
+
+ builder.SetOrder(int.MinValue);
+ });
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ Token = "2YotnFZFEjr1zCsicMWpAA"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidClient, response.Error);
+ Assert.Equal(SR.FormatID2174(ClientAuthenticationMethods.ClientSecretBasic), response.ErrorDescription);
+ }
+
[Fact]
public async Task ExtractIntrospectionRequest_MultipleClientCredentialsCauseAnError()
{
diff --git a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Revocation.cs b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Revocation.cs
index 800a7acc..9fef46ab 100644
--- a/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Revocation.cs
+++ b/test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.Revocation.cs
@@ -13,6 +13,70 @@ namespace OpenIddict.Server.Owin.IntegrationTests;
public partial class OpenIddictServerOwinIntegrationTests : OpenIddictServerIntegrationTests
{
+ [Fact]
+ public async Task ExtractRevocationRequest_ClientSecretFromRequestCausesAnErrorWhenClientSecretPostIsDisabled()
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretPost));
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
+ Token = "2YotnFZFEjr1zCsicMWpAA"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidClient, response.Error);
+ Assert.Equal(SR.FormatID2174(ClientAuthenticationMethods.ClientSecretPost), response.ErrorDescription);
+ }
+
+ [Fact]
+ public async Task ExtractRevocationRequest_ClientSecretFromHeaderCausesAnErrorWhenClientSecretBasicIsDisabled()
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.Configure(options => options.ClientAuthenticationMethods.Remove(ClientAuthenticationMethods.ClientSecretBasic));
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ var request = context.Transaction.GetOwinRequest()!;
+ request.Headers["Authorization"] = "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW";
+
+ return default;
+ });
+
+ builder.SetOrder(int.MinValue);
+ });
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ Token = "2YotnFZFEjr1zCsicMWpAA"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidClient, response.Error);
+ Assert.Equal(SR.FormatID2174(ClientAuthenticationMethods.ClientSecretBasic), response.ErrorDescription);
+ }
+
[Fact]
public async Task ExtractRevocationRequest_MultipleClientCredentialsCauseAnError()
{