private async Task <IEndpointResult> ExecuteDiscoDocAsync(HttpContext context)
        {
            _logger.LogTrace("Start discovery request");

            var baseUrl   = _context.GetIdentityServerBaseUrl().EnsureTrailingSlash();
            var allScopes = await _scopes.GetScopesAsync(publicOnly : true);

            var showScopes = new List <Scope>();

            var document = new DiscoveryDocument
            {
                issuer = _context.GetIssuerUri(),
                subject_types_supported = new[] { "public" },
                id_token_signing_alg_values_supported = new[] { Constants.SigningAlgorithms.RSA_SHA_256 }
            };

            // scopes
            if (_options.DiscoveryOptions.ShowIdentityScopes)
            {
                showScopes.AddRange(allScopes.Where(s => s.Type == ScopeType.Identity));
            }
            if (_options.DiscoveryOptions.ShowResourceScopes)
            {
                showScopes.AddRange(allScopes.Where(s => s.Type == ScopeType.Resource));
            }

            if (showScopes.Any())
            {
                document.scopes_supported = showScopes.Where(s => s.ShowInDiscoveryDocument).Select(s => s.Name).ToArray();
            }

            // claims
            if (_options.DiscoveryOptions.ShowClaims)
            {
                var claims = new List <string>();
                foreach (var s in allScopes)
                {
                    claims.AddRange(from c in s.Claims
                                    where s.Type == ScopeType.Identity
                                    select c.Name);
                }

                document.claims_supported = claims.Distinct().ToArray();
            }

            // grant types
            if (_options.DiscoveryOptions.ShowGrantTypes)
            {
                var standardGrantTypes = Constants.SupportedGrantTypes.AsEnumerable();
                if (this._options.AuthenticationOptions.EnableLocalLogin == false)
                {
                    standardGrantTypes = standardGrantTypes.Where(type => type != OidcConstants.GrantTypes.Password);
                }

                var showGrantTypes = new List <string>(standardGrantTypes);

                if (_options.DiscoveryOptions.ShowCustomGrantTypes)
                {
                    showGrantTypes.AddRange(_customGrants.GetAvailableGrantTypes());
                }

                document.grant_types_supported = showGrantTypes.ToArray();
            }

            // response types
            if (_options.DiscoveryOptions.ShowResponseTypes)
            {
                document.response_types_supported = Constants.SupportedResponseTypes.ToArray();
            }

            // response modes
            if (_options.DiscoveryOptions.ShowResponseModes)
            {
                document.response_modes_supported = Constants.SupportedResponseModes.ToArray();
            }

            // token endpoint authentication methods
            if (_options.DiscoveryOptions.ShowTokenEndpointAuthenticationMethods)
            {
                document.token_endpoint_auth_methods_supported = _parsers.GetAvailableAuthenticationMethods().ToArray();
            }

            // endpoints
            if (_options.DiscoveryOptions.ShowEndpoints)
            {
                if (_options.Endpoints.EnableEndSessionEndpoint)
                {
                    document.http_logout_supported = true;
                }

                if (_options.Endpoints.EnableAuthorizeEndpoint)
                {
                    document.authorization_endpoint = baseUrl + Constants.RoutePaths.Oidc.Authorize;
                }

                if (_options.Endpoints.EnableTokenEndpoint)
                {
                    document.token_endpoint = baseUrl + Constants.RoutePaths.Oidc.Token;
                }

                if (_options.Endpoints.EnableUserInfoEndpoint)
                {
                    document.userinfo_endpoint = baseUrl + Constants.RoutePaths.Oidc.UserInfo;
                }

                if (_options.Endpoints.EnableEndSessionEndpoint)
                {
                    document.end_session_endpoint = baseUrl + Constants.RoutePaths.Oidc.EndSession;
                }

                if (_options.Endpoints.EnableCheckSessionEndpoint)
                {
                    document.check_session_iframe = baseUrl + Constants.RoutePaths.Oidc.CheckSession;
                }

                if (_options.Endpoints.EnableTokenRevocationEndpoint)
                {
                    document.revocation_endpoint = baseUrl + Constants.RoutePaths.Oidc.Revocation;
                }

                if (_options.Endpoints.EnableIntrospectionEndpoint)
                {
                    document.introspection_endpoint = baseUrl + Constants.RoutePaths.Oidc.Introspection;
                }
            }

            if (_options.DiscoveryOptions.ShowKeySet)
            {
                if (_options.SigningCertificate != null)
                {
                    document.jwks_uri = baseUrl + Constants.RoutePaths.Oidc.DiscoveryWebKeys;
                }
            }

            return(new DiscoveryDocumentResult(document, _options.DiscoveryOptions.CustomEntries));
        }
        private async Task <IEndpointResult> ExecuteDiscoDocAsync(HttpContext context)
        {
            _logger.LogDebug("Start discovery request");

            if (!_options.Endpoints.EnableDiscoveryEndpoint)
            {
                _logger.LogInformation("Discovery endpoint disabled. 404.");
                return(new StatusCodeResult(404));
            }

            var baseUrl   = context.GetIdentityServerBaseUrl().EnsureTrailingSlash();
            var allScopes = await _scopes.GetEnabledScopesAsync(publicOnly : true);

            var showScopes = new List <Scope>();

            var document = new DiscoveryDocument
            {
                issuer = context.GetIssuerUri(),
                subject_types_supported = new[] { "public" },
                id_token_signing_alg_values_supported = new[] { Constants.SigningAlgorithms.RSA_SHA_256 },
                code_challenge_methods_supported      = new[] { OidcConstants.CodeChallengeMethods.Plain, OidcConstants.CodeChallengeMethods.Sha256 }
            };

            // scopes
            var theScopes = allScopes as Scope[] ?? allScopes.ToArray();

            if (_options.DiscoveryOptions.ShowIdentityScopes)
            {
                showScopes.AddRange(theScopes.Where(s => s.Type == ScopeType.Identity));
            }
            if (_options.DiscoveryOptions.ShowResourceScopes)
            {
                showScopes.AddRange(theScopes.Where(s => s.Type == ScopeType.Resource));
            }

            if (showScopes.Any())
            {
                document.scopes_supported = showScopes.Where(s => s.ShowInDiscoveryDocument).Select(s => s.Name).ToArray();
            }

            // claims
            if (_options.DiscoveryOptions.ShowClaims)
            {
                var claims = new List <string>();
                foreach (var s in theScopes)
                {
                    claims.AddRange(from c in s.Claims
                                    where s.Type == ScopeType.Identity
                                    select c.Name);
                }

                document.claims_supported = claims.Distinct().ToArray();
            }

            // grant types
            if (_options.DiscoveryOptions.ShowGrantTypes)
            {
                var standardGrantTypes = new List <string>
                {
                    OidcConstants.GrantTypes.AuthorizationCode,
                    OidcConstants.GrantTypes.ClientCredentials,
                    OidcConstants.GrantTypes.RefreshToken,
                    OidcConstants.GrantTypes.Implicit
                };

                if (!(_resourceOwnerValidator is NotSupportedResouceOwnerPasswordValidator))
                {
                    standardGrantTypes.Add(OidcConstants.GrantTypes.Password);
                }

                var showGrantTypes = new List <string>(standardGrantTypes);

                if (_options.DiscoveryOptions.ShowExtensionGrantTypes)
                {
                    showGrantTypes.AddRange(_extensionGrants.GetAvailableGrantTypes());
                }

                document.grant_types_supported = showGrantTypes.ToArray();
            }

            // response types
            if (_options.DiscoveryOptions.ShowResponseTypes)
            {
                document.response_types_supported = Constants.SupportedResponseTypes.ToArray();
            }

            // response modes
            if (_options.DiscoveryOptions.ShowResponseModes)
            {
                document.response_modes_supported = Constants.SupportedResponseModes.ToArray();
            }

            // token endpoint authentication methods
            if (_options.DiscoveryOptions.ShowTokenEndpointAuthenticationMethods)
            {
                document.token_endpoint_auth_methods_supported = _parsers.GetAvailableAuthenticationMethods().ToArray();
            }

            // endpoints
            if (_options.DiscoveryOptions.ShowEndpoints)
            {
                if (_options.Endpoints.EnableAuthorizeEndpoint)
                {
                    document.authorization_endpoint = baseUrl + Constants.ProtocolRoutePaths.Authorize;
                }

                if (_options.Endpoints.EnableTokenEndpoint)
                {
                    document.token_endpoint = baseUrl + Constants.ProtocolRoutePaths.Token;
                }

                if (_options.Endpoints.EnableUserInfoEndpoint)
                {
                    document.userinfo_endpoint = baseUrl + Constants.ProtocolRoutePaths.UserInfo;
                }

                if (_options.Endpoints.EnableEndSessionEndpoint)
                {
                    document.frontchannel_logout_session_supported = true;
                    document.frontchannel_logout_supported         = true;
                    document.end_session_endpoint = baseUrl + Constants.ProtocolRoutePaths.EndSession;
                }

                if (_options.Endpoints.EnableCheckSessionEndpoint)
                {
                    document.check_session_iframe = baseUrl + Constants.ProtocolRoutePaths.CheckSession;
                }

                if (_options.Endpoints.EnableTokenRevocationEndpoint)
                {
                    document.revocation_endpoint = baseUrl + Constants.ProtocolRoutePaths.Revocation;
                }

                if (_options.Endpoints.EnableIntrospectionEndpoint)
                {
                    document.introspection_endpoint = baseUrl + Constants.ProtocolRoutePaths.Introspection;
                }
            }

            if (_options.DiscoveryOptions.ShowKeySet)
            {
                if ((await _keys.GetValidationKeysAsync()).Any())
                {
                    document.jwks_uri = baseUrl + Constants.ProtocolRoutePaths.DiscoveryWebKeys;
                }
            }

            return(new DiscoveryDocumentResult(document, _options.DiscoveryOptions.CustomEntries));
        }
        /// <summary>
        /// Creates the discovery document.
        /// </summary>
        /// <param name="baseUrl">The base URL.</param>
        /// <param name="issuerUri">The issuer URI.</param>
        public virtual async Task <Dictionary <string, object> > CreateDiscoveryDocumentAsync(string baseUrl, string issuerUri)
        {
            var entries = new Dictionary <string, object>();

            // issuer
            entries.Add(OidcConstants.Discovery.Issuer, issuerUri);

            // jwks
            if (Options.Discovery.ShowKeySet)
            {
                if ((await Keys.GetValidationKeysAsync()).Any())
                {
                    entries.Add(OidcConstants.Discovery.JwksUri, baseUrl + Constants.ProtocolRoutePaths.DiscoveryWebKeys);
                }
            }

            // endpoints
            if (Options.Discovery.ShowEndpoints)
            {
                if (Options.Endpoints.EnableAuthorizeEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.AuthorizationEndpoint, baseUrl + Constants.ProtocolRoutePaths.Authorize);
                }

                if (Options.Endpoints.EnableTokenEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.TokenEndpoint, baseUrl + Constants.ProtocolRoutePaths.Token);
                }

                if (Options.Endpoints.EnableUserInfoEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.UserInfoEndpoint, baseUrl + Constants.ProtocolRoutePaths.UserInfo);
                }

                if (Options.Endpoints.EnableEndSessionEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.EndSessionEndpoint, baseUrl + Constants.ProtocolRoutePaths.EndSession);
                }

                if (Options.Endpoints.EnableCheckSessionEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.CheckSessionIframe, baseUrl + Constants.ProtocolRoutePaths.CheckSession);
                }

                if (Options.Endpoints.EnableTokenRevocationEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.RevocationEndpoint, baseUrl + Constants.ProtocolRoutePaths.Revocation);
                }

                if (Options.Endpoints.EnableIntrospectionEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.IntrospectionEndpoint, baseUrl + Constants.ProtocolRoutePaths.Introspection);
                }
            }

            // logout
            if (Options.Endpoints.EnableEndSessionEndpoint)
            {
                entries.Add(OidcConstants.Discovery.FrontChannelLogoutSupported, true);
                entries.Add(OidcConstants.Discovery.FrontChannelLogoutSessionSupported, true);
            }

            // scopes and claims
            if (Options.Discovery.ShowIdentityScopes ||
                Options.Discovery.ShowApiScopes ||
                Options.Discovery.ShowClaims)
            {
                var resources = await ResourceStore.GetAllEnabledResourcesAsync();

                var scopes = new List <string>();

                // scopes
                if (Options.Discovery.ShowIdentityScopes)
                {
                    scopes.AddRange(resources.IdentityResources.Where(x => x.ShowInDiscoveryDocument).Select(x => x.Name));
                }
                if (Options.Discovery.ShowApiScopes)
                {
                    var apiScopes = from api in resources.ApiResources
                                    from scope in api.Scopes
                                    where scope.ShowInDiscoveryDocument
                                    select scope.Name;

                    scopes.AddRange(apiScopes);
                    scopes.Add(IdentityServerConstants.StandardScopes.OfflineAccess);
                }

                if (scopes.Any())
                {
                    entries.Add(OidcConstants.Discovery.ScopesSupported, scopes.ToArray());
                }

                // claims
                if (Options.Discovery.ShowClaims)
                {
                    var claims = new List <string>();

                    claims.AddRange(resources.IdentityResources.SelectMany(x => x.UserClaims));
                    claims.AddRange(resources.ApiResources.SelectMany(x => x.UserClaims));

                    entries.Add(OidcConstants.Discovery.ClaimsSupported, claims.Distinct().ToArray());
                }
            }

            // grant types
            if (Options.Discovery.ShowGrantTypes)
            {
                var standardGrantTypes = new List <string>
                {
                    OidcConstants.GrantTypes.AuthorizationCode,
                    OidcConstants.GrantTypes.ClientCredentials,
                    OidcConstants.GrantTypes.RefreshToken,
                    OidcConstants.GrantTypes.Implicit
                };

                if (!(ResourceOwnerValidator is NotSupportedResouceOwnerPasswordValidator))
                {
                    standardGrantTypes.Add(OidcConstants.GrantTypes.Password);
                }

                var showGrantTypes = new List <string>(standardGrantTypes);

                if (Options.Discovery.ShowExtensionGrantTypes)
                {
                    showGrantTypes.AddRange(ExtensionGrants.GetAvailableGrantTypes());
                }

                entries.Add(OidcConstants.Discovery.GrantTypesSupported, showGrantTypes.ToArray());
            }

            // response types
            if (Options.Discovery.ShowResponseTypes)
            {
                entries.Add(OidcConstants.Discovery.ResponseTypesSupported, Constants.SupportedResponseTypes.ToArray());
            }

            // response modes
            if (Options.Discovery.ShowResponseModes)
            {
                entries.Add(OidcConstants.Discovery.ResponseModesSupported, Constants.SupportedResponseModes.ToArray());
            }

            // misc
            if (Options.Discovery.ShowTokenEndpointAuthenticationMethods)
            {
                entries.Add(OidcConstants.Discovery.TokenEndpointAuthenticationMethodsSupported, SecretParsers.GetAvailableAuthenticationMethods().ToArray());
            }

            entries.Add(OidcConstants.Discovery.SubjectTypesSupported, new[] { "public" });
            entries.Add(OidcConstants.Discovery.IdTokenSigningAlgorithmsSupported, new[] { Constants.SigningAlgorithms.RSA_SHA_256 });
            entries.Add(OidcConstants.Discovery.CodeChallengeMethodsSupported, new[] { OidcConstants.CodeChallengeMethods.Plain, OidcConstants.CodeChallengeMethods.Sha256 });

            // custom entries
            if (!Options.Discovery.CustomEntries.IsNullOrEmpty())
            {
                foreach (var customEntry in Options.Discovery.CustomEntries)
                {
                    if (entries.ContainsKey(customEntry.Key))
                    {
                        Logger.LogError("Discovery custom entry {key} cannot be added, because it already exists.", customEntry.Key);
                    }
                    else
                    {
                        var customValueString = customEntry.Value as string;
                        if (customValueString != null)
                        {
                            if (customValueString.StartsWith("~/") && Options.Discovery.ExpandRelativePathsInCustomEntries)
                            {
                                entries.Add(customEntry.Key, baseUrl + customValueString.Substring(2));
                                continue;
                            }
                        }

                        entries.Add(customEntry.Key, customEntry.Value);
                    }
                }
            }

            return(entries);
        }
        /// <summary>
        /// Creates the discovery document.
        /// </summary>
        /// <param name="baseUrl">The base URL.</param>
        /// <param name="issuerUri">The issuer URI.</param>
        public virtual async Task <Dictionary <string, object> > CreateDiscoveryDocumentAsync(string baseUrl, string issuerUri)
        {
            var entries = new Dictionary <string, object>
            {
                { OidcConstants.Discovery.Issuer, issuerUri }
            };

            // jwks
            if (Options.Discovery.ShowKeySet)
            {
                if ((await Keys.GetValidationKeysAsync()).Any())
                {
                    entries.Add(OidcConstants.Discovery.JwksUri, baseUrl + Constants.ProtocolRoutePaths.DiscoveryWebKeys);
                }
            }

            // endpoints
            if (Options.Discovery.ShowEndpoints)
            {
                if (Options.Endpoints.EnableAuthorizeEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.AuthorizationEndpoint, baseUrl + Constants.ProtocolRoutePaths.Authorize);
                }

                if (Options.Endpoints.EnableTokenEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.TokenEndpoint, baseUrl + Constants.ProtocolRoutePaths.Token);
                }

                if (Options.Endpoints.EnableUserInfoEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.UserInfoEndpoint, baseUrl + Constants.ProtocolRoutePaths.UserInfo);
                }

                if (Options.Endpoints.EnableEndSessionEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.EndSessionEndpoint, baseUrl + Constants.ProtocolRoutePaths.EndSession);
                }

                if (Options.Endpoints.EnableCheckSessionEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.CheckSessionIframe, baseUrl + Constants.ProtocolRoutePaths.CheckSession);
                }

                if (Options.Endpoints.EnableTokenRevocationEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.RevocationEndpoint, baseUrl + Constants.ProtocolRoutePaths.Revocation);
                }

                if (Options.Endpoints.EnableIntrospectionEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.IntrospectionEndpoint, baseUrl + Constants.ProtocolRoutePaths.Introspection);
                }

                if (Options.Endpoints.EnableDeviceAuthorizationEndpoint)
                {
                    entries.Add(OidcConstants.Discovery.DeviceAuthorizationEndpoint, baseUrl + Constants.ProtocolRoutePaths.DeviceAuthorization);
                }

                if (Options.MutualTls.Enabled)
                {
                    var mtlsEndpoints = new Dictionary <string, string>();

                    if (Options.Endpoints.EnableTokenEndpoint)
                    {
                        mtlsEndpoints.Add(OidcConstants.Discovery.TokenEndpoint, ConstructMtlsEndpoint(Constants.ProtocolRoutePaths.Token));
                    }
                    if (Options.Endpoints.EnableTokenRevocationEndpoint)
                    {
                        mtlsEndpoints.Add(OidcConstants.Discovery.RevocationEndpoint, ConstructMtlsEndpoint(Constants.ProtocolRoutePaths.Revocation));
                    }
                    if (Options.Endpoints.EnableIntrospectionEndpoint)
                    {
                        mtlsEndpoints.Add(OidcConstants.Discovery.IntrospectionEndpoint, ConstructMtlsEndpoint(Constants.ProtocolRoutePaths.Introspection));
                    }
                    if (Options.Endpoints.EnableDeviceAuthorizationEndpoint)
                    {
                        mtlsEndpoints.Add(OidcConstants.Discovery.DeviceAuthorizationEndpoint, ConstructMtlsEndpoint(Constants.ProtocolRoutePaths.DeviceAuthorization));
                    }

                    if (mtlsEndpoints.Any())
                    {
                        entries.Add(OidcConstants.Discovery.MtlsEndpointAliases, mtlsEndpoints);
                    }

                    string ConstructMtlsEndpoint(string endpoint)
                    {
                        // path based
                        if (Options.MutualTls.DomainName.IsMissing())
                        {
                            return(baseUrl + endpoint.Replace(Constants.ProtocolRoutePaths.ConnectPathPrefix, Constants.ProtocolRoutePaths.MtlsPathPrefix));
                        }
                        else
                        {
                            // domain based
                            if (Options.MutualTls.DomainName.Contains("."))
                            {
                                return($"https://{Options.MutualTls.DomainName}/{endpoint}");
                            }
                            // sub-domain based
                            else
                            {
                                var parts = baseUrl.Split("://");
                                return($"https://{Options.MutualTls.DomainName}.{parts[1]}{endpoint}");
                            }
                        }
                    }
                }
            }

            // logout
            if (Options.Endpoints.EnableEndSessionEndpoint)
            {
                entries.Add(OidcConstants.Discovery.FrontChannelLogoutSupported, true);
                entries.Add(OidcConstants.Discovery.FrontChannelLogoutSessionSupported, true);
                entries.Add(OidcConstants.Discovery.BackChannelLogoutSupported, true);
                entries.Add(OidcConstants.Discovery.BackChannelLogoutSessionSupported, true);
            }

            // scopes and claims
            if (Options.Discovery.ShowIdentityScopes ||
                Options.Discovery.ShowApiScopes ||
                Options.Discovery.ShowClaims)
            {
                var resources = await ResourceStore.GetAllEnabledResourcesAsync();

                var scopes = new List <string>();

                // scopes
                if (Options.Discovery.ShowIdentityScopes)
                {
                    scopes.AddRange(resources.IdentityResources.Where(x => x.ShowInDiscoveryDocument).Select(x => x.Name));
                }

                if (Options.Discovery.ShowApiScopes)
                {
                    var apiScopes = from api in resources.ApiResources
                                    from scope in api.Scopes
                                    where scope.ShowInDiscoveryDocument
                                    select scope.Name;

                    scopes.AddRange(apiScopes);
                    scopes.Add(IdentityServerConstants.StandardScopes.OfflineAccess);
                }

                if (scopes.Any())
                {
                    entries.Add(OidcConstants.Discovery.ScopesSupported, scopes.ToArray());
                }

                // claims
                if (Options.Discovery.ShowClaims)
                {
                    var claims = new List <string>();

                    // add non-hidden identity scopes related claims
                    claims.AddRange(resources.IdentityResources.Where(x => x.ShowInDiscoveryDocument).SelectMany(x => x.UserClaims));

                    // add non-hidden api scopes related claims
                    foreach (var resource in resources.ApiResources)
                    {
                        claims.AddRange(resource.UserClaims);

                        foreach (var scope in resource.Scopes)
                        {
                            if (scope.ShowInDiscoveryDocument)
                            {
                                claims.AddRange(scope.UserClaims);
                            }
                        }
                    }

                    entries.Add(OidcConstants.Discovery.ClaimsSupported, claims.Distinct().ToArray());
                }
            }

            // grant types
            if (Options.Discovery.ShowGrantTypes)
            {
                var standardGrantTypes = new List <string>
                {
                    OidcConstants.GrantTypes.AuthorizationCode,
                    OidcConstants.GrantTypes.ClientCredentials,
                    OidcConstants.GrantTypes.RefreshToken,
                    OidcConstants.GrantTypes.Implicit
                };

                if (!(ResourceOwnerValidator is NotSupportedResourceOwnerPasswordValidator))
                {
                    standardGrantTypes.Add(OidcConstants.GrantTypes.Password);
                }

                if (Options.Endpoints.EnableDeviceAuthorizationEndpoint)
                {
                    standardGrantTypes.Add(OidcConstants.GrantTypes.DeviceCode);
                }

                var showGrantTypes = new List <string>(standardGrantTypes);

                if (Options.Discovery.ShowExtensionGrantTypes)
                {
                    showGrantTypes.AddRange(ExtensionGrants.GetAvailableGrantTypes());
                }

                entries.Add(OidcConstants.Discovery.GrantTypesSupported, showGrantTypes.ToArray());
            }

            // response types
            if (Options.Discovery.ShowResponseTypes)
            {
                entries.Add(OidcConstants.Discovery.ResponseTypesSupported, Constants.SupportedResponseTypes.ToArray());
            }

            // response modes
            if (Options.Discovery.ShowResponseModes)
            {
                entries.Add(OidcConstants.Discovery.ResponseModesSupported, Constants.SupportedResponseModes.ToArray());
            }

            // misc
            if (Options.Discovery.ShowTokenEndpointAuthenticationMethods)
            {
                var types = SecretParsers.GetAvailableAuthenticationMethods().ToList();
                if (Options.MutualTls.Enabled)
                {
                    types.Add(OidcConstants.EndpointAuthenticationMethods.TlsClientAuth);
                    types.Add(OidcConstants.EndpointAuthenticationMethods.SelfSignedTlsClientAuth);
                }

                entries.Add(OidcConstants.Discovery.TokenEndpointAuthenticationMethodsSupported, types);
            }

            var signingCredentials = await Keys.GetAllSigningCredentialsAsync();

            if (signingCredentials.Any())
            {
                entries.Add(OidcConstants.Discovery.IdTokenSigningAlgorithmsSupported, new[] { signingCredentials.Select(c => c.Algorithm).Distinct() });
            }

            entries.Add(OidcConstants.Discovery.SubjectTypesSupported, new[] { "public" });
            entries.Add(OidcConstants.Discovery.CodeChallengeMethodsSupported, new[] { OidcConstants.CodeChallengeMethods.Plain, OidcConstants.CodeChallengeMethods.Sha256 });

            if (Options.Endpoints.EnableAuthorizeEndpoint)
            {
                entries.Add(OidcConstants.Discovery.RequestParameterSupported, true);

                if (Options.Endpoints.EnableJwtRequestUri)
                {
                    entries.Add(OidcConstants.Discovery.RequestUriParameterSupported, true);
                }
            }

            if (Options.MutualTls.Enabled)
            {
                entries.Add(OidcConstants.Discovery.TlsClientCertificateBoundAccessTokens, true);
            }

            // custom entries
            if (!Options.Discovery.CustomEntries.IsNullOrEmpty())
            {
                foreach (var customEntry in Options.Discovery.CustomEntries)
                {
                    if (entries.ContainsKey(customEntry.Key))
                    {
                        Logger.LogError("Discovery custom entry {key} cannot be added, because it already exists.", customEntry.Key);
                    }
                    else
                    {
                        if (customEntry.Value is string customValueString)
                        {
                            if (customValueString.StartsWith("~/") && Options.Discovery.ExpandRelativePathsInCustomEntries)
                            {
                                entries.Add(customEntry.Key, baseUrl + customValueString.Substring(2));
                                continue;
                            }
                        }

                        entries.Add(customEntry.Key, customEntry.Value);
                    }
                }
            }

            return(entries);
        }