diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx
index 8ff98b2c..bf0fb02e 100644
--- a/src/OpenIddict.Abstractions/OpenIddictResources.resx
+++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx
@@ -1866,6 +1866,9 @@ To use a custom policy relying on the system store, set 'OpenIddictServerOptions
The '{0}' attached to the execution context could not be resolved.
+
+ The '{0}' grant type is already assigned to a standard grant type and cannot be used for custom flows.
+
The security token is missing.
diff --git a/src/OpenIddict.Server/OpenIddictServerBuilder.cs b/src/OpenIddict.Server/OpenIddictServerBuilder.cs
index bca5f7a7..c91efbbf 100644
--- a/src/OpenIddict.Server/OpenIddictServerBuilder.cs
+++ b/src/OpenIddict.Server/OpenIddictServerBuilder.cs
@@ -924,7 +924,16 @@ public sealed class OpenIddictServerBuilder
{
ArgumentException.ThrowIfNullOrEmpty(type);
- return Configure(options => options.GrantTypes.Add(type));
+ return type switch
+ {
+ GrantTypes.AuthorizationCode or GrantTypes.ClientCredentials or
+ GrantTypes.DeviceCode or GrantTypes.Implicit or
+ GrantTypes.Password or GrantTypes.RefreshToken or
+ GrantTypes.TokenExchange
+ => throw new ArgumentException(SR.FormatID0517(type), nameof(type)),
+
+ _ => Configure(options => options.GrantTypes.Add(type))
+ };
}
///
diff --git a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs
index 36491060..624ec52e 100644
--- a/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs
+++ b/test/OpenIddict.Server.Tests/OpenIddictServerBuilderTests.cs
@@ -537,6 +537,27 @@ public class OpenIddictServerBuilderTests
Assert.Equal("type", exception.ParamName);
}
+ [Theory]
+ [InlineData(GrantTypes.AuthorizationCode)]
+ [InlineData(GrantTypes.ClientCredentials)]
+ [InlineData(GrantTypes.DeviceCode)]
+ [InlineData(GrantTypes.Implicit)]
+ [InlineData(GrantTypes.Password)]
+ [InlineData(GrantTypes.RefreshToken)]
+ [InlineData(GrantTypes.TokenExchange)]
+ public void AllowCustomFlow_ThrowsAnExceptionForStandardGrantType(string type)
+ {
+ // Arrange
+ var services = CreateServices();
+ var builder = CreateBuilder(services);
+
+ // Act and assert
+ var exception = Assert.ThrowsAny(() => builder.AllowCustomFlow(type));
+
+ Assert.Equal("type", exception.ParamName);
+ Assert.StartsWith(SR.FormatID0517(type), exception.Message);
+ }
+
[Fact]
public void AllowCustomFlow_CustomFlowIsAdded()
{