Merge pull request #2205 from DuendeSoftware/beh/additional-metadata-fields

Additional Discovery Document Fields
This commit is contained in:
Joe DeCock 2025-10-16 17:15:19 -05:00 committed by GitHub
commit dd3932febd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 823 additions and 43 deletions

View file

@ -62,11 +62,26 @@ public class DiscoveryOptions
/// </summary>
public bool ShowTokenEndpointAuthenticationMethods { get; set; } = true;
/// <summary>
/// Show revocation endpoint authentication methods
/// </summary>
public bool ShowRevocationEndpointAuthenticationMethods { get; set; } = true;
/// <summary>
/// Show introspection endpoint authentication methods
/// </summary>
public bool ShowIntrospectionEndpointAuthenticationMethods { get; set; } = true;
/// <summary>
/// Turns relative paths that start with ~/ into absolute paths
/// </summary>
public bool ExpandRelativePathsInCustomEntries { get; set; } = true;
/// <summary>
/// Options for how the dynamic client registration endpoint is shown in the discovery document
/// </summary>
public DynamicClientRegistrationDiscoveryOptions DynamicClientRegistration { get; set; } = new DynamicClientRegistrationDiscoveryOptions();
/// <summary>
/// Sets the maxage value of the cache control header (in seconds) of the HTTP response. This gives clients a hint how often they should refresh their cached copy of the discovery document. If set to 0 no-cache headers will be set. Defaults to null, which does not set the header.
/// </summary>

View file

@ -0,0 +1,42 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
#nullable enable
namespace Duende.IdentityServer.Configuration;
public enum RegistrationEndpointMode
{
/// <summary>
/// Will not show a registration endpoint in the discovery document
/// </summary>
None,
/// <summary>
/// Will use the static URL from <see cref="DynamicClientRegistrationDiscoveryOptions.StaticRegistrationEndpoint"/>
/// </summary>
Static,
/// <summary>
/// Will infer the URL dynamically based on the host
/// </summary>
Inferred
}
public class DynamicClientRegistrationDiscoveryOptions
{
/// <summary>
/// Gets or sets the type of the registration endpoint
/// </summary>
/// <value>
/// The type of the registration endpoint.
/// </value>
public RegistrationEndpointMode RegistrationEndpointMode { get; set; } = RegistrationEndpointMode.None;
/// <summary>
/// Gets or sets the custom registration endpoint
/// </summary>
/// <value>
/// The URL of the authorization endpoint to use in the discovery document if <see cref="RegistrationEndpointMode"/> is set to <see cref="RegistrationEndpointMode.Static"/>.
/// </value>
public Uri? StaticRegistrationEndpoint { get; set; }
}

View file

@ -215,6 +215,12 @@ public static class IdentityServerApplicationBuilderExtensions
{
throw new InvalidOperationException("CorsPolicyName is not configured");
}
if (options.Discovery.DynamicClientRegistration.RegistrationEndpointMode == RegistrationEndpointMode.Static
&& options.Discovery.DynamicClientRegistration.StaticRegistrationEndpoint == null)
{
throw new InvalidOperationException("DynamicClientRegistration.CustomRegistrationEndpoint must be set when using static registration endpoint type.");
}
}
internal static object? TestService(IServiceProvider serviceProvider, Type service, ILogger logger, string? message = null, bool doThrow = true)

View file

@ -257,6 +257,7 @@ public static class IdentityServerConstants
public const string DeviceAuthorization = ConnectPathPrefix + "/deviceauthorization";
public const string PushedAuthorization = ConnectPathPrefix + "/par";
public const string OAuthMetadata = ".well-known/oauth-authorization-server";
public const string DynamicClientRegistration = ConnectPathPrefix + "/dcr";
public const string MtlsPathPrefix = ConnectPathPrefix + "/mtls";

View file

