diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
index 33daed0c..b646f391 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
@@ -54,8 +54,8 @@ namespace OpenIddict.Server
ValidateScopes.Descriptor,
ValidateEndpointPermissions.Descriptor,
ValidateGrantTypePermissions.Descriptor,
- ValidateProofKeyForCodeExchangeRequirement.Descriptor,
ValidateScopePermissions.Descriptor,
+ ValidateProofKeyForCodeExchangeRequirement.Descriptor,
/*
* Authorization response processing:
@@ -1469,15 +1469,14 @@ namespace OpenIddict.Server
}
///
- /// Contains the logic responsible of rejecting authorization requests made by
- /// applications for which proof key for code exchange (PKCE) was enforced.
- /// Note: this handler is not used when the degraded mode is enabled.
+ /// Contains the logic responsible of rejecting authorization requests made by unauthorized applications.
+ /// Note: this handler is not used when the degraded mode is enabled or when scope permissions are disabled.
///
- public class ValidateProofKeyForCodeExchangeRequirement : IOpenIddictServerHandler
+ public class ValidateScopePermissions : IOpenIddictServerHandler
{
private readonly IOpenIddictApplicationManager _applicationManager;
- public ValidateProofKeyForCodeExchangeRequirement() => throw new InvalidOperationException(new StringBuilder()
+ public ValidateScopePermissions() => throw new InvalidOperationException(new StringBuilder()
.AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
@@ -1485,7 +1484,7 @@ namespace OpenIddict.Server
.Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
.ToString());
- public ValidateProofKeyForCodeExchangeRequirement([NotNull] IOpenIddictApplicationManager applicationManager)
+ public ValidateScopePermissions([NotNull] IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
///
@@ -1493,8 +1492,9 @@ namespace OpenIddict.Server
///
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .AddFilter()
.AddFilter()
- .UseScopedHandler()
+ .UseScopedHandler()
.SetOrder(ValidateGrantTypePermissions.Descriptor.Order + 1_000)
.Build();
@@ -1512,42 +1512,47 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context));
}
- // If a code_challenge was provided, the request is always considered valid,
- // whether the proof key for code exchange requirement is enforced or not.
- if (!string.IsNullOrEmpty(context.Request.CodeChallenge))
- {
- return;
- }
-
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application == null)
{
throw new InvalidOperationException("The client application details cannot be found in the database.");
}
- if (await _applicationManager.HasRequirementAsync(application, Requirements.Features.ProofKeyForCodeExchange))
+ foreach (var scope in context.Request.GetScopes())
{
- context.Logger.LogError("The authorization request was rejected because the " +
- "required 'code_challenge' parameter was missing.");
+ // Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes.
+ if (string.Equals(scope, Scopes.OfflineAccess, StringComparison.Ordinal) ||
+ string.Equals(scope, Scopes.OpenId, StringComparison.Ordinal))
+ {
+ continue;
+ }
- context.Reject(
- error: Errors.InvalidRequest,
- description: "The mandatory 'code_challenge' parameter is missing.");
+ // Reject the request if the application is not allowed to use the iterated scope.
+ if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.Scope + scope))
+ {
+ context.Logger.LogError("The authorization request was rejected because the application '{ClientId}' " +
+ "was not allowed to use the scope {Scope}.", context.ClientId, scope);
- return;
+ context.Reject(
+ error: Errors.InvalidRequest,
+ description: "This client application is not allowed to use the specified scope.");
+
+ return;
+ }
}
}
}
///
- /// Contains the logic responsible of rejecting authorization requests made by unauthorized applications.
- /// Note: this handler is not used when the degraded mode is enabled or when scope permissions are disabled.
+ /// Contains the logic responsible of rejecting authorization requests made by
+ /// applications for which proof key for code exchange (PKCE) was enforced.
+ /// Note: this handler is not used when the degraded mode is enabled.
///
- public class ValidateScopePermissions : IOpenIddictServerHandler
+ public class ValidateProofKeyForCodeExchangeRequirement : IOpenIddictServerHandler
{
private readonly IOpenIddictApplicationManager _applicationManager;
- public ValidateScopePermissions() => throw new InvalidOperationException(new StringBuilder()
+ public ValidateProofKeyForCodeExchangeRequirement() => throw new InvalidOperationException(new StringBuilder()
.AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
@@ -1555,7 +1560,7 @@ namespace OpenIddict.Server
.Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
.ToString());
- public ValidateScopePermissions([NotNull] IOpenIddictApplicationManager applicationManager)
+ public ValidateProofKeyForCodeExchangeRequirement([NotNull] IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
///
@@ -1563,10 +1568,9 @@ namespace OpenIddict.Server
///
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
- .AddFilter()
.AddFilter()
- .UseScopedHandler()
- .SetOrder(ValidateProofKeyForCodeExchangeRequirement.Descriptor.Order + 1_000)
+ .UseScopedHandler()
+ .SetOrder(ValidateScopePermissions.Descriptor.Order + 1_000)
.Build();
///
@@ -1583,33 +1587,29 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context));
}
+ // If a code_challenge was provided, the request is always considered valid,
+ // whether the proof key for code exchange requirement is enforced or not.
+ if (!string.IsNullOrEmpty(context.Request.CodeChallenge))
+ {
+ return;
+ }
+
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application == null)
{
throw new InvalidOperationException("The client application details cannot be found in the database.");
}
- foreach (var scope in context.Request.GetScopes())
+ if (await _applicationManager.HasRequirementAsync(application, Requirements.Features.ProofKeyForCodeExchange))
{
- // Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes.
- if (string.Equals(scope, Scopes.OfflineAccess, StringComparison.Ordinal) ||
- string.Equals(scope, Scopes.OpenId, StringComparison.Ordinal))
- {
- continue;
- }
-
- // Reject the request if the application is not allowed to use the iterated scope.
- if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.Scope + scope))
- {
- context.Logger.LogError("The authorization request was rejected because the application '{ClientId}' " +
- "was not allowed to use the scope {Scope}.", context.ClientId, scope);
+ context.Logger.LogError("The authorization request was rejected because the " +
+ "required 'code_challenge' parameter was missing.");
- context.Reject(
- error: Errors.InvalidRequest,
- description: "This client application is not allowed to use the specified scope.");
+ context.Reject(
+ error: Errors.InvalidRequest,
+ description: "The mandatory 'code_challenge' parameter is missing.");
- return;
- }
+ return;
}
}
}
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
index e9040dad..dc61e414 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
@@ -54,8 +54,8 @@ namespace OpenIddict.Server
ValidateClientSecret.Descriptor,
ValidateEndpointPermissions.Descriptor,
ValidateGrantTypePermissions.Descriptor,
- ValidateProofKeyForCodeExchangeRequirement.Descriptor,
ValidateScopePermissions.Descriptor,
+ ValidateProofKeyForCodeExchangeRequirement.Descriptor,
ValidateToken.Descriptor,
ValidatePresenters.Descriptor,
ValidateRedirectUri.Descriptor,
@@ -1171,15 +1171,15 @@ namespace OpenIddict.Server
}
///
- /// Contains the logic responsible of rejecting token requests made by
- /// applications for which proof key for code exchange (PKCE) was enforced.
+ /// Contains the logic responsible of rejecting token requests made by applications
+ /// that haven't been granted the appropriate grant type permission.
/// Note: this handler is not used when the degraded mode is enabled.
///
- public class ValidateProofKeyForCodeExchangeRequirement : IOpenIddictServerHandler
+ public class ValidateScopePermissions : IOpenIddictServerHandler
{
private readonly IOpenIddictApplicationManager _applicationManager;
- public ValidateProofKeyForCodeExchangeRequirement() => throw new InvalidOperationException(new StringBuilder()
+ public ValidateScopePermissions() => throw new InvalidOperationException(new StringBuilder()
.AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
@@ -1187,7 +1187,7 @@ namespace OpenIddict.Server
.Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
.ToString());
- public ValidateProofKeyForCodeExchangeRequirement([NotNull] IOpenIddictApplicationManager applicationManager)
+ public ValidateScopePermissions([NotNull] IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
///
@@ -1197,7 +1197,8 @@ namespace OpenIddict.Server
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.AddFilter()
.AddFilter()
- .UseScopedHandler()
+ .AddFilter()
+ .UseScopedHandler()
.SetOrder(ValidateGrantTypePermissions.Descriptor.Order + 1_000)
.Build();
@@ -1215,48 +1216,47 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context));
}
- if (!context.Request.IsAuthorizationCodeGrantType())
- {
- return;
- }
-
- // If a code_verifier was provided, the request is always considered valid,
- // whether the proof key for code exchange requirement is enforced or not.
- if (!string.IsNullOrEmpty(context.Request.CodeVerifier))
- {
- return;
- }
-
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application == null)
{
throw new InvalidOperationException("The client application details cannot be found in the database.");
}
- if (await _applicationManager.HasRequirementAsync(application, Requirements.Features.ProofKeyForCodeExchange))
+ foreach (var scope in context.Request.GetScopes())
{
- context.Logger.LogError("The token request was rejected because the " +
- "required 'code_verifier' parameter was missing.");
+ // Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes.
+ if (string.Equals(scope, Scopes.OfflineAccess, StringComparison.Ordinal) ||
+ string.Equals(scope, Scopes.OpenId, StringComparison.Ordinal))
+ {
+ continue;
+ }
- context.Reject(
- error: Errors.InvalidRequest,
- description: "The mandatory 'code_verifier' parameter is missing.");
+ // Reject the request if the application is not allowed to use the iterated scope.
+ if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.Scope + scope))
+ {
+ context.Logger.LogError("The token request was rejected because the application '{ClientId}' " +
+ "was not allowed to use the scope {Scope}.", context.ClientId, scope);
- return;
+ context.Reject(
+ error: Errors.InvalidRequest,
+ description: "This client application is not allowed to use the specified scope.");
+
+ return;
+ }
}
}
}
///
- /// Contains the logic responsible of rejecting token requests made by applications
- /// that haven't been granted the appropriate grant type permission.
+ /// Contains the logic responsible of rejecting token requests made by
+ /// applications for which proof key for code exchange (PKCE) was enforced.
/// Note: this handler is not used when the degraded mode is enabled.
///
- public class ValidateScopePermissions : IOpenIddictServerHandler
+ public class ValidateProofKeyForCodeExchangeRequirement : IOpenIddictServerHandler
{
private readonly IOpenIddictApplicationManager _applicationManager;
- public ValidateScopePermissions() => throw new InvalidOperationException(new StringBuilder()
+ public ValidateProofKeyForCodeExchangeRequirement() => throw new InvalidOperationException(new StringBuilder()
.AppendLine("The core services must be registered when enabling the OpenIddict server feature.")
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
@@ -1264,7 +1264,7 @@ namespace OpenIddict.Server
.Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.")
.ToString());
- public ValidateScopePermissions([NotNull] IOpenIddictApplicationManager applicationManager)
+ public ValidateProofKeyForCodeExchangeRequirement([NotNull] IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
///
@@ -1274,9 +1274,8 @@ namespace OpenIddict.Server
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.AddFilter()
.AddFilter()
- .AddFilter()
- .UseScopedHandler()
- .SetOrder(ValidateProofKeyForCodeExchangeRequirement.Descriptor.Order + 1_000)
+ .UseScopedHandler()
+ .SetOrder(ValidateScopePermissions.Descriptor.Order + 1_000)
.Build();
///
@@ -1293,33 +1292,34 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context));
}
+ if (!context.Request.IsAuthorizationCodeGrantType())
+ {
+ return;
+ }
+
+ // If a code_verifier was provided, the request is always considered valid,
+ // whether the proof key for code exchange requirement is enforced or not.
+ if (!string.IsNullOrEmpty(context.Request.CodeVerifier))
+ {
+ return;
+ }
+
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application == null)
{
throw new InvalidOperationException("The client application details cannot be found in the database.");
}
- foreach (var scope in context.Request.GetScopes())
+ if (await _applicationManager.HasRequirementAsync(application, Requirements.Features.ProofKeyForCodeExchange))
{
- // Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes.
- if (string.Equals(scope, Scopes.OfflineAccess, StringComparison.Ordinal) ||
- string.Equals(scope, Scopes.OpenId, StringComparison.Ordinal))
- {
- continue;
- }
-
- // Reject the request if the application is not allowed to use the iterated scope.
- if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.Scope + scope))
- {
- context.Logger.LogError("The token request was rejected because the application '{ClientId}' " +
- "was not allowed to use the scope {Scope}.", context.ClientId, scope);
+ context.Logger.LogError("The token request was rejected because the " +
+ "required 'code_verifier' parameter was missing.");
- context.Reject(
- error: Errors.InvalidRequest,
- description: "This client application is not allowed to use the specified scope.");
+ context.Reject(
+ error: Errors.InvalidRequest,
+ description: "The mandatory 'code_verifier' parameter is missing.");
- return;
- }
+ return;
}
}
}
@@ -1341,7 +1341,7 @@ namespace OpenIddict.Server
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.UseScopedHandler()
- .SetOrder(ValidateScopePermissions.Descriptor.Order + 1_000)
+ .SetOrder(ValidateProofKeyForCodeExchangeRequirement.Descriptor.Order + 1_000)
.Build();
///
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs
index da8100ee..a4b883f9 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs
@@ -1283,6 +1283,151 @@ namespace OpenIddict.Server.FunctionalTests
Permissions.Prefixes.Scope + Scopes.Email, It.IsAny()), Times.Once());
}
+ [Fact]
+ public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenCodeChallengeIsMissingWithPkceFeatureEnforced()
+ {
+ // Arrange
+ var application = new OpenIddictApplication();
+
+ var manager = CreateApplicationManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
+ .ReturnsAsync(application);
+
+ mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.HasRequirementAsync(application,
+ Requirements.Features.ProofKeyForCodeExchange, It.IsAny()))
+ .ReturnsAsync(true);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.Services.AddSingleton(manager);
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ CodeChallenge = null,
+ CodeChallengeMethod = null,
+ RedirectUri = "http://www.fabrikam.com/path",
+ ResponseType = ResponseTypes.Code
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidRequest, response.Error);
+ Assert.Equal("The mandatory 'code_challenge' parameter is missing.", response.ErrorDescription);
+
+ Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce());
+ Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application,
+ Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenCodeChallengeIsMissingWithPkceFeatureNotEnforced()
+ {
+ // Arrange
+ var application = new OpenIddictApplication();
+
+ var manager = CreateApplicationManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
+ .ReturnsAsync(application);
+
+ mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.HasRequirementAsync(application,
+ Requirements.Features.ProofKeyForCodeExchange, It.IsAny()))
+ .ReturnsAsync(false);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.Services.AddSingleton(manager);
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetClaim(Claims.Subject, "Bob le Magnifique");
+
+ return default;
+ }));
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ CodeChallenge = null,
+ CodeChallengeMethod = null,
+ RedirectUri = "http://www.fabrikam.com/path",
+ ResponseType = ResponseTypes.Code
+ });
+
+ // Assert
+ Assert.NotNull(response.Code);
+
+ Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce());
+ Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application,
+ Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ValidateAuthorizationRequest_RequestIsValidatedWhenCodeChallengeIsPresentWithPkceFeatureEnforced()
+ {
+ // Arrange
+ var application = new OpenIddictApplication();
+
+ var manager = CreateApplicationManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
+ .ReturnsAsync(application);
+
+ mock.Setup(manager => manager.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.HasRequirementAsync(application,
+ Requirements.Features.ProofKeyForCodeExchange, It.IsAny()))
+ .ReturnsAsync(true);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.Services.AddSingleton(manager);
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetClaim(Claims.Subject, "Bob le Magnifique");
+
+ return default;
+ }));
+ });
+
+ // Act
+ var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
+ CodeChallengeMethod = CodeChallengeMethods.Sha256,
+ RedirectUri = "http://www.fabrikam.com/path",
+ ResponseType = ResponseTypes.Code
+ });
+
+ // Assert
+ Assert.NotNull(response.Code);
+
+ Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce());
+ Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application,
+ Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Never());
+ }
+
[Theory]
[InlineData("custom_error", null, null)]
[InlineData("custom_error", "custom_description", null)]
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
index 51620484..30935582 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
@@ -1192,7 +1192,7 @@ namespace OpenIddict.Server.FunctionalTests
}
[Fact]
- public async Task ValidateTokenRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted()
+ public async Task ValidateTokenRequest_ClientCredentialsRequestFromPublicClientIsRejected()
{
// Arrange
var application = new OpenIddictApplication();
@@ -1202,38 +1202,71 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- mock.Setup(manager => manager.HasPermissionAsync(application,
- Permissions.Endpoints.Token, It.IsAny()))
- .ReturnsAsync(false);
+ mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
+ .ReturnsAsync(ClientTypes.Public);
});
var client = CreateClient(options =>
{
options.Services.AddSingleton(manager);
+ });
- options.Configure(options => options.IgnoreEndpointPermissions = false);
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
+ GrantType = GrantTypes.ClientCredentials
+ });
+
+ // Assert
+ Assert.Equal(Errors.UnauthorizedClient, response.Error);
+ Assert.Equal("The specified 'grant_type' parameter is not valid for this client application.", response.ErrorDescription);
+
+ Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce());
+ Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ValidateTokenRequest_ClientSecretCannotBeUsedByPublicClients()
+ {
+ // Arrange
+ var application = new OpenIddictApplication();
+
+ var manager = CreateApplicationManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
+ .ReturnsAsync(application);
+
+ mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
+ .ReturnsAsync(ClientTypes.Public);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.Services.AddSingleton(manager);
});
// 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.UnauthorizedClient, response.Error);
- Assert.Equal("This client application is not allowed to use the token endpoint.", response.ErrorDescription);
+ Assert.Equal(Errors.InvalidRequest, response.Error);
+ Assert.Equal("The 'client_secret' parameter is not valid for this client application.", response.ErrorDescription);
Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce());
- Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application,
- Permissions.Endpoints.Token, It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once());
}
[Fact]
- public async Task ValidateTokenRequest_RequestIsRejectedWhenGrantTypePermissionIsNotGranted()
+ public async Task ValidateTokenRequest_ClientSecretIsRequiredForConfidentialClients()
{
// Arrange
var application = new OpenIddictApplication();
@@ -1243,38 +1276,73 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- mock.Setup(manager => manager.HasPermissionAsync(application,
- Permissions.GrantTypes.Password, It.IsAny()))
- .ReturnsAsync(false);
+ mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
+ .ReturnsAsync(ClientTypes.Confidential);
});
var client = CreateClient(options =>
{
options.Services.AddSingleton(manager);
+ });
- options.Configure(options => options.IgnoreGrantTypePermissions = false);
+ // Act
+ var response = await client.PostAsync("/connect/token", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ ClientSecret = null,
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
+ });
+
+ // Assert
+ Assert.Equal(Errors.InvalidClient, response.Error);
+ Assert.Equal("The 'client_secret' parameter required for this client application is missing.", response.ErrorDescription);
+
+ Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce());
+ Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ValidateTokenRequest_ClientSecretIsRequiredForHybridClients()
+ {
+ // Arrange
+ var application = new OpenIddictApplication();
+
+ var manager = CreateApplicationManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
+ .ReturnsAsync(application);
+
+ mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
+ .ReturnsAsync(ClientTypes.Hybrid);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.Services.AddSingleton(manager);
});
// Act
var response = await client.PostAsync("/connect/token", new OpenIddictRequest
{
ClientId = "Fabrikam",
+ ClientSecret = null,
GrantType = GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
- Assert.Equal(Errors.UnauthorizedClient, response.Error);
- Assert.Equal("This client application is not allowed to use the specified grant type.", response.ErrorDescription);
+ Assert.Equal(Errors.InvalidClient, response.Error);
+ Assert.Equal("The 'client_secret' parameter required for this client application is missing.", response.ErrorDescription);
Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce());
- Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application,
- Permissions.GrantTypes.Password, It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once());
}
[Fact]
- public async Task ValidateTokenRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenPermissionIsNotGranted()
+ public async Task ValidateTokenRequest_RequestIsRejectedWhenClientCredentialsAreInvalid()
{
// Arrange
var application = new OpenIddictApplication();
@@ -1284,12 +1352,50 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- mock.Setup(manager => manager.HasPermissionAsync(application,
- Permissions.GrantTypes.Password, It.IsAny()))
- .ReturnsAsync(true);
+ mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
+ .ReturnsAsync(ClientTypes.Confidential);
+
+ mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()))
+ .ReturnsAsync(false);
+ });
+
+ var client = CreateClient(options =>
+ {
+ options.Services.AddSingleton(manager);
+ });
+
+ // 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("The specified client credentials are invalid.", response.ErrorDescription);
+
+ Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce());
+ Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.AtLeastOnce());
+ Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once());
+ }
+
+ [Fact]
+ public async Task ValidateTokenRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted()
+ {
+ // Arrange
+ var application = new OpenIddictApplication();
+
+ var manager = CreateApplicationManager(mock =>
+ {
+ mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
+ .ReturnsAsync(application);
mock.Setup(manager => manager.HasPermissionAsync(application,
- Permissions.GrantTypes.RefreshToken, It.IsAny()))
+ Permissions.Endpoints.Token, It.IsAny()))
.ReturnsAsync(false);
});
@@ -1297,7 +1403,7 @@ namespace OpenIddict.Server.FunctionalTests
{
options.Services.AddSingleton(manager);
- options.Configure(options => options.IgnoreGrantTypePermissions = false);
+ options.Configure(options => options.IgnoreEndpointPermissions = false);
});
// Act
@@ -1306,20 +1412,20 @@ namespace OpenIddict.Server.FunctionalTests
ClientId = "Fabrikam",
GrantType = GrantTypes.Password,
Username = "johndoe",
- Password = "A3ddj3w",
- Scope = Scopes.OfflineAccess
+ Password = "A3ddj3w"
});
// Assert
- Assert.Equal(Errors.InvalidRequest, response.Error);
- Assert.Equal("The client application is not allowed to use the 'offline_access' scope.", response.ErrorDescription);
+ Assert.Equal(Errors.UnauthorizedClient, response.Error);
+ Assert.Equal("This client application is not allowed to use the token endpoint.", response.ErrorDescription);
+ Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce());
Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application,
- Permissions.GrantTypes.RefreshToken, It.IsAny()), Times.Once());
+ Permissions.Endpoints.Token, It.IsAny()), Times.Once());
}
[Fact]
- public async Task ValidateTokenRequest_ClientCredentialsRequestFromPublicClientIsRejected()
+ public async Task ValidateTokenRequest_RequestIsRejectedWhenGrantTypePermissionIsNotGranted()
{
// Arrange
var application = new OpenIddictApplication();
@@ -1329,33 +1435,38 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
- .ReturnsAsync(ClientTypes.Public);
+ mock.Setup(manager => manager.HasPermissionAsync(application,
+ Permissions.GrantTypes.Password, It.IsAny()))
+ .ReturnsAsync(false);
});
var client = CreateClient(options =>
{
options.Services.AddSingleton(manager);
+
+ options.Configure(options => options.IgnoreGrantTypePermissions = false);
});
// Act
var response = await client.PostAsync("/connect/token", new OpenIddictRequest
{
ClientId = "Fabrikam",
- ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
- GrantType = GrantTypes.ClientCredentials
+ GrantType = GrantTypes.Password,
+ Username = "johndoe",
+ Password = "A3ddj3w"
});
// Assert
Assert.Equal(Errors.UnauthorizedClient, response.Error);
- Assert.Equal("The specified 'grant_type' parameter is not valid for this client application.", response.ErrorDescription);
+ Assert.Equal("This client application is not allowed to use the specified grant type.", response.ErrorDescription);
Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce());
- Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application,
+ Permissions.GrantTypes.Password, It.IsAny()), Times.Once());
}
[Fact]
- public async Task ValidateTokenRequest_RequestIsRejectedWhenScopePermissionIsNotGranted()
+ public async Task ValidateTokenRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenPermissionIsNotGranted()
{
// Arrange
var application = new OpenIddictApplication();
@@ -1365,15 +1476,12 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()))
.ReturnsAsync(application);
- mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
- .ReturnsAsync(ClientTypes.Public);
-
mock.Setup(manager => manager.HasPermissionAsync(application,
- Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny()))
+ Permissions.GrantTypes.Password, It.IsAny()))
.ReturnsAsync(true);
mock.Setup(manager => manager.HasPermissionAsync(application,
- Permissions.Prefixes.Scope + Scopes.Email, It.IsAny()))
+ Permissions.GrantTypes.RefreshToken, It.IsAny()))
.ReturnsAsync(false);
});
@@ -1381,8 +1489,7 @@ namespace OpenIddict.Server.FunctionalTests
{
options.Services.AddSingleton(manager);
- options.RegisterScopes(Scopes.Email, Scopes.Profile);
- options.Configure(options => options.IgnoreScopePermissions = false);
+ options.Configure(options => options.IgnoreGrantTypePermissions = false);
});
// Act
@@ -1392,25 +1499,19 @@ namespace OpenIddict.Server.FunctionalTests
GrantType = GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w",
- Scope = "openid offline_access profile email"
+ Scope = Scopes.OfflineAccess
});
// Assert
Assert.Equal(Errors.InvalidRequest, response.Error);
- Assert.Equal("This client application is not allowed to use the specified scope.", response.ErrorDescription);
+ Assert.Equal("The client application is not allowed to use the 'offline_access' scope.", response.ErrorDescription);
Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application,
- Permissions.Prefixes.Scope + Scopes.OpenId, It.IsAny()), Times.Never());
- Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application,
- Permissions.Prefixes.Scope + Scopes.OfflineAccess, It.IsAny()), Times.Never());
- Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application,
- Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny()), Times.Once());
- Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application,
- Permissions.Prefixes.Scope + Scopes.Email, It.IsAny()), Times.Once());
+ Permissions.GrantTypes.RefreshToken, It.IsAny()), Times.Once());
}
[Fact]
- public async Task ValidateTokenRequest_ClientSecretCannotBeUsedByPublicClients()
+ public async Task ValidateTokenRequest_RequestIsRejectedWhenScopePermissionIsNotGranted()
{
// Arrange
var application = new OpenIddictApplication();
@@ -1422,33 +1523,50 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
.ReturnsAsync(ClientTypes.Public);
+
+ mock.Setup(manager => manager.HasPermissionAsync(application,
+ Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny()))
+ .ReturnsAsync(true);
+
+ mock.Setup(manager => manager.HasPermissionAsync(application,
+ Permissions.Prefixes.Scope + Scopes.Email, It.IsAny()))
+ .ReturnsAsync(false);
});
var client = CreateClient(options =>
{
options.Services.AddSingleton(manager);
+
+ options.RegisterScopes(Scopes.Email, Scopes.Profile);
+ options.Configure(options => options.IgnoreScopePermissions = false);
});
// Act
var response = await client.PostAsync("/connect/token", new OpenIddictRequest
{
ClientId = "Fabrikam",
- ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
GrantType = GrantTypes.Password,
Username = "johndoe",
- Password = "A3ddj3w"
+ Password = "A3ddj3w",
+ Scope = "openid offline_access profile email"
});
// Assert
Assert.Equal(Errors.InvalidRequest, response.Error);
- Assert.Equal("The 'client_secret' parameter is not valid for this client application.", response.ErrorDescription);
+ Assert.Equal("This client application is not allowed to use the specified scope.", response.ErrorDescription);
- Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce());
- Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application,
+ Permissions.Prefixes.Scope + Scopes.OpenId, It.IsAny()), Times.Never());
+ Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application,
+ Permissions.Prefixes.Scope + Scopes.OfflineAccess, It.IsAny()), Times.Never());
+ Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application,
+ Permissions.Prefixes.Scope + Scopes.Profile, It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application,
+ Permissions.Prefixes.Scope + Scopes.Email, It.IsAny()), Times.Once());
}
[Fact]
- public async Task ValidateTokenRequest_ClientSecretIsRequiredForConfidentialClients()
+ public async Task ValidateTokenRequest_RequestIsRejectedWhenCodeVerifierIsMissingWithPkceFeatureEnforced()
{
// Arrange
var application = new OpenIddictApplication();
@@ -1459,7 +1577,11 @@ namespace OpenIddict.Server.FunctionalTests
.ReturnsAsync(application);
mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
- .ReturnsAsync(ClientTypes.Confidential);
+ .ReturnsAsync(ClientTypes.Public);
+
+ mock.Setup(manager => manager.HasRequirementAsync(application,
+ Requirements.Features.ProofKeyForCodeExchange, It.IsAny()))
+ .ReturnsAsync(true);
});
var client = CreateClient(options =>
@@ -1471,22 +1593,22 @@ namespace OpenIddict.Server.FunctionalTests
var response = await client.PostAsync("/connect/token", new OpenIddictRequest
{
ClientId = "Fabrikam",
- ClientSecret = null,
- GrantType = GrantTypes.Password,
- Username = "johndoe",
- Password = "A3ddj3w"
+ Code = "SplxlOBeZQQYbYS6WxSbIA",
+ CodeVerifier = null,
+ GrantType = GrantTypes.AuthorizationCode,
+ RedirectUri = "http://www.fabrikam.com/path"
});
// Assert
- Assert.Equal(Errors.InvalidClient, response.Error);
- Assert.Equal("The 'client_secret' parameter required for this client application is missing.", response.ErrorDescription);
+ Assert.Equal(Errors.InvalidRequest, response.Error);
+ Assert.Equal("The mandatory 'code_verifier' parameter is missing.", response.ErrorDescription);
- Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce());
- Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application,
+ Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Once());
}
[Fact]
- public async Task ValidateTokenRequest_ClientSecretIsRequiredForHybridClients()
+ public async Task ValidateTokenRequest_RequestIsValidatedWhenCodeVerifierIsMissingWithPkceFeatureNotEnforced()
{
// Arrange
var application = new OpenIddictApplication();
@@ -1497,34 +1619,59 @@ namespace OpenIddict.Server.FunctionalTests
.ReturnsAsync(application);
mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
- .ReturnsAsync(ClientTypes.Hybrid);
+ .ReturnsAsync(ClientTypes.Public);
+
+ mock.Setup(manager => manager.HasRequirementAsync(application,
+ Requirements.Features.ProofKeyForCodeExchange, It.IsAny()))
+ .ReturnsAsync(false);
});
var client = CreateClient(options =>
{
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
+ Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetPresenters("Fabrikam")
+ .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur");
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
options.Services.AddSingleton(manager);
+
+ options.SetRevocationEndpointUris(Array.Empty());
+ options.DisableTokenStorage();
+ options.DisableSlidingExpiration();
});
// Act
var response = await client.PostAsync("/connect/token", new OpenIddictRequest
{
ClientId = "Fabrikam",
- ClientSecret = null,
- GrantType = GrantTypes.Password,
- Username = "johndoe",
- Password = "A3ddj3w"
+ Code = "SplxlOBeZQQYbYS6WxSbIA",
+ CodeVerifier = null,
+ GrantType = GrantTypes.AuthorizationCode,
+ RedirectUri = "http://www.fabrikam.com/path"
});
// Assert
- Assert.Equal(Errors.InvalidClient, response.Error);
- Assert.Equal("The 'client_secret' parameter required for this client application is missing.", response.ErrorDescription);
+ Assert.NotNull(response.AccessToken);
- Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce());
- Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application,
+ Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Once());
}
[Fact]
- public async Task ValidateTokenRequest_RequestIsRejectedWhenClientCredentialsAreInvalid()
+ public async Task ValidateTokenRequest_RequestIsValidatedWhenCodeVerifierIsPresentWithPkceFeatureEnforced()
{
// Arrange
var application = new OpenIddictApplication();
@@ -1535,34 +1682,57 @@ namespace OpenIddict.Server.FunctionalTests
.ReturnsAsync(application);
mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny()))
- .ReturnsAsync(ClientTypes.Confidential);
+ .ReturnsAsync(ClientTypes.Public);
- mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()))
+ mock.Setup(manager => manager.HasRequirementAsync(application,
+ Requirements.Features.ProofKeyForCodeExchange, It.IsAny()))
.ReturnsAsync(false);
});
var client = CreateClient(options =>
{
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
+ Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetPresenters("Fabrikam")
+ .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
+ .SetClaim(Claims.Subject, "Bob le Bricoleur")
+ .SetClaim(Claims.Private.CodeChallenge, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM")
+ .SetClaim(Claims.Private.CodeChallengeMethod, CodeChallengeMethods.Sha256);
+
+ return default;
+ });
+
+ builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
+ });
+
options.Services.AddSingleton(manager);
+
+ options.SetRevocationEndpointUris(Array.Empty());
+ options.DisableTokenStorage();
+ options.DisableSlidingExpiration();
});
// Act
var response = await client.PostAsync("/connect/token", new OpenIddictRequest
{
ClientId = "Fabrikam",
- ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
- GrantType = GrantTypes.Password,
- Username = "johndoe",
- Password = "A3ddj3w"
+ Code = "SplxlOBeZQQYbYS6WxSbIA",
+ CodeVerifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
+ GrantType = GrantTypes.AuthorizationCode,
+ RedirectUri = "http://www.fabrikam.com/path"
});
// Assert
- Assert.Equal(Errors.InvalidClient, response.Error);
- Assert.Equal("The specified client credentials are invalid.", response.ErrorDescription);
+ Assert.NotNull(response.AccessToken);
- Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce());
- Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.AtLeastOnce());
- Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once());
+ Mock.Get(manager).Verify(manager => manager.HasRequirementAsync(application,
+ Requirements.Features.ProofKeyForCodeExchange, It.IsAny()), Times.Never());
}
[Theory]