diff --git a/src/OpenIddict.Abstractions/Resources/OpenIddictResources.resx b/src/OpenIddict.Abstractions/Resources/OpenIddictResources.resx
index c4fbcd42..f80328ab 100644
--- a/src/OpenIddict.Abstractions/Resources/OpenIddictResources.resx
+++ b/src/OpenIddict.Abstractions/Resources/OpenIddictResources.resx
@@ -1776,6 +1776,10 @@ To register the OpenIddict core services, reference the 'OpenIddict.Core' packag
The token shouldn't be null or empty at this point.
{Locked}
+
+ The OpenIddict Core services should be registered.
+ {Locked}
+
An error occurred while validating the token '{Token}'.
{Locked}
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
index 8db9b064..e01c61c5 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
@@ -1080,13 +1080,15 @@ namespace OpenIddict.Server
///
/// Contains the logic responsible of rejecting authorization requests that use unregistered scopes.
- /// Note: this handler is not used when the degraded mode is enabled or when scope validation is disabled.
+ /// Note: this handler partially works with the degraded mode but is not used when scope validation is disabled.
///
public class ValidateScopes : IOpenIddictServerHandler
{
- private readonly IOpenIddictScopeManager _scopeManager;
+ private readonly IOpenIddictScopeManager? _scopeManager;
- public ValidateScopes() => throw new InvalidOperationException(SR.GetResourceString(SR.ID1015));
+ public ValidateScopes()
+ {
+ }
public ValidateScopes(IOpenIddictScopeManager scopeManager)
=> _scopeManager = scopeManager;
@@ -1097,7 +1099,6 @@ namespace OpenIddict.Server
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.AddFilter()
- .AddFilter()
.UseScopedHandler()
.SetOrder(ValidateClientRedirectUri.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
@@ -1115,8 +1116,13 @@ namespace OpenIddict.Server
var scopes = new HashSet(context.Request.GetScopes(), StringComparer.Ordinal);
scopes.ExceptWith(context.Options.Scopes);
- if (scopes.Count != 0)
+ // Note: the remaining scopes are only checked if the degraded mode was not enabled,
+ // as this requires using the scope manager, which is never used with the degraded mode,
+ // even if the service was registered and resolved from the dependency injection container.
+ if (scopes.Count != 0 && !context.Options.EnableDegradedMode)
{
+ Debug.Assert(_scopeManager != null, SR.GetResourceString(SR.ID5011));
+
await foreach (var scope in _scopeManager.FindByNamesAsync(scopes.ToImmutableArray()))
{
var name = await _scopeManager.GetNameAsync(scope);
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs
index b767d343..9904622c 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs
@@ -364,13 +364,15 @@ namespace OpenIddict.Server
///
/// Contains the logic responsible of rejecting authorization requests that use unregistered scopes.
- /// Note: this handler is not used when the degraded mode is enabled or when scope validation is disabled.
+ /// Note: this handler partially works with the degraded mode but is not used when scope validation is disabled.
///
public class ValidateScopes : IOpenIddictServerHandler
{
- private readonly IOpenIddictScopeManager _scopeManager;
+ private readonly IOpenIddictScopeManager? _scopeManager;
- public ValidateScopes() => throw new InvalidOperationException(SR.GetResourceString(SR.ID1015));
+ public ValidateScopes()
+ {
+ }
public ValidateScopes(IOpenIddictScopeManager scopeManager)
=> _scopeManager = scopeManager;
@@ -381,7 +383,6 @@ namespace OpenIddict.Server
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.AddFilter()
- .AddFilter()
.UseScopedHandler()
.SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
@@ -399,8 +400,13 @@ namespace OpenIddict.Server
var scopes = new HashSet(context.Request.GetScopes(), StringComparer.Ordinal);
scopes.ExceptWith(context.Options.Scopes);
- if (scopes.Count != 0)
+ // Note: the remaining scopes are only checked if the degraded mode was not enabled,
+ // as this requires using the scope manager, which is never used with the degraded mode,
+ // even if the service was registered and resolved from the dependency injection container.
+ if (scopes.Count != 0 && !context.Options.EnableDegradedMode)
{
+ Debug.Assert(_scopeManager != null, SR.GetResourceString(SR.ID5011));
+
await foreach (var scope in _scopeManager.FindByNamesAsync(scopes.ToImmutableArray()))
{
var name = await _scopeManager.GetNameAsync(scope);
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
index ebfea6eb..10777c2e 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
@@ -645,13 +645,15 @@ namespace OpenIddict.Server
///
/// Contains the logic responsible of rejecting authorization requests that use unregistered scopes.
- /// Note: this handler is not used when the degraded mode is enabled or when scope validation is disabled.
+ /// Note: this handler partially works with the degraded mode but is not used when scope validation is disabled.
///
public class ValidateScopes : IOpenIddictServerHandler
{
- private readonly IOpenIddictScopeManager _scopeManager;
+ private readonly IOpenIddictScopeManager? _scopeManager;
- public ValidateScopes() => throw new InvalidOperationException(SR.GetResourceString(SR.ID1015));
+ public ValidateScopes()
+ {
+ }
public ValidateScopes(IOpenIddictScopeManager scopeManager)
=> _scopeManager = scopeManager;
@@ -662,7 +664,6 @@ namespace OpenIddict.Server
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.AddFilter()
- .AddFilter()
.UseScopedHandler()
.SetOrder(ValidatePasswordParameters.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
@@ -680,8 +681,13 @@ namespace OpenIddict.Server
var scopes = new HashSet(context.Request.GetScopes(), StringComparer.Ordinal);
scopes.ExceptWith(context.Options.Scopes);
- if (scopes.Count != 0)
+ // Note: the remaining scopes are only checked if the degraded mode was not enabled,
+ // as this requires using the scope manager, which is never used with the degraded mode,
+ // even if the service was registered and resolved from the dependency injection container.
+ if (scopes.Count != 0 && !context.Options.EnableDegradedMode)
{
+ Debug.Assert(_scopeManager != null, SR.GetResourceString(SR.ID5011));
+
await foreach (var scope in _scopeManager.FindByNamesAsync(scopes.ToImmutableArray()))
{
var name = await _scopeManager.GetNameAsync(scope);
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs
index 7f655ee1..5b4d3c0b 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs
@@ -615,38 +615,8 @@ namespace OpenIddict.Server.IntegrationTests
// Arrange
await using var server = await CreateServerAsync(options =>
{
+ options.EnableDegradedMode();
options.RegisterScopes("registered_scope");
- options.SetRevocationEndpointUris(Array.Empty());
- options.DisableTokenStorage();
- options.DisableSlidingRefreshTokenExpiration();
-
- options.Services.AddSingleton(CreateApplicationManager(mock =>
- {
- var application = new OpenIddictApplication();
-
- 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.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()))
- .ReturnsAsync(true);
- }));
-
- options.Services.AddSingleton(CreateApplicationManager(mock =>
- {
- var application = new OpenIddictApplication();
-
- 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.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny()))
- .ReturnsAsync(true);
- }));
options.AddEventHandler(builder =>
builder.UseInlineHandler(context =>
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
index 0d5821b8..d5905f8e 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
@@ -886,6 +886,7 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
+ options.RegisterScopes(Scopes.Phone, Scopes.Profile);
options.AddEventHandler(builder =>
{
@@ -930,6 +931,7 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
+ options.RegisterScopes(Scopes.Phone, Scopes.Profile);
options.AddEventHandler(builder =>
{
@@ -974,6 +976,7 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
+ options.RegisterScopes(Scopes.Phone, Scopes.Profile);
options.AddEventHandler(builder =>
{
@@ -1016,6 +1019,7 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
+ options.RegisterScopes(Scopes.Phone, Scopes.Profile);
options.AddEventHandler(builder =>
{