@ -322,23 +322,24 @@ public class DiscoveryResponseGenerator : IDiscoveryResponseGenerator
entries.Add(OidcConstants.Discovery.ResponseModesSupported, Constants.SupportedResponseModes.ToArray());
}
var supportedAuthMethods = GetSupportedAuthMethods();
// 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);
entries.Add(OidcConstants.Discovery.TokenEndpointAuthenticationMethodsSupported, supportedAuthMethods);
AddSigningAlgorithmsForEndpointIfNeeded(OidcConstants.Discovery.TokenEndpointAuthSigningAlgorithmsSupported, entries, supportedAuthMethods);
}
if (types.Contains(OidcConstants.EndpointAuthenticationMethods.PrivateKeyJwt) &&
!IEnumerableExtensions.IsNullOrEmpty(Options.SupportedClientAssertionSigningAlgorithms))
{
entries.Add(OidcConstants.Discovery.TokenEndpointAuthSigningAlgorithmsSupported,
Options.SupportedClientAssertionSigningAlgorithms);
}
if (Options.Discovery.ShowRevocationEndpointAuthenticationMethods)
{
entries.Add(OidcConstants.Discovery.RevocationEndpointAuthenticationMethodsSupported, supportedAuthMethods);
AddSigningAlgorithmsForEndpointIfNeeded(OidcConstants.Discovery.RevocationEndpointAuthSigningAlgorithmsSupported, entries, supportedAuthMethods);
}
if (Options.Discovery.ShowIntrospectionEndpointAuthenticationMethods)
{
entries.Add(OidcConstants.Discovery.IntrospectionEndpointAuthenticationMethodsSupported, supportedAuthMethods);
AddSigningAlgorithmsForEndpointIfNeeded(OidcConstants.Discovery.IntrospectionEndpointAuthSigningAlgorithmsSupported, entries, supportedAuthMethods);
}
var signingCredentials = await Keys.GetAllSigningCredentialsAsync();
@ -346,6 +347,16 @@ public class DiscoveryResponseGenerator : IDiscoveryResponseGenerator
{
var signingAlgorithms = signingCredentials.Select(c => c.Algorithm).Distinct();
entries.Add(OidcConstants.Discovery.IdTokenSigningAlgorithmsSupported, signingAlgorithms);
if (Options.Endpoints.EnableUserInfoEndpoint)
{
entries.Add(OidcConstants.Discovery.UserInfoSigningAlgorithmsSupported, signingAlgorithms);
}
if (Options.Endpoints.EnableIntrospectionEndpoint)
{
entries.Add(OidcConstants.Discovery.IntrospectionSigningAlgorithmsSupported, signingAlgorithms);
}
}
entries.Add(OidcConstants.Discovery.SubjectTypesSupported, SubjectTypesSupported);
@ -384,6 +395,12 @@ public class DiscoveryResponseGenerator : IDiscoveryResponseGenerator
entries.Add(OidcConstants.Discovery.BackchannelTokenDeliveryModesSupported,
new[] { OidcConstants.BackchannelTokenDeliveryModes.Poll });
entries.Add(OidcConstants.Discovery.BackchannelUserCodeParameterSupported, true);
if (!IEnumerableExtensions.IsNullOrEmpty(Options.SupportedRequestObjectSigningAlgorithms))
{
entries.Add(OidcConstants.Discovery.BackchannelAuthenticationRequestSigningAlgValuesSupported,
Options.SupportedRequestObjectSigningAlgorithms);
}
}
if (Options.Endpoints.EnableTokenEndpoint &&
@ -392,6 +409,22 @@ public class DiscoveryResponseGenerator : IDiscoveryResponseGenerator
entries.Add(OidcConstants.Discovery.DPoPSigningAlgorithmsSupported, Options.DPoP.SupportedDPoPSigningAlgorithms);
}
switch (Options.Discovery.DynamicClientRegistration.RegistrationEndpointMode)
{
case RegistrationEndpointMode.Static:
if (Options.Discovery.DynamicClientRegistration.StaticRegistrationEndpoint != null)
{
entries.Add(OidcConstants.Discovery.RegistrationEndpoint, Options.Discovery.DynamicClientRegistration.StaticRegistrationEndpoint.ToString());
}
break;
case RegistrationEndpointMode.Inferred:
entries.Add(OidcConstants.Discovery.RegistrationEndpoint, baseUrl + ProtocolRoutePaths.DynamicClientRegistration);
break;
case RegistrationEndpointMode.None:
default:
break;
}
// custom entries
if (!IEnumerableExtensions.IsNullOrEmpty(Options.Discovery.CustomEntries))
{
@ -541,4 +574,24 @@ public class DiscoveryResponseGenerator : IDiscoveryResponseGenerator
return webKeys;
}
private List<string> GetSupportedAuthMethods()
{
var types = SecretParsers.GetAvailableAuthenticationMethods().ToList();
if (Options.MutualTls.Enabled)
{
types.Add(OidcConstants.EndpointAuthenticationMethods.TlsClientAuth);
types.Add(OidcConstants.EndpointAuthenticationMethods.SelfSignedTlsClientAuth);
}
return types;
}
private void AddSigningAlgorithmsForEndpointIfNeeded(string key, Dictionary<string, object> entries, IEnumerable<string> supportedAuthMethods)
{
if (supportedAuthMethods.Contains(OidcConstants.EndpointAuthenticationMethods.PrivateKeyJwt) && !IEnumerableExtensions.IsNullOrEmpty(Options.SupportedClientAssertionSigningAlgorithms))
{
entries.Add(key, Options.SupportedClientAssertionSigningAlgorithms);
}
}
}

View file

