From 4a1d15f1073fa0a321da338315fc5f99b9646af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Wed, 2 Oct 2019 21:52:44 +0200 Subject: [PATCH] Introduce OpenIddictServerOptions.ResponseTypes/ResponseModes to support registering custom response types/modes --- samples/Mvc.Server/Startup.cs | 11 ++ .../Primitives/OpenIddictExtensions.cs | 19 ++ .../Primitives/OpenIddictMessage.cs | 3 +- .../OpenIddictServerConfiguration.cs | 30 ++++ ...OpenIddictServerHandlers.Authentication.cs | 167 ++++++++++-------- .../OpenIddictServerHandlers.Discovery.cs | 46 ++--- .../OpenIddictServerHandlers.Exchange.cs | 6 + .../OpenIddictServerHandlers.Introspection.cs | 6 + .../OpenIddictServerHandlers.Revocation.cs | 6 + .../OpenIddictServerHandlers.Session.cs | 6 + .../OpenIddictServerHandlers.Userinfo.cs | 6 + .../OpenIddictServerOptions.cs | 18 ++ 12 files changed, 215 insertions(+), 109 deletions(-) diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs index d21963b5..9daf7098 100644 --- a/samples/Mvc.Server/Startup.cs +++ b/samples/Mvc.Server/Startup.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -135,6 +136,16 @@ namespace Mvc.Server app.UseStatusCodePagesWithReExecute("/error"); + // Note: ASP.NET Core is impacted by a bug that prevents the status code pages + // from working correctly with endpoint routing. For more information, visit + // https://github.com/aspnet/AspNetCore/issues/13715#issuecomment-528929683. + app.Use((context, next) => + { + context.SetEndpoint(null); + + return next(); + }); + app.UseRouting(); app.UseAuthentication(); diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs index 321bf763..6d1bcddb 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs @@ -79,6 +79,25 @@ namespace OpenIddict.Abstractions return ImmutableHashSet.CreateRange(StringComparer.Ordinal, GetValues(request.AcrValues, Separators.Space)); } + /// + /// Extracts the response types from an . + /// + /// The instance. + public static ImmutableHashSet GetResponseTypes([NotNull] this OpenIddictRequest request) + { + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + if (string.IsNullOrEmpty(request.ResponseType)) + { + return ImmutableHashSet.Create(StringComparer.Ordinal); + } + + return ImmutableHashSet.CreateRange(StringComparer.Ordinal, GetValues(request.ResponseType, Separators.Space)); + } + /// /// Extracts the scopes from an . /// diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs index 7be2b39f..d125005a 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs +++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs @@ -231,8 +231,7 @@ namespace OpenIddict.Abstractions /// Gets all the parameters associated with this instance. /// /// The parameters associated with this instance. - public ImmutableDictionary GetParameters() - => Parameters.ToImmutableDictionary(StringComparer.Ordinal); + public IReadOnlyDictionary GetParameters() => Parameters; /// /// Determines whether the current message contains the specified parameter. diff --git a/src/OpenIddict.Server/OpenIddictServerConfiguration.cs b/src/OpenIddict.Server/OpenIddictServerConfiguration.cs index 8f6834da..d83e339a 100644 --- a/src/OpenIddict.Server/OpenIddictServerConfiguration.cs +++ b/src/OpenIddict.Server/OpenIddictServerConfiguration.cs @@ -172,6 +172,36 @@ namespace OpenIddict.Server options.Scopes.Add(Scopes.OfflineAccess); } + if (options.GrantTypes.Contains(GrantTypes.AuthorizationCode)) + { + options.ResponseTypes.Add(ResponseTypes.Code); + } + + if (options.GrantTypes.Contains(GrantTypes.Implicit)) + { + options.ResponseTypes.Add(ResponseTypes.IdToken); + options.ResponseTypes.Add(ResponseTypes.IdToken + ' ' + ResponseTypes.Token); + options.ResponseTypes.Add(ResponseTypes.Token); + } + + if (options.GrantTypes.Contains(GrantTypes.AuthorizationCode) && options.GrantTypes.Contains(GrantTypes.Implicit)) + { + options.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.IdToken); + options.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.IdToken + ' ' + ResponseTypes.Token); + options.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.Token); + } + + if (options.ResponseTypes.Count != 0) + { + options.ResponseModes.Add(ResponseModes.FormPost); + options.ResponseModes.Add(ResponseModes.Fragment); + + if (options.ResponseTypes.Contains(ResponseTypes.Code)) + { + options.ResponseModes.Add(ResponseModes.Query); + } + } + foreach (var key in options.EncryptionCredentials .Select(credentials => credentials.Key) .Concat(options.SigningCredentials.Select(credentials => credentials.Key))) diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs index c8b6d314..3f8ffe0a 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Immutable; +using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; @@ -44,6 +45,7 @@ namespace OpenIddict.Server ValidateRedirectUriParameter.Descriptor, ValidateResponseTypeParameter.Descriptor, ValidateResponseModeParameter.Descriptor, + ValidateScopeParameter.Descriptor, ValidateNonceParameter.Descriptor, ValidatePromptParameter.Descriptor, ValidateCodeChallengeParameters.Descriptor, @@ -363,6 +365,12 @@ namespace OpenIddict.Server context.SkipRequest(); return; } + + throw new InvalidOperationException(new StringBuilder() + .Append("The authorization response was not correctly applied. To apply authorization response, ") + .Append("create a class implementing 'IOpenIddictServerHandler' ") + .AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.") + .ToString()); } } @@ -632,32 +640,10 @@ namespace OpenIddict.Server return default; } - // Reject requests containing the id_token response_type if no openid scope has been received. - if (context.Request.HasResponseType(ResponseTypes.IdToken) && !context.Request.HasScope(Scopes.OpenId)) - { - context.Logger.LogError("The authorization request was rejected because the 'openid' scope was missing."); - - context.Reject( - error: Errors.InvalidRequest, - description: "The mandatory 'openid' scope is missing."); - - return default; - } - - // Reject requests containing the code response_type if the token endpoint has been disabled. - if (context.Request.HasResponseType(ResponseTypes.Code) && context.Options.TokenEndpointUris.Count == 0) - { - context.Logger.LogError("The authorization request was rejected because the authorization code flow was disabled."); - - context.Reject( - error: Errors.UnsupportedResponseType, - description: "The specified 'response_type' is not supported by this server."); - - return default; - } - // Reject requests that specify an unsupported response_type. - if (!context.Request.IsAuthorizationCodeFlow() && !context.Request.IsHybridFlow() && !context.Request.IsImplicitFlow()) + var types = context.Request.GetResponseTypes(); + if (!context.Options.ResponseTypes.Any(type => + types.SetEquals(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries)))) { context.Logger.LogError("The authorization request was rejected because the '{ResponseType}' " + "response type is not supported.", context.Request.ResponseType); @@ -669,71 +655,110 @@ namespace OpenIddict.Server return default; } - // Reject code flow authorization requests if the authorization code flow is not enabled. - if (context.Request.IsAuthorizationCodeFlow() && !context.Options.GrantTypes.Contains(GrantTypes.AuthorizationCode)) - { - context.Logger.LogError("The authorization request was rejected because " + - "the authorization code flow was not enabled."); + return default; + } + } - context.Reject( - error: Errors.UnsupportedResponseType, - description: "The specified 'response_type' parameter is not allowed."); + /// + /// Contains the logic responsible of rejecting authorization requests that specify an invalid response_mode parameter. + /// + public class ValidateResponseModeParameter : IOpenIddictServerHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(ValidateResponseTypeParameter.Descriptor.Order + 1_000) + .Build(); - return default; + /// + /// Processes the event. + /// + /// The context associated with the event to process. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public ValueTask HandleAsync([NotNull] ValidateAuthorizationRequestContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); } - // Reject implicit flow authorization requests if the implicit flow is not enabled. - if (context.Request.IsImplicitFlow() && !context.Options.GrantTypes.Contains(GrantTypes.Implicit)) + // response_mode=query (explicit or not) and a response_type containing id_token + // or token are not considered as a safe combination and MUST be rejected. + // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security. + if (context.Request.IsQueryResponseMode() && (context.Request.HasResponseType(ResponseTypes.IdToken) || + context.Request.HasResponseType(ResponseTypes.Token))) { - context.Logger.LogError("The authorization request was rejected because the implicit flow was not enabled."); + context.Logger.LogError("The authorization request was rejected because the 'response_type'/'response_mode' " + + "combination was invalid: {ResponseType} ; {ResponseMode}.", + context.Request.ResponseType, context.Request.ResponseMode); context.Reject( - error: Errors.UnsupportedResponseType, - description: "The specified 'response_type' parameter is not allowed."); + error: Errors.InvalidRequest, + description: "The specified 'response_type'/'response_mode' combination is invalid."); return default; } - // Reject hybrid flow authorization requests if the authorization code or the implicit flows are not enabled. - if (context.Request.IsHybridFlow() && (!context.Options.GrantTypes.Contains(GrantTypes.AuthorizationCode) || - !context.Options.GrantTypes.Contains(GrantTypes.Implicit))) + // Reject requests that specify an unsupported response_mode or don't specify a different response_mode + // if the default response_mode inferred from the response_type was explicitly disabled in the options. + if (!ValidateResponseMode(context.Request, context.Options)) { - context.Logger.LogError("The authorization request was rejected because the " + - "authorization code flow or the implicit flow was not enabled."); + context.Logger.LogError("The authorization request was rejected because the '{ResponseMode}' " + + "response mode is not supported.", context.Request.ResponseMode); context.Reject( - error: Errors.UnsupportedResponseType, - description: "The specified 'response_type' parameter is not allowed."); + error: Errors.InvalidRequest, + description: "The specified 'response_mode' parameter is not supported."); return default; } - // Reject authorization requests that specify scope=offline_access if the refresh token flow is not enabled. - if (context.Request.HasScope(Scopes.OfflineAccess) && !context.Options.GrantTypes.Contains(GrantTypes.RefreshToken)) + return default; + + static bool ValidateResponseMode(OpenIddictRequest request, OpenIddictServerOptions options) { - context.Reject( - error: Errors.InvalidRequest, - description: "The 'offline_access' scope is not allowed."); + // Note: both the fragment and query response modes are used as default response modes + // when using the implicit/hybrid and code flows if no explicit value was set. + // To ensure requests are rejected if the default response mode was manually disabled, + // the fragment and query response modes are checked first using the appropriate extensions. - return default; - } + if (request.IsFragmentResponseMode()) + { + return options.ResponseModes.Contains(ResponseModes.Fragment); + } - return default; + if (request.IsQueryResponseMode()) + { + return options.ResponseModes.Contains(ResponseModes.Query); + } + + if (string.IsNullOrEmpty(request.ResponseMode)) + { + return true; + } + + return options.ResponseModes.Contains(request.ResponseMode); + } } } /// - /// Contains the logic responsible of rejecting authorization requests that specify an invalid response_mode parameter. + /// Contains the logic responsible of rejecting authorization requests that don't specify a valid scope parameter. /// - public class ValidateResponseModeParameter : IOpenIddictServerHandler + public class ValidateScopeParameter : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() - .UseSingletonHandler() - .SetOrder(ValidateResponseTypeParameter.Descriptor.Order + 1_000) + .UseSingletonHandler() + .SetOrder(ValidateResponseModeParameter.Descriptor.Order + 1_000) .Build(); /// @@ -750,34 +775,24 @@ namespace OpenIddict.Server throw new ArgumentNullException(nameof(context)); } - // response_mode=query (explicit or not) and a response_type containing id_token - // or token are not considered as a safe combination and MUST be rejected. - // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security. - if (context.Request.IsQueryResponseMode() && (context.Request.HasResponseType(ResponseTypes.IdToken) || - context.Request.HasResponseType(ResponseTypes.Token))) + // Reject authorization requests containing the id_token response_type if no openid scope has been received. + if (context.Request.HasResponseType(ResponseTypes.IdToken) && !context.Request.HasScope(Scopes.OpenId)) { - context.Logger.LogError("The authorization request was rejected because the 'response_type'/'response_mode' " + - "combination was invalid: {ResponseType} ; {ResponseMode}.", - context.Request.ResponseType, context.Request.ResponseMode); + context.Logger.LogError("The authorization request was rejected because the 'openid' scope was missing."); context.Reject( error: Errors.InvalidRequest, - description: "The specified 'response_type'/'response_mode' combination is invalid."); + description: "The mandatory 'openid' scope is missing."); return default; } - // Reject requests that specify an unsupported response_mode. - if (!string.IsNullOrEmpty(context.Request.ResponseMode) && !context.Request.IsFormPostResponseMode() && - !context.Request.IsFragmentResponseMode() && - !context.Request.IsQueryResponseMode()) + // Reject authorization requests that specify scope=offline_access if the refresh token flow is not enabled. + if (context.Request.HasScope(Scopes.OfflineAccess) && !context.Options.GrantTypes.Contains(GrantTypes.RefreshToken)) { - context.Logger.LogError("The authorization request was rejected because the '{ResponseMode}' " + - "response mode is not supported.", context.Request.ResponseMode); - context.Reject( error: Errors.InvalidRequest, - description: "The specified 'response_mode' parameter is not supported."); + description: "The 'offline_access' scope is not allowed."); return default; } @@ -797,7 +812,7 @@ namespace OpenIddict.Server public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() - .SetOrder(ValidateResponseModeParameter.Descriptor.Order + 1_000) + .SetOrder(ValidateScopeParameter.Descriptor.Order + 1_000) .Build(); /// diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs index 45e7a812..eac975c2 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Discovery.cs @@ -353,6 +353,12 @@ namespace OpenIddict.Server context.SkipRequest(); return; } + + throw new InvalidOperationException(new StringBuilder() + .Append("The configuration response was not correctly applied. To apply configuration response, ") + .Append("create a class implementing 'IOpenIddictServerHandler' ") + .AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.") + .ToString()); } } @@ -499,11 +505,7 @@ namespace OpenIddict.Server throw new ArgumentNullException(nameof(context)); } - // Only populate grant_type_supported if the authorization or token endpoints are enabled. - if (context.AuthorizationEndpoint != null || context.TokenEndpoint != null) - { - context.GrantTypes.UnionWith(context.Options.GrantTypes); - } + context.GrantTypes.UnionWith(context.Options.GrantTypes); return default; } @@ -537,13 +539,7 @@ namespace OpenIddict.Server throw new ArgumentNullException(nameof(context)); } - // Only populate response_modes_supported if the authorization endpoint is enabled. - if (context.AuthorizationEndpoint != null) - { - context.ResponseModes.Add(ResponseModes.FormPost); - context.ResponseModes.Add(ResponseModes.Fragment); - context.ResponseModes.Add(ResponseModes.Query); - } + context.ResponseModes.UnionWith(context.Options.ResponseModes); return default; } @@ -577,25 +573,7 @@ namespace OpenIddict.Server throw new ArgumentNullException(nameof(context)); } - if (context.GrantTypes.Contains(GrantTypes.AuthorizationCode)) - { - context.ResponseTypes.Add(ResponseTypes.Code); - } - - if (context.GrantTypes.Contains(GrantTypes.AuthorizationCode) && - context.GrantTypes.Contains(GrantTypes.Implicit)) - { - context.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.IdToken); - context.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.IdToken + ' ' + ResponseTypes.Token); - context.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.Token); - } - - if (context.GrantTypes.Contains(GrantTypes.Implicit)) - { - context.ResponseTypes.Add(ResponseTypes.IdToken); - context.ResponseTypes.Add(ResponseTypes.IdToken + ' ' + ResponseTypes.Token); - context.ResponseTypes.Add(ResponseTypes.Token); - } + context.ResponseTypes.UnionWith(context.Options.ResponseTypes); return default; } @@ -1224,6 +1202,12 @@ namespace OpenIddict.Server context.SkipRequest(); return; } + + throw new InvalidOperationException(new StringBuilder() + .Append("The cryptography response was not correctly applied. To apply cryptography response, ") + .Append("create a class implementing 'IOpenIddictServerHandler' ") + .AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.") + .ToString()); } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs index 8e6a034f..300e9006 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs @@ -355,6 +355,12 @@ namespace OpenIddict.Server context.SkipRequest(); return; } + + throw new InvalidOperationException(new StringBuilder() + .Append("The token response was not correctly applied. To apply token response, ") + .Append("create a class implementing 'IOpenIddictServerHandler' ") + .AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.") + .ToString()); } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs index 53fb8bb2..12f738e7 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs @@ -369,6 +369,12 @@ namespace OpenIddict.Server context.SkipRequest(); return; } + + throw new InvalidOperationException(new StringBuilder() + .Append("The introspection response was not correctly applied. To apply introspection response, ") + .Append("create a class implementing 'IOpenIddictServerHandler' ") + .AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.") + .ToString()); } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs index 6989c537..6424800d 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs @@ -317,6 +317,12 @@ namespace OpenIddict.Server context.SkipRequest(); return; } + + throw new InvalidOperationException(new StringBuilder() + .Append("The revocation response was not correctly applied. To apply revocation response, ") + .Append("create a class implementing 'IOpenIddictServerHandler' ") + .AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.") + .ToString()); } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs index a4ea3221..fb7479e3 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs @@ -341,6 +341,12 @@ namespace OpenIddict.Server context.SkipRequest(); return; } + + throw new InvalidOperationException(new StringBuilder() + .Append("The revocation response was not correctly applied. To apply revocation response, ") + .Append("create a class implementing 'IOpenIddictServerHandler' ") + .AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.") + .ToString()); } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs index 2e0f409e..2a4f51d1 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs @@ -345,6 +345,12 @@ namespace OpenIddict.Server context.SkipRequest(); return; } + + throw new InvalidOperationException(new StringBuilder() + .Append("The userinfo response was not correctly applied. To apply userinfo response, ") + .Append("create a class implementing 'IOpenIddictServerHandler' ") + .AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.") + .ToString()); } } diff --git a/src/OpenIddict.Server/OpenIddictServerOptions.cs b/src/OpenIddict.Server/OpenIddictServerOptions.cs index 97694df6..3a8c5709 100644 --- a/src/OpenIddict.Server/OpenIddictServerOptions.cs +++ b/src/OpenIddict.Server/OpenIddictServerOptions.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using Microsoft.IdentityModel.Tokens; using OpenIddict.Abstractions; @@ -123,6 +124,7 @@ namespace OpenIddict.Server /// This option MUST be enabled with extreme caution and custom handlers MUST be registered to /// properly validate OpenID Connect requests. /// + [EditorBrowsable(EditorBrowsableState.Advanced)] public bool EnableDegradedMode { get; set; } /// @@ -189,6 +191,22 @@ namespace OpenIddict.Server /// public ISet GrantTypes { get; } = new HashSet(StringComparer.Ordinal); + /// + /// Gets the OAuth 2.0/OpenID Connect response types enabled for this application. + /// Response types are automatically inferred from the supported standard grant types, + /// but additional values can be added for advanced scenarios (e.g custom type support). + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public ISet ResponseTypes { get; } = new HashSet(StringComparer.Ordinal); + + /// + /// Gets the OAuth 2.0/OpenID Connect response modes enabled for this application. + /// Response modes are automatically inferred from the supported standard grant types, + /// but additional values can be added for advanced scenarios (e.g custom mode support). + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public ISet ResponseModes { get; } = new HashSet(StringComparer.Ordinal); + /// /// Gets or sets a boolean indicating whether endpoint permissions should be ignored. /// Setting this property to true is NOT recommended, unless all