@ -2,6 +2,7 @@
// See LICENSE in the project root for license information.
using System.Text.Json;
using Duende.IdentityModel;
using Duende.IdentityModel.Client;
using Duende.IdentityServer.Configuration;
using Duende.IdentityServer.Endpoints.Results;
@ -77,8 +78,99 @@ public class DiscoveryEndpointTests
algorithmsSupported.ShouldContain(SecurityAlgorithms.EcdsaSha256);
}
[Fact]
[Trait("Category", Category)]
public async Task UserInfo_signing_algorithms_supported_should_match_signing_key()
{
var key = CryptoHelper.CreateECDsaSecurityKey(JsonWebKeyECTypes.P256);
var expectedAlgorithm = SecurityAlgorithms.EcdsaSha256;
var pipeline = new IdentityServerPipeline();
pipeline.OnPostConfigureServices += services =>
{
// add key to standard RSA key
services.AddIdentityServerBuilder()
.AddSigningCredential(key, expectedAlgorithm);
};
pipeline.Initialize();
var result = await pipeline.BackChannelClient.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
var algorithmsSupported = result.UserInfoSigningAlgorithmsSupported;
algorithmsSupported.Count().ShouldBe(2);
algorithmsSupported.ShouldContain(SecurityAlgorithms.RsaSha256);
algorithmsSupported.ShouldContain(SecurityAlgorithms.EcdsaSha256);
}
[Fact]
[Trait("Category", Category)]
public async Task UserInfo_signing_algorithms_supported_should_not_be_present_if_userinfo_endpoint_disabled()
{
var key = CryptoHelper.CreateECDsaSecurityKey(JsonWebKeyECTypes.P256);
var expectedAlgorithm = SecurityAlgorithms.EcdsaSha256;
var pipeline = new IdentityServerPipeline();
pipeline.OnPostConfigureServices += services =>
{
// add key to standard RSA key
services.AddIdentityServerBuilder()
.AddSigningCredential(key, expectedAlgorithm);
};
pipeline.Initialize();
pipeline.Options.Endpoints.EnableUserInfoEndpoint = false;
var result = await pipeline.BackChannelClient.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
result.UserInfoSigningAlgorithmsSupported.ShouldBeEmpty();
}
[Fact]
[Trait("Category", Category)]
public async Task Introspection_signing_algorithms_supported_should_match_signing_key()
{
var key = CryptoHelper.CreateECDsaSecurityKey(JsonWebKeyECTypes.P256);
var expectedAlgorithm = SecurityAlgorithms.EcdsaSha256;
var pipeline = new IdentityServerPipeline();
pipeline.OnPostConfigureServices += services =>
{
// add key to standard RSA key
services.AddIdentityServerBuilder()
.AddSigningCredential(key, expectedAlgorithm);
};
pipeline.Initialize();
var result = await pipeline.BackChannelClient.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
var algorithmsSupported = result.IntrospectionSigningAlgorithmsSupported;
algorithmsSupported.Count().ShouldBe(2);
algorithmsSupported.ShouldContain(SecurityAlgorithms.RsaSha256);
algorithmsSupported.ShouldContain(SecurityAlgorithms.EcdsaSha256);
}
[Fact]
[Trait("Category", Category)]
public async Task Introspection_signing_algorithms_supported_should_not_be_present_if_introspection_endpoint_disabled()
{
var key = CryptoHelper.CreateECDsaSecurityKey(JsonWebKeyECTypes.P256);
var expectedAlgorithm = SecurityAlgorithms.EcdsaSha256;
var pipeline = new IdentityServerPipeline();
pipeline.OnPostConfigureServices += services =>
{
// add key to standard RSA key
services.AddIdentityServerBuilder()
.AddSigningCredential(key, expectedAlgorithm);
};
pipeline.Initialize();
pipeline.Options.Endpoints.EnableIntrospectionEndpoint = false;
var result = await pipeline.BackChannelClient.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
result.IntrospectionSigningAlgorithmsSupported.ShouldBeEmpty();
}
[Fact]
[Trait("Category", Category)]
@ -216,7 +308,6 @@ public class DiscoveryEndpointTests
result.Issuer.ShouldBe("https://грант.рф");
}
[Fact]
[Trait("Category", Category)]
public async Task prompt_values_supported_should_contain_defaults()
@ -376,6 +467,51 @@ public class DiscoveryEndpointTests
var result = await pipeline.BackChannelClient.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
result.MtlsEndpointAliases.PushedAuthorizationRequestEndpoint.ShouldNotBeNull();
}
[Fact]
[Trait("Category", Category)]
public async Task registration_endpoint_should_be_custom_when_static_type_and_custom_endpoint_set()
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
pipeline.Options.Discovery.DynamicClientRegistration.RegistrationEndpointMode = RegistrationEndpointMode.Static;
pipeline.Options.Discovery.DynamicClientRegistration.StaticRegistrationEndpoint = new Uri("https://custom.example.com/register");
var result = await pipeline.BackChannelClient.GetAsync("https://server/.well-known/openid-configuration");
var json = await result.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json);
data.ShouldContainKey(OidcConstants.Discovery.RegistrationEndpoint);
data[OidcConstants.Discovery.RegistrationEndpoint].GetString().ShouldBe("https://custom.example.com/register");
}
[Fact]
[Trait("Category", Category)]
public async Task registration_endpoint_should_be_default_when_dynamic_type()
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
pipeline.Options.Discovery.DynamicClientRegistration.RegistrationEndpointMode = RegistrationEndpointMode.Inferred;
var result = await pipeline.BackChannelClient.GetAsync("https://server/.well-known/openid-configuration");
var json = await result.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json);
data.ShouldContainKey(OidcConstants.Discovery.RegistrationEndpoint);
data[OidcConstants.Discovery.RegistrationEndpoint].GetString().ShouldBe("https://server/connect/dcr");
}
[Fact]
[Trait("Category", Category)]
public async Task registration_endpoint_should_not_be_present_when_none_type()
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
pipeline.Options.Discovery.DynamicClientRegistration.RegistrationEndpointMode = RegistrationEndpointMode.None;
var result = await pipeline.BackChannelClient.GetAsync("https://server/.well-known/openid-configuration");
var json = await result.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json);
data.ShouldNotContainKey(OidcConstants.Discovery.RegistrationEndpoint);
}
}
class DiscoCustomizaztion : IHttpResponseWriter<DiscoveryDocumentResult>

View file

@ -0,0 +1,26 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
using Duende.IdentityServer.IntegrationTests.Common;
using Microsoft.Extensions.DependencyInjection;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Discovery;
public abstract class DiscoveryEndpointTestsBase
{
protected static IdentityServerPipeline CreatePipelineWithJwtBearer()
{
var pipeline = new IdentityServerPipeline();
pipeline.OnPostConfigureServices += svcs =>
svcs.AddIdentityServerBuilder().AddJwtBearerClientAuthentication();
pipeline.Initialize();
return pipeline;
}
public static IEnumerable<object[]> NullOrEmptySupportedAlgorithms() =>
new List<object[]>
{
new object[] { Enumerable.Empty<string>() },
new object[] { null }
};
}

View file

@ -0,0 +1,74 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
using Duende.IdentityModel;
using Duende.IdentityModel.Client;
using Duende.IdentityServer.IntegrationTests.Common;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Discovery;
public class DiscoveryEndpointTests_introspection_endpoint_auth_methods_supported : DiscoveryEndpointTestsBase
{
private const string Category = "Discovery endpoint - introspection_endpoint_auth_methods_supported";
[Fact]
[Trait("Category", Category)]
public async Task introspection_endpoint_auth_methods_supported_should_default_to_basic_auth_and_post_body()
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
pipeline.Options.MutualTls.Enabled = false;
var disco = await pipeline.BackChannelClient
.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
disco.IsError.ShouldBeFalse();
var authMethodsSupported = disco.IntrospectionEndpointAuthenticationMethodsSupported;
authMethodsSupported.Count().ShouldBe(2);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.BasicAuthentication);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.PostBody);
}
[Fact]
[Trait("Category", Category)]
public async Task introspection_endpoint_auth_methods_supported_should_include_tls_client_and_self_signed_tls_client_when_mtls_is_enabled()
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
pipeline.Options.MutualTls.Enabled = true;
var disco = await pipeline.BackChannelClient
.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
disco.IsError.ShouldBeFalse();
var authMethodsSupported = disco.IntrospectionEndpointAuthenticationMethodsSupported;
authMethodsSupported.Count().ShouldBe(4);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.BasicAuthentication);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.PostBody);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.TlsClientAuth);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.SelfSignedTlsClientAuth);
}
[Fact]
[Trait("Category", Category)]
public async Task introspection_endpoint_auth_methods_supported_should_include_private_key_jwt_when_jwt_bearer_is_enabled()
{
var pipeline = CreatePipelineWithJwtBearer();
pipeline.Initialize();
pipeline.Options.MutualTls.Enabled = false;
var disco = await pipeline.BackChannelClient
.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
disco.IsError.ShouldBeFalse();
var authMethodsSupported = disco.IntrospectionEndpointAuthenticationMethodsSupported;
authMethodsSupported.Count().ShouldBe(3);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.BasicAuthentication);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.PostBody);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.PrivateKeyJwt);
}
}

View file

@ -0,0 +1,105 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
using System.Text.Json;
using Duende.IdentityModel;
using Duende.IdentityModel.Client;
using Duende.IdentityServer.IntegrationTests.Common;
using Microsoft.IdentityModel.Tokens;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Discovery;
public class DiscoveryEndpointTests_introspection_endpoint_auth_signing_algs_supported : DiscoveryEndpointTestsBase
{
private const string Category = "Discovery endpoint - introspection_endpoint_auth_signing_alg_values_supported";
[Fact]
[Trait("Category", Category)]
public async Task introspection_endpoint_auth_signing_alg_values_supported_should_match_configuration()
{
var pipeline = CreatePipelineWithJwtBearer();
pipeline.Options.SupportedClientAssertionSigningAlgorithms =
[
SecurityAlgorithms.RsaSsaPssSha256,
SecurityAlgorithms.EcdsaSha256
];
pipeline.Options.Discovery.ShowIntrospectionEndpointAuthenticationMethods = true;
var disco = await pipeline.BackChannelClient
.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
disco.IsError.ShouldBeFalse();
var algorithmsSupported = disco.IntrospectionEndpointAuthenticationSigningAlgorithmsSupported;
algorithmsSupported.Count().ShouldBe(2);
algorithmsSupported.ShouldContain(SecurityAlgorithms.RsaSsaPssSha256);
algorithmsSupported.ShouldContain(SecurityAlgorithms.EcdsaSha256);
}
[Fact]
[Trait("Category", Category)]
public async Task introspection_endpoint_auth_signing_alg_values_supported_should_default_to_rs_ps_es()
{
var pipeline = CreatePipelineWithJwtBearer();
pipeline.Options.Discovery.ShowIntrospectionEndpointAuthenticationMethods = true;
var result =
await pipeline.BackChannelClient.GetDiscoveryDocumentAsync(
"https://server/.well-known/openid-configuration");
result.IsError.ShouldBeFalse();
var algorithmsSupported = result.IntrospectionEndpointAuthenticationSigningAlgorithmsSupported;
algorithmsSupported.ShouldBe([
SecurityAlgorithms.RsaSha256,
SecurityAlgorithms.RsaSha384,
SecurityAlgorithms.RsaSha512,
SecurityAlgorithms.RsaSsaPssSha384,
SecurityAlgorithms.RsaSsaPssSha512,
SecurityAlgorithms.RsaSsaPssSha256,
SecurityAlgorithms.EcdsaSha256,
SecurityAlgorithms.EcdsaSha384,
SecurityAlgorithms.EcdsaSha512,
], ignoreOrder: true);
}
[Fact]
[Trait("Category", Category)]
public async Task introspection_endpoint_auth_signing_alg_values_supported_should_not_be_present_if_private_key_jwt_is_not_configured()
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
pipeline.Options.SupportedClientAssertionSigningAlgorithms = [SecurityAlgorithms.RsaSha256];
pipeline.Options.Discovery.ShowIntrospectionEndpointAuthenticationMethods = true;
var disco = await pipeline.BackChannelClient
.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
// Verify assumptions
disco.IsError.ShouldBeFalse();
disco.IntrospectionEndpointAuthenticationMethodsSupported.ShouldNotContain("private_key_jwt");
disco.IntrospectionEndpointAuthenticationMethodsSupported.ShouldNotContain("client_secret_jwt");
// Assert that we got no signing algs.
disco.IntrospectionEndpointAuthenticationSigningAlgorithmsSupported.ShouldBeEmpty();
}
[Theory]
[MemberData(nameof(NullOrEmptySupportedAlgorithms))]
[Trait("Category", Category)]
public async Task introspection_endpoint_auth_signing_alg_values_supported_should_not_be_present_if_option_is_null_or_empty(
ICollection<string> algorithms)
{
var pipeline = CreatePipelineWithJwtBearer();
pipeline.Options.SupportedClientAssertionSigningAlgorithms = algorithms;
pipeline.Options.Discovery.ShowIntrospectionEndpointAuthenticationMethods = true;
var result = await pipeline.BackChannelClient
.GetAsync("https://server/.well-known/openid-configuration");
var json = await result.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json);
data.ShouldNotContainKey(OidcConstants.Discovery.IntrospectionEndpointAuthSigningAlgorithmsSupported);
}
}

View file

@ -0,0 +1,73 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
using Duende.IdentityModel;
using Duende.IdentityModel.Client;
using Duende.IdentityServer.IntegrationTests.Common;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Discovery;
public class DiscoveryEndpointTests_revocation_endpoint_auth_methods_supported : DiscoveryEndpointTestsBase
{
private const string Category = "Discovery endpoint - revocation_endpoint_auth_methods_supported";
[Fact]
[Trait("Category", Category)]
public async Task revocation_endpoint_auth_methods_supported_should_default_to_basic_auth_and_post_body()
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
pipeline.Options.MutualTls.Enabled = false;
var disco = await pipeline.BackChannelClient
.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
disco.IsError.ShouldBeFalse();
var authMethodsSupported = disco.RevocationEndpointAuthenticationMethodsSupported;
authMethodsSupported.Count().ShouldBe(2);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.BasicAuthentication);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.PostBody);
}
[Fact]
[Trait("Category", Category)]
public async Task revocation_endpoint_auth_methods_supported_should_include_tls_client_and_self_signed_tls_client_when_mtls_is_enabled()
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
pipeline.Options.MutualTls.Enabled = true;
var disco = await pipeline.BackChannelClient
.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
disco.IsError.ShouldBeFalse();
var authMethodsSupported = disco.RevocationEndpointAuthenticationMethodsSupported;
authMethodsSupported.Count().ShouldBe(4);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.BasicAuthentication);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.PostBody);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.TlsClientAuth);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.SelfSignedTlsClientAuth);
}
[Fact]
[Trait("Category", Category)]
public async Task revocation_endpoint_auth_methods_supported_should_include_private_key_jwt_when_jwt_bearer_is_enabled()
{
var pipeline = CreatePipelineWithJwtBearer();
pipeline.Initialize();
pipeline.Options.MutualTls.Enabled = false;
var disco = await pipeline.BackChannelClient
.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
disco.IsError.ShouldBeFalse();
var authMethodsSupported = disco.RevocationEndpointAuthenticationMethodsSupported;
authMethodsSupported.Count().ShouldBe(3);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.BasicAuthentication);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.PostBody);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.PrivateKeyJwt);
}
}

View file

@ -0,0 +1,98 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
using System.Text.Json;
using Duende.IdentityModel;
using Duende.IdentityModel.Client;
using Duende.IdentityServer.IntegrationTests.Common;
using Microsoft.IdentityModel.Tokens;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Discovery;
public class DiscoveryEndpointTests_revocation_endpoint_auth_signing_algs_supported : DiscoveryEndpointTestsBase
{
private const string Category = "Discovery endpoint - revocation_endpoint_auth_signing_alg_values_supported";
[Fact]
[Trait("Category", Category)]
public async Task revocation_endpoint_auth_signing_alg_values_supported_should_match_configuration()
{
var pipeline = CreatePipelineWithJwtBearer();
pipeline.Options.SupportedClientAssertionSigningAlgorithms =
[
SecurityAlgorithms.RsaSsaPssSha256,
SecurityAlgorithms.EcdsaSha256
];
var disco = await pipeline.BackChannelClient
.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
disco.IsError.ShouldBeFalse();
var algorithmsSupported = disco.RevocationEndpointAuthenticationSigningAlgorithmsSupported;
algorithmsSupported.Count().ShouldBe(2);
algorithmsSupported.ShouldContain(SecurityAlgorithms.RsaSsaPssSha256);
algorithmsSupported.ShouldContain(SecurityAlgorithms.EcdsaSha256);
}
[Fact]
[Trait("Category", Category)]
public async Task revocation_endpoint_auth_signing_alg_values_supported_should_default_to_rs_ps_es()
{
var pipeline = CreatePipelineWithJwtBearer();
pipeline.Initialize();
var result =
await pipeline.BackChannelClient.GetDiscoveryDocumentAsync(
"https://server/.well-known/openid-configuration");
result.IsError.ShouldBeFalse();
var algorithmsSupported = result.RevocationEndpointAuthenticationSigningAlgorithmsSupported;
algorithmsSupported.ShouldBe([
SecurityAlgorithms.RsaSha256,
SecurityAlgorithms.RsaSha384,
SecurityAlgorithms.RsaSha512,
SecurityAlgorithms.RsaSsaPssSha384,
SecurityAlgorithms.RsaSsaPssSha512,
SecurityAlgorithms.RsaSsaPssSha256,
SecurityAlgorithms.EcdsaSha256,
SecurityAlgorithms.EcdsaSha384,
SecurityAlgorithms.EcdsaSha512,
], ignoreOrder: true);
}
[Fact]
[Trait("Category", Category)]
public async Task revocation_endpoint_auth_signing_alg_values_supported_should_not_be_present_if_private_key_jwt_is_not_configured()
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
pipeline.Options.SupportedClientAssertionSigningAlgorithms = [SecurityAlgorithms.RsaSha256];
var disco = await pipeline.BackChannelClient
.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
disco.IsError.ShouldBeFalse();
disco.RevocationEndpointAuthenticationMethodsSupported.ShouldNotContain("private_key_jwt");
disco.RevocationEndpointAuthenticationMethodsSupported.ShouldNotContain("client_secret_jwt");
disco.RevocationEndpointAuthenticationSigningAlgorithmsSupported.ShouldBeEmpty();
}
[Theory]
[MemberData(nameof(NullOrEmptySupportedAlgorithms))]
[Trait("Category", Category)]
public async Task revocation_endpoint_auth_signing_alg_values_supported_should_not_be_present_if_option_is_null_or_empty(
ICollection<string> algorithms)
{
var pipeline = CreatePipelineWithJwtBearer();
pipeline.Options.SupportedClientAssertionSigningAlgorithms = algorithms;
var result = await pipeline.BackChannelClient
.GetAsync("https://server/.well-known/openid-configuration");
var json = await result.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json);
data.ShouldNotContainKey(OidcConstants.Discovery.RevocationEndpointAuthSigningAlgorithmsSupported);
}
}

View file

@ -0,0 +1,74 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
using Duende.IdentityModel;
using Duende.IdentityModel.Client;
using Duende.IdentityServer.IntegrationTests.Common;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Discovery;
public class DiscoveryEndpointTests_token_endpoint_auth_methods_supported : DiscoveryEndpointTestsBase
{
private const string Category = "Discovery endpoint - token_endpoint_auth_methods_supported";
[Fact]
[Trait("Category", Category)]
public async Task token_endpoint_auth_methods_supported_should_default_to_basic_auth_and_post_body()
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
pipeline.Options.MutualTls.Enabled = false;
var disco = await pipeline.BackChannelClient
.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
disco.IsError.ShouldBeFalse();
var authMethodsSupported = disco.TokenEndpointAuthenticationMethodsSupported;
authMethodsSupported.Count().ShouldBe(2);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.BasicAuthentication);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.PostBody);
}
[Fact]
[Trait("Category", Category)]
public async Task token_endpoint_auth_methods_supported_should_include_tls_client_and_self_signed_tls_client_when_mtls_is_enabled()
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
pipeline.Options.MutualTls.Enabled = true;
var disco = await pipeline.BackChannelClient
.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
disco.IsError.ShouldBeFalse();
var authMethodsSupported = disco.TokenEndpointAuthenticationMethodsSupported;
authMethodsSupported.Count().ShouldBe(4);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.BasicAuthentication);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.PostBody);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.TlsClientAuth);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.SelfSignedTlsClientAuth);
}
[Fact]
[Trait("Category", Category)]
public async Task token_endpoint_auth_methods_supported_should_include_private_key_jwt_when_jwt_bearer_is_enabled()
{
var pipeline = CreatePipelineWithJwtBearer();
pipeline.Initialize();
pipeline.Options.MutualTls.Enabled = false;
var disco = await pipeline.BackChannelClient
.GetDiscoveryDocumentAsync("https://server/.well-known/openid-configuration");
disco.IsError.ShouldBeFalse();
var authMethodsSupported = disco.TokenEndpointAuthenticationMethodsSupported;
authMethodsSupported.Count().ShouldBe(3);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.BasicAuthentication);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.PostBody);
authMethodsSupported.ShouldContain(OidcConstants.EndpointAuthenticationMethods.PrivateKeyJwt);
}
}

View file

@ -5,12 +5,11 @@ using System.Text.Json;
using Duende.IdentityModel;
using Duende.IdentityModel.Client;
using Duende.IdentityServer.IntegrationTests.Common;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Discovery;
public class DiscoveryEndpointTests_token_endpoint_auth_signing_alg_values_supported
public class DiscoveryEndpointTests_token_endpoint_auth_signing_alg_values_supported : DiscoveryEndpointTestsBase
{
private const string Category = "Discovery endpoint - token_endpoint_auth_signing_alg_values_supported";
@ -18,10 +17,7 @@ public class DiscoveryEndpointTests_token_endpoint_auth_signing_alg_values_suppo
[Trait("Category", Category)]
public async Task token_endpoint_auth_signing_alg_values_supported_should_match_configuration()
{
var pipeline = new IdentityServerPipeline();
pipeline.OnPostConfigureServices += svcs =>
svcs.AddIdentityServerBuilder().AddJwtBearerClientAuthentication();
pipeline.Initialize();
var pipeline = CreatePipelineWithJwtBearer();
pipeline.Options.SupportedClientAssertionSigningAlgorithms =
[
SecurityAlgorithms.RsaSsaPssSha256,
@ -43,10 +39,7 @@ public class DiscoveryEndpointTests_token_endpoint_auth_signing_alg_values_suppo
[Trait("Category", Category)]
public async Task token_endpoint_auth_signing_alg_values_supported_should_default_to_rs_ps_es()
{
var pipeline = new IdentityServerPipeline();
pipeline.OnPostConfigureServices += svcs =>
svcs.AddIdentityServerBuilder().AddJwtBearerClientAuthentication();
pipeline.Initialize();
var pipeline = CreatePipelineWithJwtBearer();
var result =
await pipeline.BackChannelClient.GetDiscoveryDocumentAsync(
@ -95,10 +88,7 @@ public class DiscoveryEndpointTests_token_endpoint_auth_signing_alg_values_suppo
public async Task token_endpoint_auth_signing_alg_values_supported_should_not_be_present_if_option_is_null_or_empty(
ICollection<string> algorithms)
{
var pipeline = new IdentityServerPipeline();
pipeline.OnPostConfigureServices += svcs =>
svcs.AddIdentityServerBuilder().AddJwtBearerClientAuthentication();
pipeline.Initialize();
var pipeline = CreatePipelineWithJwtBearer();
pipeline.Options.SupportedClientAssertionSigningAlgorithms = algorithms;
var result = await pipeline.BackChannelClient
@ -108,11 +98,4 @@ public class DiscoveryEndpointTests_token_endpoint_auth_signing_alg_values_suppo
data.ShouldNotContainKey(OidcConstants.Discovery.TokenEndpointAuthSigningAlgorithmsSupported);
}
public static IEnumerable<object[]> NullOrEmptySupportedAlgorithms() =>
new List<object[]>
{
new object[] { Enumerable.Empty<string>() },
new object[] { null }
};
}

View file

@ -0,0 +1,101 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
using System.Text.Json;
using Duende.IdentityModel.Client;
using Duende.IdentityServer.IntegrationTests.Common;
using Microsoft.IdentityModel.Tokens;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Discovery;
public class DiscoveryEndpoint_backchannel_authentication_request_object_auth_signing_algs_supported_Tests : DiscoveryEndpointTestsBase
{
private const string Category = "Discovery endpoint - backchannel_authentication_request_signing_alg_values_supported";
[Fact]
[Trait("Category", Category)]
public async Task backchannel_authentication_request_signing_alg_values_supported_should_match_configuration()
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
pipeline.Options.SupportedRequestObjectSigningAlgorithms =
[
SecurityAlgorithms.RsaSsaPssSha256,
SecurityAlgorithms.EcdsaSha256
];
var result =
await pipeline.BackChannelClient.GetDiscoveryDocumentAsync(
"https://server/.well-known/openid-configuration");
var algorithmsSupported = result.BackchannelAuthenticationRequestSigningAlgValuesSupported;
algorithmsSupported.Count().ShouldBe(2);
algorithmsSupported.ShouldContain(SecurityAlgorithms.RsaSsaPssSha256);
algorithmsSupported.ShouldContain(SecurityAlgorithms.EcdsaSha256);
}
[Theory]
[MemberData(nameof(NullOrEmptySupportedAlgorithms))]
[Trait("Category", Category)]
public async Task backchannel_authentication_request_signing_alg_values_supported_should_not_be_present_if_option_is_null_or_empty(
ICollection<string> algorithms)
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
pipeline.Options.SupportedRequestObjectSigningAlgorithms = algorithms;
var result = await pipeline.BackChannelClient
.GetAsync("https://server/.well-known/openid-configuration");
var json = await result.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json);
data.ShouldNotContainKey("backchannel_authentication_request_signing_alg_values_supported");
}
[Fact]
[Trait("Category", Category)]
public async Task
backchannel_authentication_request_signing_alg_values_supported_should_be_present_if_ciba_is_disabled()
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
pipeline.Options.Endpoints.EnableBackchannelAuthenticationEndpoint = false;
var result = await pipeline.BackChannelClient
.GetAsync("https://server/.well-known/openid-configuration");
var json = await result.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json);
data.ShouldNotContainKey("backchannel_authentication_request_signing_alg_values_supported");
}
[Fact]
[Trait("Category", Category)]
public async Task backchannel_authentication_request_signing_alg_values_supported_should_default_to_rs_ps_es()
{
var pipeline = new IdentityServerPipeline();
pipeline.Initialize();
var result =
await pipeline.BackChannelClient.GetDiscoveryDocumentAsync(
"https://server/.well-known/openid-configuration");
var algorithmsSupported = result.BackchannelAuthenticationRequestSigningAlgValuesSupported;
algorithmsSupported.ShouldBe([
SecurityAlgorithms.RsaSha256,
SecurityAlgorithms.RsaSha384,
SecurityAlgorithms.RsaSha512,
SecurityAlgorithms.RsaSsaPssSha384,
SecurityAlgorithms.RsaSsaPssSha512,
SecurityAlgorithms.RsaSsaPssSha256,
SecurityAlgorithms.EcdsaSha256,
SecurityAlgorithms.EcdsaSha384,
SecurityAlgorithms.EcdsaSha512,
], ignoreOrder: true);
}
}

View file

@ -8,7 +8,7 @@ using Microsoft.IdentityModel.Tokens;
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Discovery;
public class DiscoveryEndpoint_request_object_auth_signing_algs_supported_Tests
public class DiscoveryEndpoint_request_object_auth_signing_algs_supported_Tests : DiscoveryEndpointTestsBase
{
private const string Category = "Discovery endpoint - request_object_signing_algs_supported";
@ -77,11 +77,4 @@ public class DiscoveryEndpoint_request_object_auth_signing_algs_supported_Tests
SecurityAlgorithms.EcdsaSha512,
], ignoreOrder: true);
}
public static IEnumerable<object[]> NullOrEmptySupportedAlgorithms() =>
new List<object[]>
{
new object[] { Enumerable.Empty<string>() },
new object[] { null }
};
}