diff --git a/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs b/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs
index 60bc76e5..83e4ac5b 100644
--- a/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs
+++ b/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs
@@ -1,4 +1,5 @@
-using System.Text;
+using System.Globalization;
+using System.Text;
using System.Xml.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
@@ -34,24 +35,22 @@ namespace OpenIddict.Client.WebIntegration.Generators
"OpenIddictClientWebIntegrationConstants.generated.cs",
SourceText.From(GenerateConstants(document), Encoding.UTF8));
- context.AddSource(
- "OpenIddictClientWebIntegrationEnvironments.generated.cs",
- SourceText.From(GenerateEnvironments(document), Encoding.UTF8));
-
context.AddSource(
"OpenIddictClientWebIntegrationHelpers.generated.cs",
SourceText.From(GenerateHelpers(document), Encoding.UTF8));
context.AddSource(
- "OpenIddictClientWebIntegrationSettings.generated.cs",
- SourceText.From(GenerateSettings(document), Encoding.UTF8));
+ "OpenIddictClientWebIntegrationOptions.generated.cs",
+ SourceText.From(GenerateOptions(document), Encoding.UTF8));
static string GenerateBuilderMethods(XDocument document)
{
var template = Template.Parse(@"#nullable enable
+using System.ComponentModel;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
using OpenIddict.Client;
using OpenIddict.Client.WebIntegration;
using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants;
@@ -62,27 +61,162 @@ public partial class OpenIddictClientWebIntegrationBuilder
{
{{~ for provider in providers ~}}
///
- /// Enables {{ provider.name }} integration using the specified settings.
+ /// Enables {{ provider.name }} integration using the specified options.
+ {{~ if provider.documentation ~}}
+ /// For more information, visit the official website.
///
+ {{~ end ~}}
+ /// This extension can be safely called multiple times.
+ /// The .
+ public OpenIddictClientWebIntegrationBuilder.{{ provider.name }} Use{{ provider.name }}()
+ {
+ // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
+ Services.TryAddEnumerable(new[]
+ {
+ ServiceDescriptor.Singleton<
+ IConfigureOptions, OpenIddictClientWebIntegrationConfiguration.{{ provider.name }}>(),
+ ServiceDescriptor.Singleton<
+ IPostConfigureOptions, OpenIddictClientWebIntegrationConfiguration.{{ provider.name }}>()
+ });
+
+ return new OpenIddictClientWebIntegrationBuilder.{{ provider.name }}(Services);
+ }
+
+ ///
+ /// Enables {{ provider.name }} integration using the specified options.
{{~ if provider.documentation ~}}
- ///
- /// For more information about {{ provider.name }} integration, visit the official website.
- ///
+ /// For more information, visit the official website.
+ ///
{{~ end ~}}
- /// The provider settings.
+ /// This extension can be safely called multiple times.
+ /// The delegate used to configure the OpenIddict/{{ provider.name }} options.
/// The .
- public OpenIddictClientWebIntegrationBuilder Add{{ provider.name }}(OpenIddictClientWebIntegrationSettings.{{ provider.name }} settings)
+ public OpenIddictClientWebIntegrationBuilder Use{{ provider.name }}(Action configuration)
{
- if (settings is null)
+ if (configuration is null)
{
- throw new ArgumentNullException(nameof(settings));
+ throw new ArgumentNullException(nameof(configuration));
}
- // Note: TryAddEnumerable() is used here to ensure the initializer is registered only once.
- Services.TryAddEnumerable(ServiceDescriptor.Singleton<
- IConfigureOptions, OpenIddictClientWebIntegrationConfiguration.{{ provider.name }}>());
+ configuration(Use{{ provider.name }}());
- return Configure(options => options.Providers.Add(new OpenIddictClientWebIntegrationProvider(Providers.{{ provider.name }}, settings)));
+ return this;
+ }
+ {{~ end ~}}
+
+ {{~ for provider in providers ~}}
+ ///
+ /// Exposes the necessary methods required to configure the {{ provider.name }} integration.
+ ///
+ public class {{ provider.name }}
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The services collection.
+ public {{ provider.name }}(IServiceCollection services)
+ => Services = services ?? throw new ArgumentNullException(nameof(services));
+
+ ///
+ /// Gets the services collection.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public IServiceCollection Services { get; }
+
+ ///
+ /// Amends the default OpenIddict client {{ provider.name }} configuration.
+ ///
+ /// The delegate used to configure the OpenIddict options.
+ /// This extension can be safely called multiple times.
+ /// The .
+ public {{ provider.name }} Configure(Action configuration)
+ {
+ if (configuration is null)
+ {
+ throw new ArgumentNullException(nameof(configuration));
+ }
+
+ Services.Configure(configuration);
+
+ return this;
+ }
+
+ ///
+ /// Sets the client identifier.
+ ///
+ /// The client identifier.
+ /// The .
+ public {{ provider.name }} SetClientId(string identifier)
+ => Configure(options => options.ClientId = identifier);
+
+ ///
+ /// Sets the client secret, if applicable.
+ ///
+ /// The client secret.
+ /// The .
+ public {{ provider.name }} SetClientSecret(string secret)
+ => Configure(options => options.ClientSecret = secret);
+
+ ///
+ /// Sets the redirection URI, if applicable.
+ ///
+ /// The redirection URI.
+ /// The .
+ public {{ provider.name }} SetRedirectUri(Uri? address)
+ => Configure(options => options.RedirectUri = address);
+
+ ///
+ /// Sets the redirection URI, if applicable.
+ ///
+ /// The redirection URI.
+ /// The .
+ public {{ provider.name }} SetRedirectUri(string? address)
+ => SetRedirectUri(!string.IsNullOrEmpty(address) ? new Uri(address, UriKind.RelativeOrAbsolute) : null);
+
+ ///
+ /// Adds one or more scopes to the list of requested scopes, if applicable.
+ ///
+ /// The scopes.
+ /// The .
+ public {{ provider.name }} AddScopes(params string[] scopes)
+ => Configure(options => options.Scopes.UnionWith(scopes));
+
+ {{~ for environment in provider.environments ~}}
+ ///
+ /// Configures the provider to use the ""{{ environment.name }}"" environment.
+ ///
+ /// The .
+ public {{ provider.name }} Use{{ environment.name }}Environment()
+ => Configure(options => options.Environment = OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }});
+ {{~ end ~}}
+
+ {{~ for setting in provider.settings ~}}
+ {{~ if setting.description ~}}
+ ///
+ /// Configures {{ setting.description }}.
+ ///
+ {{~ end ~}}
+ {{~ if setting.collection ~}}
+ public {{ provider.name }} Add{{ setting.name }}(params {{ setting.clr_type }}[] values)
+ => Configure(options => options.{{ setting.name }}.UnionWith(values));
+ {{~ else ~}}
+ public {{ provider.name }} Set{{ setting.name }}({{ setting.clr_type }}? value)
+ => Configure(options => options.{{ setting.name }} = value);
+ {{~ end ~}}
+
+ {{~ end ~}}
+
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object? obj) => base.Equals(obj);
+
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode() => base.GetHashCode();
+
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string? ToString() => base.ToString();
}
{{~ end ~}}
}
@@ -93,7 +227,39 @@ public partial class OpenIddictClientWebIntegrationBuilder
.Select(provider => new
{
Name = (string) provider.Attribute("Name"),
- Documentation = (string?) provider.Attribute("Documentation")
+ Documentation = (string?) provider.Attribute("Documentation"),
+
+ Environments = provider.Elements("Environment").Select(environment => new
+ {
+ Name = (string?) environment.Attribute("Name") ?? "Production"
+ })
+ .ToList(),
+
+ Settings = provider.Elements("Setting").Select(setting => new
+ {
+ Name = (string) setting.Attribute("Name"),
+ Collection = (bool?) setting.Attribute("Collection") ?? false,
+ Description = (string) setting.Attribute("Description") is string description ?
+ char.ToLower(description[0], CultureInfo.GetCultureInfo("en-US")) + description.Substring(1) : null,
+ ClrType = (string) setting.Attribute("Type") switch
+ {
+ "EncryptionKey" when (string) setting.Element("EncryptionAlgorithm").Attribute("Value")
+ is "RS256" or "RS384" or "RS512" => "RsaSecurityKey",
+
+ "SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value")
+ is "ES256" or "ES384" or "ES512" => "ECDsaSecurityKey",
+
+ "SigningKey" when (string) setting.Element("SigningAlgorithm").Attribute("Value")
+ is "PS256" or "PS384" or "PS512" or
+ "RS256" or "RS384" or "RS512" => "RsaSecurityKey",
+
+ "String" => "string",
+ "StringHashSet" => "HashSet",
+
+ string value => value
+ }
+ })
+ .ToList()
})
.ToList()
});
@@ -107,6 +273,18 @@ namespace OpenIddict.Client.WebIntegration;
public static partial class OpenIddictClientWebIntegrationConstants
{
+ {{~ for provider in providers ~}}
+ public static class {{ provider.name }}
+ {
+ public static class Environments
+ {
+ {{~ for environment in provider.environments ~}}
+ public const string {{ environment.name }} = ""{{ environment.name }}"";
+ {{~ end ~}}
+ }
+ }
+ {{~ end ~}}
+
public static class Providers
{
{{~ for provider in providers ~}}
@@ -114,35 +292,6 @@ public static partial class OpenIddictClientWebIntegrationConstants
{{~ end ~}}
}
}
-");
- return template.Render(new
- {
- Providers = document.Root.Elements("Provider")
- .Select(provider => new { Name = (string) provider.Attribute("Name") })
- .ToList()
- });
- }
-
- static string GenerateEnvironments(XDocument document)
- {
- var template = Template.Parse(@"#nullable enable
-
-namespace OpenIddict.Client.WebIntegration;
-
-public partial class OpenIddictClientWebIntegrationEnvironments
-{
- {{~ for provider in providers ~}}
- ///
- /// Exposes the environments supported by the {{ provider.name }} provider.
- ///
- public enum {{ provider.name }}
- {
- {{~ for environment in provider.environments ~}}
- {{ environment.name }},
- {{~ end ~}}
- }
- {{~ end ~}}
-}
");
return template.Render(new
{
@@ -155,7 +304,7 @@ public partial class OpenIddictClientWebIntegrationEnvironments
{
Name = (string?) environment.Attribute("Name") ?? "Production"
})
- .ToList()
+ .ToList(),
})
.ToList()
});
@@ -165,6 +314,7 @@ public partial class OpenIddictClientWebIntegrationEnvironments
{
var template = Template.Parse(@"#nullable enable
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Client;
@@ -181,226 +331,224 @@ public partial class OpenIddictClientWebIntegrationConfiguration
///
/// Contains the methods required to register the {{ provider.name }} integration in the OpenIddict client options.
///
- public class {{ provider.name }} : IConfigureOptions
+ public class {{ provider.name }} : IConfigureOptions,
+ IPostConfigureOptions
{
- private readonly IOptions _options;
+ private readonly IServiceProvider _provider;
///
/// Creates a new instance of the class.
///
- /// The OpenIddict client web integration options.
- /// is null.
- public {{ provider.name }}(IOptions options)
- => _options = options ?? throw new ArgumentNullException(nameof(options));
+ /// The service provider.
+ /// is null.
+ public {{ provider.name }}(IServiceProvider provider)
+ => _provider = provider ?? throw new ArgumentNullException(nameof(provider));
///
- /// Ensures the {{ provider.name }} configuration is in a consistent and valid state
- /// and registers the {{ provider.name }} integration in the OpenIddict client options.
+ /// Ensures the {{ provider.name }} configuration is in a consistent and valid state.
///
+ /// The name of the options instance to configure, if applicable.
/// The options instance to initialize.
- public void Configure(OpenIddictClientOptions options)
+ public void PostConfigure(string name, OpenIddictClientWebIntegrationOptions.{{ provider.name }} options)
{
- foreach (var provider in _options.Value.Providers)
+ if (string.IsNullOrEmpty(options.ClientId))
{
- if (provider.Name is not Providers.{{ provider.name }})
- {
- continue;
- }
-
- if (provider.Settings is not OpenIddictClientWebIntegrationSettings.{{ provider.name }} settings)
- {
- throw new InvalidOperationException(SR.FormatID0331(Providers.{{ provider.name }}));
- }
-
- if (string.IsNullOrEmpty(settings.ClientId))
- {
- throw new InvalidOperationException(SR.FormatID0332(nameof(settings.ClientId), Providers.{{ provider.name }}));
- }
+ throw new InvalidOperationException(SR.FormatID0332(nameof(options.ClientId), Providers.{{ provider.name }}));
+ }
- if (settings.RedirectUri is null)
- {
- throw new InvalidOperationException(SR.FormatID0332(nameof(settings.RedirectUri), Providers.{{ provider.name }}));
- }
+ if (options.RedirectUri is null)
+ {
+ throw new InvalidOperationException(SR.FormatID0332(nameof(options.RedirectUri), Providers.{{ provider.name }}));
+ }
- {{~ for setting in provider.settings ~}}
- {{~ if setting.required ~}}
- {{~ if setting.type == 'String' ~}}
- if (string.IsNullOrEmpty(settings.{{ setting.name }}))
- {{~ else ~}}
- if (settings.{{ setting.name }} is null)
- {{~ end ~}}
- {
- throw new InvalidOperationException(SR.FormatID0332(nameof(settings.{{ setting.name }}), Providers.{{ provider.name }}));
- }
- {{~ end ~}}
- {{~ end ~}}
+ {{~ for setting in provider.settings ~}}
+ {{~ if setting.required ~}}
+ {{~ if setting.type == 'String' ~}}
+ if (string.IsNullOrEmpty(options.{{ setting.name }}))
+ {{~ else ~}}
+ if (options.{{ setting.name }} is null)
+ {{~ end ~}}
+ {
+ throw new InvalidOperationException(SR.FormatID0332(nameof(options.{{ setting.name }}), Providers.{{ provider.name }}));
+ }
+ {{~ end ~}}
+ {{~ end ~}}
- {{~ for environment in provider.environments ~}}
- if (settings.Environment is OpenIddictClientWebIntegrationEnvironments.{{ provider.name }}.{{ environment.name }})
+ {{~ for environment in provider.environments ~}}
+ if (options.Environment is OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }})
+ {
+ if (options.Scopes.Count is 0)
{
- if (settings.Scopes.Count is 0)
- {
- {{~ for scope in environment.scopes ~}}
- {{~ if scope.default && !scope.required ~}}
- settings.Scopes.Add(""{{ scope.name }}"");
- {{~ end ~}}
- {{~ end ~}}
- }
-
{{~ for scope in environment.scopes ~}}
- {{~ if scope.required ~}}
- settings.Scopes.Add(""{{ scope.name }}"");
+ {{~ if scope.default && !scope.required ~}}
+ options.Scopes.Add(""{{ scope.name }}"");
{{~ end ~}}
{{~ end ~}}
}
- {{~ end ~}}
- {{~ for setting in provider.settings ~}}
- {{~ if setting.default_value && setting.type == 'String' ~}}
- if (string.IsNullOrEmpty(settings.{{ setting.name }}))
- {
- settings.{{ setting.name }} = ""{{ setting.default_value }}"";
- }
+ {{~ for scope in environment.scopes ~}}
+ {{~ if scope.required ~}}
+ options.Scopes.Add(""{{ scope.name }}"");
{{~ end ~}}
{{~ end ~}}
+ }
+ {{~ end ~}}
- {{~ for setting in provider.settings ~}}
- {{~ if setting.collection ~}}
- if (settings.{{ setting.name }}.Count is 0)
- {
- {{~ for item in setting.collection_items ~}}
- {{~ if item.default && !item.required ~}}
- settings.{{ setting.name }}.Add(""{{ item.value }}"");
- {{~ end ~}}
- {{~ end ~}}
- }
- {{~ end ~}}
+ {{~ for setting in provider.settings ~}}
+ {{~ if setting.default_value && setting.type == 'String' ~}}
+ if (string.IsNullOrEmpty(options.{{ setting.name }}))
+ {
+ options.{{ setting.name }} = ""{{ setting.default_value }}"";
+ }
+ {{~ end ~}}
+ {{~ end ~}}
+ {{~ for setting in provider.settings ~}}
+ {{~ if setting.collection ~}}
+ if (options.{{ setting.name }}.Count is 0)
+ {
{{~ for item in setting.collection_items ~}}
- {{~ if item.required ~}}
- settings.{{ setting.name }}.Add(""{{ item.value }}"");
- {{~ end ~}}
+ {{~ if item.default && !item.required ~}}
+ options.{{ setting.name }}.Add(""{{ item.value }}"");
{{~ end ~}}
{{~ end ~}}
+ }
+ {{~ end ~}}
+
+ {{~ for item in setting.collection_items ~}}
+ {{~ if item.required ~}}
+ options.{{ setting.name }}.Add(""{{ item.value }}"");
+ {{~ end ~}}
+ {{~ end ~}}
+ {{~ end ~}}
+ }
- var formatter = Smart.CreateDefaultSmartFormat(new SmartSettings
+ ///
+ /// Registers the {{ provider.name }} integration in the OpenIddict client options.
+ ///
+ /// The options instance to initialize.
+ public void Configure(OpenIddictClientOptions options)
+ {
+ var formatter = Smart.CreateDefaultSmartFormat(new SmartSettings
+ {
+ CaseSensitivity = CaseSensitivityType.CaseInsensitive
+ });
+
+ // Resolve the provider options from the service provider and create a registration based on the specified settings.
+ var settings = _provider.GetRequiredService>().CurrentValue;
+
+ var registration = new OpenIddictClientRegistration
+ {
+ Issuer = settings.Environment switch
{
- CaseSensitivity = CaseSensitivityType.CaseInsensitive
- });
+ {{~ for environment in provider.environments ~}}
+ OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }}
+ => new Uri(formatter.Format(""{{ environment.issuer }}"", options), UriKind.Absolute),
+ {{~ end ~}}
+
+ _ => throw new InvalidOperationException(SR.FormatID0194(nameof(settings.Environment)))
+ },
- var registration = new OpenIddictClientRegistration
+ ClientId = settings.ClientId,
+ ClientSecret = settings.ClientSecret,
+ RedirectUri = settings.RedirectUri,
+
+ Configuration = settings.Environment switch
{
- Issuer = settings.Environment switch
+ {{~ for environment in provider.environments ~}}
+ {{~ if environment.configuration ~}}
+ OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }} => new OpenIddictConfiguration
{
- {{~ for environment in provider.environments ~}}
- OpenIddictClientWebIntegrationEnvironments.{{ provider.name }}.{{ environment.name }}
- => new Uri(formatter.Format(""{{ environment.issuer }}"", settings), UriKind.Absolute),
+ {{~ if environment.configuration.authorization_endpoint ~}}
+ AuthorizationEndpoint = new Uri(formatter.Format(""{{ environment.configuration.authorization_endpoint }}"", options), UriKind.Absolute),
{{~ end ~}}
- _ => throw new InvalidOperationException(SR.FormatID0194(nameof(settings.Environment)))
- },
+ {{~ if environment.configuration.token_endpoint ~}}
+ TokenEndpoint = new Uri(formatter.Format(""{{ environment.configuration.token_endpoint }}"", options), UriKind.Absolute),
+ {{~ end ~}}
- ClientId = settings.ClientId,
- ClientSecret = settings.ClientSecret,
- RedirectUri = settings.RedirectUri,
+ {{~ if environment.configuration.userinfo_endpoint ~}}
+ UserinfoEndpoint = new Uri(formatter.Format(""{{ environment.configuration.userinfo_endpoint }}"", options), UriKind.Absolute),
+ {{~ end ~}}
- Configuration = settings.Environment switch
- {
- {{~ for environment in provider.environments ~}}
- {{~ if environment.configuration ~}}
- OpenIddictClientWebIntegrationEnvironments.{{ provider.name }}.{{ environment.name }} => new OpenIddictConfiguration
+ CodeChallengeMethodsSupported =
{
- {{~ if environment.configuration.authorization_endpoint ~}}
- AuthorizationEndpoint = new Uri(formatter.Format(""{{ environment.configuration.authorization_endpoint }}"", settings), UriKind.Absolute),
+ {{~ for method in environment.configuration.code_challenge_methods_supported ~}}
+ ""{{ method }}"",
{{~ end ~}}
+ },
- {{~ if environment.configuration.token_endpoint ~}}
- TokenEndpoint = new Uri(formatter.Format(""{{ environment.configuration.token_endpoint }}"", settings), UriKind.Absolute),
+ GrantTypesSupported =
+ {
+ {{~ for type in environment.configuration.grant_types_supported ~}}
+ ""{{ type }}"",
{{~ end ~}}
+ },
- {{~ if environment.configuration.userinfo_endpoint ~}}
- UserinfoEndpoint = new Uri(formatter.Format(""{{ environment.configuration.userinfo_endpoint }}"", settings), UriKind.Absolute),
+ ResponseModesSupported =
+ {
+ {{~ for mode in environment.configuration.response_modes_supported ~}}
+ ""{{ mode }}"",
{{~ end ~}}
+ },
- CodeChallengeMethodsSupported =
- {
- {{~ for method in environment.configuration.code_challenge_methods_supported ~}}
- ""{{ method }}"",
- {{~ end ~}}
- },
-
- GrantTypesSupported =
- {
- {{~ for type in environment.configuration.grant_types_supported ~}}
- ""{{ type }}"",
- {{~ end ~}}
- },
-
- ResponseModesSupported =
- {
- {{~ for mode in environment.configuration.response_modes_supported ~}}
- ""{{ mode }}"",
- {{~ end ~}}
- },
-
- ResponseTypesSupported =
- {
- {{~ for type in environment.configuration.response_types_supported ~}}
- ""{{ type }}"",
- {{~ end ~}}
- },
-
- ScopesSupported =
- {
- {{~ for scope in environment.configuration.scopes_supported ~}}
- ""{{ scope }}"",
- {{~ end ~}}
- },
+ ResponseTypesSupported =
+ {
+ {{~ for type in environment.configuration.response_types_supported ~}}
+ ""{{ type }}"",
+ {{~ end ~}}
+ },
- TokenEndpointAuthMethodsSupported =
- {
- {{~ for method in environment.configuration.token_endpoint_auth_methods_supported ~}}
- ""{{ method }}"",
- {{~ end ~}}
- }
+ ScopesSupported =
+ {
+ {{~ for scope in environment.configuration.scopes_supported ~}}
+ ""{{ scope }}"",
+ {{~ end ~}}
},
- {{~ else ~}}
- OpenIddictClientWebIntegrationEnvironments.{{ provider.name }}.{{ environment.name }} => null,
- {{~ end ~}}
- {{~ end ~}}
- _ => throw new InvalidOperationException(SR.FormatID0194(nameof(settings.Environment)))
+ TokenEndpointAuthMethodsSupported =
+ {
+ {{~ for method in environment.configuration.token_endpoint_auth_methods_supported ~}}
+ ""{{ method }}"",
+ {{~ end ~}}
+ }
},
+ {{~ else ~}}
+ OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.{{ environment.name }} => null,
+ {{~ end ~}}
+ {{~ end ~}}
- EncryptionCredentials =
- {
- {{~ for setting in provider.settings ~}}
- {{~ if setting.type == 'EncryptionKey' ~}}
- new EncryptingCredentials(settings.{{ setting.name }}, ""{{ setting.encryption_algorithm }}"", SecurityAlgorithms.Aes256CbcHmacSha512),
- {{~ end ~}}
- {{~ end ~}}
- },
+ _ => throw new InvalidOperationException(SR.FormatID0194(nameof(settings.Environment)))
+ },
- SigningCredentials =
- {
- {{~ for setting in provider.settings ~}}
- {{~ if setting.type == 'SigningKey' ~}}
- new SigningCredentials(settings.{{ setting.name }}, ""{{ setting.signing_algorithm }}""),
- {{~ end ~}}
- {{~ end ~}}
- },
+ EncryptionCredentials =
+ {
+ {{~ for setting in provider.settings ~}}
+ {{~ if setting.type == 'EncryptionKey' ~}}
+ new EncryptingCredentials(settings.{{ setting.name }}, ""{{ setting.encryption_algorithm }}"", SecurityAlgorithms.Aes256CbcHmacSha512),
+ {{~ end ~}}
+ {{~ end ~}}
+ },
- Properties =
- {
- [Properties.ProviderName] = Providers.{{ provider.name }},
- [Properties.ProviderSettings] = settings
- }
- };
+ SigningCredentials =
+ {
+ {{~ for setting in provider.settings ~}}
+ {{~ if setting.type == 'SigningKey' ~}}
+ new SigningCredentials(settings.{{ setting.name }}, ""{{ setting.signing_algorithm }}""),
+ {{~ end ~}}
+ {{~ end ~}}
+ },
+
+ Properties =
+ {
+ [Properties.ProviderName] = Providers.{{ provider.name }},
+ [Properties.ProviderOptions] = settings
+ }
+ };
- registration.Scopes.UnionWith(settings.Scopes);
+ registration.Scopes.UnionWith(settings.Scopes);
- options.Registrations.Add(registration);
- }
+ options.Registrations.Add(registration);
}
}
{{~ end ~}}
@@ -529,14 +677,15 @@ public partial class OpenIddictClientWebIntegrationHelpers
{
{{~ for provider in providers ~}}
///
- /// Resolves the {{ provider.name }} provider settings from the specified registration.
+ /// Resolves the {{ provider.name }} provider options from the specified registration.
///
/// The client registration.
- /// The {{ provider.name }} provider settings.
- /// The provider settings cannot be resolved.
- public static OpenIddictClientWebIntegrationSettings.{{ provider.name }} Get{{ provider.name }}Settings(this OpenIddictClientRegistration registration)
- => registration.GetProviderSettings() ??
+ /// The {{ provider.name }} provider options.
+ /// The provider options cannot be resolved.
+ public static OpenIddictClientWebIntegrationOptions.{{ provider.name }} Get{{ provider.name }}Options(this OpenIddictClientRegistration registration)
+ => registration.GetProviderOptions() ??
throw new InvalidOperationException(SR.FormatID0333(Providers.{{ provider.name }}));
+
{{~ end ~}}
}
");
@@ -548,7 +697,7 @@ public partial class OpenIddictClientWebIntegrationHelpers
});
}
- static string GenerateSettings(XDocument document)
+ static string GenerateOptions(XDocument document)
{
var template = Template.Parse(@"#nullable enable
@@ -556,18 +705,43 @@ using Microsoft.IdentityModel.Tokens;
namespace OpenIddict.Client.WebIntegration;
-public partial class OpenIddictClientWebIntegrationSettings
+public partial class OpenIddictClientWebIntegrationOptions
{
{{~ for provider in providers ~}}
///
- /// Provides various settings needed to configure the {{ provider.name }} integration.
+ /// Provides various options needed to configure the {{ provider.name }} integration.
///
- public class {{ provider.name }} : OpenIddictClientWebIntegrationSettings
+ public class {{ provider.name }}
{
+ ///
+ /// Gets or sets the client identifier.
+ ///
+ public string? ClientId { get; set; }
+
+ ///
+ /// Gets or sets the client secret, if applicable.
+ ///
+ public string? ClientSecret { get; set; }
+
+ ///
+ /// Gets or sets the redirection URL.
+ ///
+ public Uri? RedirectUri { get; set; }
+
+ ///
+ /// Gets the scopes requested to the authorization server.
+ ///
+ public HashSet Scopes { get; } = new(StringComparer.Ordinal);
+
+ ///
+ /// Gets or sets the environment that determines the endpoints to use (by default, ""Production"").
+ ///
+ public string? Environment { get; set; } = OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.Production;
+
{{~ for setting in provider.settings ~}}
{{~ if setting.description ~}}
///
- /// {{ setting.description }}
+ /// Gets or sets {{ setting.description }}.
///
{{~ end ~}}
{{~ if setting.collection ~}}
@@ -575,12 +749,8 @@ public partial class OpenIddictClientWebIntegrationSettings
{{~ else ~}}
public {{ setting.clr_type }}? {{ setting.name }} { get; set; }
{{~ end ~}}
- {{~ end ~}}
- ///
- /// Gets or sets the environment that determines the endpoints to use.
- ///
- public OpenIddictClientWebIntegrationEnvironments.{{ provider.name }} Environment { get; set; }
+ {{~ end ~}}
}
{{~ end ~}}
}
@@ -596,7 +766,8 @@ public partial class OpenIddictClientWebIntegrationSettings
{
Name = (string) setting.Attribute("Name"),
Collection = (bool?) setting.Attribute("Collection") ?? false,
- Description = (string) setting.Attribute("Description"),
+ Description = (string) setting.Attribute("Description") is string description ?
+ char.ToLower(description[0], CultureInfo.GetCultureInfo("en-US")) + description.Substring(1) : null,
ClrType = (string) setting.Attribute("Type") switch
{
"EncryptionKey" when (string) setting.Element("EncryptionAlgorithm").Attribute("Value")
diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs
index 85136371..b724e927 100644
--- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs
+++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs
@@ -92,8 +92,9 @@ namespace OpenIddict.Sandbox.AspNet.Client
.EnableRedirectionEndpointPassthrough()
.EnablePostLogoutRedirectionEndpointPassthrough();
- // Register the System.Net.Http integration.
- options.UseSystemNetHttp();
+ // Register the System.Net.Http integration and configure the HTTP options.
+ options.UseSystemNetHttp()
+ .SetProductInformation("DemoApp", "1.0.0");
// Add a client registration matching the client application definition in the server project.
options.AddRegistration(new OpenIddictClientRegistration
@@ -110,24 +111,24 @@ namespace OpenIddict.Sandbox.AspNet.Client
// Register the Web providers integrations.
options.UseWebProviders()
- .AddGitHub(new()
+ .UseGitHub(options =>
{
- ClientId = "c4ade52327b01ddacff3",
- ClientSecret = "da6bed851b75e317bf6b2cb67013679d9467c122",
- RedirectUri = new Uri("https://localhost:44378/callback/login/github", UriKind.Absolute)
+ options.SetClientId("c4ade52327b01ddacff3")
+ .SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122")
+ .SetRedirectUri("https://localhost:44378/callback/login/github");
})
- .AddGoogle(new()
+ .UseGoogle(options =>
{
- ClientId = "1016114395689-kgtgq2p6dj27d7v6e2kjkoj54dgrrckh.apps.googleusercontent.com",
- ClientSecret = "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",
- RedirectUri = new Uri("https://localhost:44378/callback/login/google", UriKind.Absolute),
- Scopes = { Scopes.Profile }
+ options.SetClientId("1016114395689-kgtgq2p6dj27d7v6e2kjkoj54dgrrckh.apps.googleusercontent.com")
+ .SetClientSecret("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")
+ .SetRedirectUri("https://localhost:44378/callback/login/google")
+ .AddScopes(Scopes.Profile);
})
- .AddTwitter(new()
+ .UseTwitter(options =>
{
- ClientId = "bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ",
- ClientSecret = "VcohOgBp-6yQCurngo4GAyKeZh0D6SUCCSjJgEo1uRzJarjIUS",
- RedirectUri = new Uri("https://localhost:44378/callback/login/twitter", UriKind.Absolute)
+ options.SetClientId("bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ")
+ .SetClientSecret("VcohOgBp-6yQCurngo4GAyKeZh0D6SUCCSjJgEo1uRzJarjIUS")
+ .SetRedirectUri("https://localhost:44378/callback/login/twitter");
});
});
diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs
index 3cc2078e..fcd0019b 100644
--- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs
+++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Startup.cs
@@ -140,16 +140,17 @@ namespace OpenIddict.Sandbox.AspNet.Server
options.UseOwin()
.EnableRedirectionEndpointPassthrough();
- // Register the System.Net.Http integration.
- options.UseSystemNetHttp();
+ // Register the System.Net.Http integration and configure the HTTP options.
+ options.UseSystemNetHttp()
+ .SetProductInformation("DemoApp", "1.0.0");
// Register the Web providers integrations.
options.UseWebProviders()
- .AddGitHub(new()
+ .UseGitHub(options =>
{
- ClientId = "c4ade52327b01ddacff3",
- ClientSecret = "da6bed851b75e317bf6b2cb67013679d9467c122",
- RedirectUri = new Uri("https://localhost:44349/callback/login/github", UriKind.Absolute)
+ options.SetClientId("c4ade52327b01ddacff3")
+ .SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122")
+ .SetRedirectUri("https://localhost:44349/callback/login/github");
});
})
diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs
index 889d1204..a8a59d91 100644
--- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs
+++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs
@@ -102,8 +102,9 @@ public class Startup
.EnableRedirectionEndpointPassthrough()
.EnablePostLogoutRedirectionEndpointPassthrough();
- // Register the System.Net.Http integration.
- options.UseSystemNetHttp();
+ // Register the System.Net.Http integration and configure the HTTP options.
+ options.UseSystemNetHttp()
+ .SetProductInformation("DemoApp", "1.0.0");
// Add a client registration matching the client application definition in the server project.
options.AddRegistration(new OpenIddictClientRegistration
@@ -120,32 +121,30 @@ public class Startup
// Register the Web providers integrations.
options.UseWebProviders()
- .AddGitHub(new()
+ .UseGitHub(options =>
{
- ClientId = "c4ade52327b01ddacff3",
- ClientSecret = "da6bed851b75e317bf6b2cb67013679d9467c122",
- RedirectUri = new Uri("https://localhost:44381/callback/login/github", UriKind.Absolute)
+ options.SetClientId("c4ade52327b01ddacff3")
+ .SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122")
+ .SetRedirectUri("https://localhost:44381/callback/login/github");
})
- .AddGoogle(new()
+ .UseGoogle(options =>
{
- ClientId = "1016114395689-kgtgq2p6dj27d7v6e2kjkoj54dgrrckh.apps.googleusercontent.com",
- ClientSecret = "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",
- RedirectUri = new Uri("https://localhost:44381/callback/login/google", UriKind.Absolute),
- Scopes = { Scopes.Profile }
+ options.SetClientId("1016114395689-kgtgq2p6dj27d7v6e2kjkoj54dgrrckh.apps.googleusercontent.com")
+ .SetClientSecret("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")
+ .SetRedirectUri("https://localhost:44381/callback/login/google")
+ .AddScopes(Scopes.Profile);
})
- .AddReddit(new()
+ .UseReddit(options =>
{
- ClientId = "vDLNqhrkwrvqHgnoBWF3og",
- ClientSecret = "Tpab28Dz0upyZLqn7AN3GFD1O-zaAw",
- RedirectUri = new Uri("https://localhost:44381/callback/login/reddit", UriKind.Absolute),
- ProductName = "DemoApp",
- ProductVersion = "1.0.0"
+ options.SetClientId("vDLNqhrkwrvqHgnoBWF3og")
+ .SetClientSecret("Tpab28Dz0upyZLqn7AN3GFD1O-zaAw")
+ .SetRedirectUri("https://localhost:44381/callback/login/reddit");
})
- .AddTwitter(new()
+ .UseTwitter(options =>
{
- ClientId = "bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ",
- ClientSecret = "VcohOgBp-6yQCurngo4GAyKeZh0D6SUCCSjJgEo1uRzJarjIUS",
- RedirectUri = new Uri("https://localhost:44381/callback/login/twitter", UriKind.Absolute)
+ options.SetClientId("bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ")
+ .SetClientSecret("VcohOgBp-6yQCurngo4GAyKeZh0D6SUCCSjJgEo1uRzJarjIUS")
+ .SetRedirectUri("https://localhost:44381/callback/login/twitter");
});
});
diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs
index 1aacc6d1..895c7ea2 100644
--- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs
+++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs
@@ -1,3 +1,4 @@
+using System.Net.Http.Headers;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Sandbox.AspNetCore.Server.Models;
@@ -86,16 +87,17 @@ public class Startup
.EnableStatusCodePagesIntegration()
.EnableRedirectionEndpointPassthrough();
- // Register the System.Net.Http integration.
- options.UseSystemNetHttp();
+ // Register the System.Net.Http integration and configure the HTTP options.
+ options.UseSystemNetHttp()
+ .SetProductInformation("DemoApp", "1.0.0");
// Register the Web providers integrations.
options.UseWebProviders()
- .AddGitHub(new()
+ .UseGitHub(options =>
{
- ClientId = "c4ade52327b01ddacff3",
- ClientSecret = "da6bed851b75e317bf6b2cb67013679d9467c122",
- RedirectUri = new Uri("https://localhost:44395/callback/login/github", UriKind.Absolute)
+ options.SetClientId("c4ade52327b01ddacff3")
+ .SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122")
+ .SetRedirectUri("https://localhost:44395/callback/login/github");
});
})
diff --git a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreBuilder.cs b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreBuilder.cs
index 0c01b124..9b42b165 100644
--- a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreBuilder.cs
+++ b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreBuilder.cs
@@ -89,25 +89,15 @@ public class OpenIddictClientAspNetCoreBuilder
public OpenIddictClientAspNetCoreBuilder EnableStatusCodePagesIntegration()
=> Configure(options => options.EnableStatusCodePagesIntegration = true);
- ///
- /// Determines whether the specified object is equal to the current object.
- ///
- /// The object to compare with the current object.
- /// if the specified object is equal to the current object; otherwise, false.
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
- ///
- /// Serves as the default hash function.
- ///
- /// A hash code for the current object.
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
- ///
- /// Returns a string that represents the current object.
- ///
- /// A string that represents the current object.
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}
diff --git a/src/OpenIddict.Client.Owin/OpenIddictClientOwinBuilder.cs b/src/OpenIddict.Client.Owin/OpenIddictClientOwinBuilder.cs
index 2ede1cd2..e1ad39b4 100644
--- a/src/OpenIddict.Client.Owin/OpenIddictClientOwinBuilder.cs
+++ b/src/OpenIddict.Client.Owin/OpenIddictClientOwinBuilder.cs
@@ -81,25 +81,15 @@ public class OpenIddictClientOwinBuilder
public OpenIddictClientOwinBuilder EnableErrorPassthrough()
=> Configure(options => options.EnableErrorPassthrough = true);
- ///
- /// Determines whether the specified object is equal to the current object.
- ///
- /// The object to compare with the current object.
- /// if the specified object is equal to the current object; otherwise, false.
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
- ///
- /// Serves as the default hash function.
- ///
- /// A hash code for the current object.
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
- ///
- /// Returns a string that represents the current object.
- ///
- /// A string that represents the current object.
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}
diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs
index aa859f8d..d584ac34 100644
--- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs
+++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpBuilder.cs
@@ -5,6 +5,7 @@
*/
using System.ComponentModel;
+using System.Net.Http.Headers;
using OpenIddict.Client.SystemNetHttp;
using Polly;
@@ -51,28 +52,37 @@ public class OpenIddictClientSystemNetHttpBuilder
///
/// The HTTP Polly error policy.
/// The .
- public OpenIddictClientSystemNetHttpBuilder SetHttpErrorPolicy(IAsyncPolicy policy)
+ public OpenIddictClientSystemNetHttpBuilder SetHttpErrorPolicy(IAsyncPolicy? policy)
=> Configure(options => options.HttpErrorPolicy = policy);
///
- /// Determines whether the specified object is equal to the current object.
+ /// Sets the product information used in the user agent header that is attached
+ /// to the backchannel HTTP requests sent to the authorization server.
///
- /// The object to compare with the current object.
- /// if the specified object is equal to the current object; otherwise, false.
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override bool Equals(object? obj) => base.Equals(obj);
+ /// The product information.
+ /// The .
+ public OpenIddictClientSystemNetHttpBuilder SetProductInformation(ProductInfoHeaderValue? information)
+ => Configure(options => options.ProductInformation = information);
///
- /// Serves as the default hash function.
+ /// Sets the product information used in the user agent header that is attached
+ /// to the backchannel HTTP requests sent to the authorization server.
///
- /// A hash code for the current object.
+ /// The product name.
+ /// The product version.
+ /// The .
+ public OpenIddictClientSystemNetHttpBuilder SetProductInformation(string? name, string? version)
+ => SetProductInformation(!string.IsNullOrEmpty(name) ? new ProductInfoHeaderValue(name, version) : null);
+
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object? obj) => base.Equals(obj);
+
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
- ///
- /// Returns a string that represents the current object.
- ///
- /// A string that represents the current object.
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}
diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs
index f2c542de..ec339be5 100644
--- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs
+++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpConfiguration.cs
@@ -18,10 +18,10 @@ public class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptions
{
#if !SUPPORTS_SERVICE_PROVIDER_IN_HTTP_MESSAGE_HANDLER_BUILDER
- private readonly IServiceProvider _serviceProvider;
+ private readonly IServiceProvider _provider;
- public OpenIddictClientSystemNetHttpConfiguration(IServiceProvider serviceProvider)
- => _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
+ public OpenIddictClientSystemNetHttpConfiguration(IServiceProvider provider)
+ => _provider = provider ?? throw new ArgumentNullException(nameof(provider));
#endif
public void Configure(OpenIddictClientOptions options)
@@ -35,8 +35,7 @@ public class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptions Debug.Fail("This infrastructure method shouldn't be called.");
+ public void Configure(HttpClientFactoryOptions options) => Configure(Options.DefaultName, options);
public void Configure(string name, HttpClientFactoryOptions options)
{
@@ -45,8 +44,8 @@ public class OpenIddictClientSystemNetHttpConfiguration : IConfigureOptions>();
#else
- var options = _serviceProvider.GetRequiredService>();
+ var options = _provider.GetRequiredService>();
#endif
var policy = options.CurrentValue.HttpErrorPolicy;
if (policy is not null)
diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs
index 70871df3..a5432d47 100644
--- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs
+++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpHandlers.cs
@@ -11,6 +11,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Headers;
using System.Text;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpConstants;
@@ -114,6 +115,11 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
///
public class AttachUserAgent : IOpenIddictClientHandler where TContext : BaseExternalContext
{
+ private readonly IOptionsMonitor _options;
+
+ public AttachUserAgent(IOptionsMonitor options)
+ => _options = options ?? throw new ArgumentNullException(nameof(options));
+
///
/// Gets the default descriptor definition assigned to this handler.
///
@@ -140,9 +146,18 @@ public static partial class OpenIddictClientSystemNetHttpHandlers
var request = context.Transaction.GetHttpRequestMessage() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0173));
- var assembly = typeof(OpenIddictClientSystemNetHttpHandlers).Assembly.GetName();
+ // Some authorization servers are known to aggressively check user agents and encourage
+ // developers to use unique user agents. While a default user agent is always added,
+ // the default value doesn't differ accross applications. To reduce the risks of seeing
+ // requests blocked, a more specific user agent header can be configured by the developer.
+ // In this case, the value specified by the developer always appears first in the list.
+ if (_options.CurrentValue.ProductInformation is ProductInfoHeaderValue information)
+ {
+ request.Headers.UserAgent.Add(information);
+ }
// Attach a user agent based on the assembly version of the System.Net.Http integration.
+ var assembly = typeof(OpenIddictClientSystemNetHttpHandlers).Assembly.GetName();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(
productName: assembly.Name!,
productVersion: assembly.Version!.ToString()));
diff --git a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs
index c31806b3..10338513 100644
--- a/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs
+++ b/src/OpenIddict.Client.SystemNetHttp/OpenIddictClientSystemNetHttpOptions.cs
@@ -5,6 +5,7 @@
*/
using System.Net;
+using System.Net.Http.Headers;
using Polly;
using Polly.Extensions.Http;
@@ -18,8 +19,14 @@ public class OpenIddictClientSystemNetHttpOptions
///
/// Gets or sets the HTTP Polly error policy used by the internal OpenIddict HTTP clients.
///
- public IAsyncPolicy HttpErrorPolicy { get; set; }
+ public IAsyncPolicy? HttpErrorPolicy { get; set; }
= HttpPolicyExtensions.HandleTransientHttpError()
.OrResult(response => response.StatusCode == HttpStatusCode.NotFound)
.WaitAndRetryAsync(4, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
+
+ ///
+ /// Gets or sets the product information used in the user agent header that is
+ /// attached to the backchannel HTTP requests sent to the authorization server.
+ ///
+ public ProductInfoHeaderValue? ProductInformation { get; set; }
}
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConstants.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConstants.cs
index 3f48b474..7f8796cb 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConstants.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConstants.cs
@@ -16,6 +16,6 @@ public static partial class OpenIddictClientWebIntegrationConstants
public static class Properties
{
public const string ProviderName = ".provider_name";
- public const string ProviderSettings = ".provider_settings";
+ public const string ProviderOptions = ".provider_options";
}
}
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationEnvironments.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationEnvironments.cs
deleted file mode 100644
index 351a9786..00000000
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationEnvironments.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
- * See https://github.com/openiddict/openiddict-core for more information concerning
- * the license and the contributors participating to this project.
- */
-
-namespace OpenIddict.Client.WebIntegration;
-
-///
-/// Exposes the provider-specific environments supported by the OpenIddict client Web integration services.
-///
-public static partial class OpenIddictClientWebIntegrationEnvironments
-{
- // Note: environments are automatically generated by the source generator.
-}
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs
index 836b2e53..c8ecbe59 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs
@@ -53,8 +53,8 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// is replaced by this handler to always use "https://login.microsoftonline.com/common/v2.0".
if (context.Registration.GetProviderName() is Providers.Microsoft)
{
- var settings = context.Registration.GetMicrosoftSettings();
- if (string.Equals(settings.Tenant, "common", StringComparison.OrdinalIgnoreCase))
+ var options = context.Registration.GetMicrosoftOptions();
+ if (string.Equals(options.Tenant, "common", StringComparison.OrdinalIgnoreCase))
{
context.Response[Metadata.Issuer] = "https://login.microsoftonline.com/common/v2.0";
}
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs
deleted file mode 100644
index 8ed8c4df..00000000
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
- * See https://github.com/openiddict/openiddict-core for more information concerning
- * the license and the contributors participating to this project.
- */
-
-using System.Collections.Immutable;
-
-namespace OpenIddict.Client.WebIntegration;
-
-public static partial class OpenIddictClientWebIntegrationHandlers
-{
- public static class Exchange
- {
- public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create(
- /*
- * Token request preparation:
- */
- AddProductNameToUserAgentHeader.Descriptor);
- }
-}
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Protection.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Protection.cs
index ba509c1a..a488f401 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Protection.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Protection.cs
@@ -57,7 +57,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// that is associated with the client application. Since the tenant cannot be
// inferred when targeting the common tenant instance, issuer validation is disabled.
Providers.Microsoft when string.Equals(
- context.Registration.GetMicrosoftSettings().Tenant,
+ context.Registration.GetMicrosoftOptions().Tenant,
"common", StringComparison.OrdinalIgnoreCase)
=> false,
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
index 5330b5b1..eca50931 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
@@ -19,7 +19,6 @@ public static partial class OpenIddictClientWebIntegrationHandlers
/*
* Userinfo request preparation:
*/
- AddProductNameToUserAgentHeader.Descriptor,
AttachNonStandardFieldParameter.Descriptor,
/*
@@ -59,11 +58,11 @@ public static partial class OpenIddictClientWebIntegrationHandlers
if (context.Registration.GetProviderName() is Providers.Twitter)
{
- var settings = context.Registration.GetTwitterSettings();
+ var options = context.Registration.GetTwitterOptions();
- context.Request["expansions"] = string.Join(",", settings.Expansions);
- context.Request["tweet.fields"] = string.Join(",", settings.TweetFields);
- context.Request["user.fields"] = string.Join(",", settings.UserFields);
+ context.Request["expansions"] = string.Join(",", options.Expansions);
+ context.Request["tweet.fields"] = string.Join(",", options.TweetFields);
+ context.Request["user.fields"] = string.Join(",", options.UserFields);
}
return default;
@@ -97,7 +96,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
Debug.Assert(context.Response is not null, SR.GetResourceString(SR.ID4007));
// Some providers are known to wrap their userinfo payloads in top-level JSON nodes
- // (generally named "d", "data" or "content"), which prevents the default extraction
+ // (generally named "d", "data" or "response"), which prevents the default extraction
// logic from mapping the parameters to CLR claims. To work around that, this handler
// is responsible for extracting the nested payload and replacing the userinfo response.
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
index 2025d817..59625ba0 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
@@ -7,10 +7,7 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
-using System.Net.Http.Headers;
using System.Security.Claims;
-using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpHandlerFilters;
-using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpHandlers;
using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants;
namespace OpenIddict.Client.WebIntegration;
@@ -31,7 +28,6 @@ public static partial class OpenIddictClientWebIntegrationHandlers
AttachNonDefaultResponseMode.Descriptor,
FormatNonStandardScopeParameter.Descriptor)
.AddRange(Discovery.DefaultHandlers)
- .AddRange(Exchange.DefaultHandlers)
.AddRange(Protection.DefaultHandlers)
.AddRange(Userinfo.DefaultHandlers);
@@ -70,8 +66,8 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// see https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens.
if (context.Registration.GetProviderName() is Providers.Apple)
{
- var settings = context.Registration.GetAppleSettings();
- context.ClientAssertionTokenPrincipal.SetClaim(Claims.Private.Issuer, settings.TeamId);
+ var options = context.Registration.GetAppleOptions();
+ context.ClientAssertionTokenPrincipal.SetClaim(Claims.Private.Issuer, options.TeamId);
context.ClientAssertionTokenPrincipal.SetAudiences("https://appleid.apple.com");
}
@@ -200,55 +196,4 @@ public static partial class OpenIddictClientWebIntegrationHandlers
return default;
}
}
-
- ///
- /// Contains the logic responsible for enriching the user agent with an optional product name/product version.
- ///
- public class AddProductNameToUserAgentHeader : IOpenIddictClientHandler
- where TContext : BaseExternalContext
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictClientHandlerDescriptor Descriptor { get; }
- = OpenIddictClientHandlerDescriptor.CreateBuilder()
- .AddFilter()
- .UseSingletonHandler>()
- .SetOrder(AttachUserAgent.Descriptor.Order + 500)
- .SetType(OpenIddictClientHandlerType.BuiltIn)
- .Build();
-
- ///
- public ValueTask HandleAsync(TContext context)
- {
- if (context is null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved,
- // this may indicate that the request was incorrectly processed by another client stack.
- var request = context.Transaction.GetHttpRequestMessage() ??
- throw new InvalidOperationException(SR.GetResourceString(SR.ID0173));
-
- // A few providers (like Reddit) are known to aggressively check user agents and encourage
- // developers to use unique user agents. While OpenIddict itself always adds a user agent,
- // the default value doesn't differ accross applications. To reduce the risks of seeing
- // requests blocked by these providers, a more specific user agent header containing the
- // product name/version set by the user (or the client identifier if unset) is appended.
- var settings = context.Registration.GetProviderSettings();
- if (settings is not null)
- {
- var name = settings.ProductName ?? context.Registration.ClientId;
- if (!string.IsNullOrEmpty(name))
- {
- request.Headers.UserAgent.Add(new ProductInfoHeaderValue(
- productName: name,
- productVersion: settings.ProductVersion));
- }
- }
-
- return default;
- }
- }
}
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHelpers.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHelpers.cs
index d3d67587..35f95a9b 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHelpers.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHelpers.cs
@@ -32,35 +32,24 @@ public static partial class OpenIddictClientWebIntegrationHelpers
}
///
- /// Resolves the provider settings associated with the client registration or
- /// if no provider information is attached to the registration.
+ /// Resolves the provider options associated with the client registration or
+ /// if no provider information is attached to the registration or if
+ /// the actual setting information doesn't match the specified .
///
+ /// The type of the provider options.
/// The client registration.
- /// The provider settings, if applicable.
+ /// The provider options, if applicable.
/// is null.
- public static OpenIddictClientWebIntegrationSettings? GetProviderSettings(this OpenIddictClientRegistration registration)
+ public static TOptions? GetProviderOptions(this OpenIddictClientRegistration registration)
{
if (registration is null)
{
throw new ArgumentNullException(nameof(registration));
}
- return registration.Properties.TryGetValue(Properties.ProviderSettings, out var value)
- && value is OpenIddictClientWebIntegrationSettings settings ? settings : null;
- }
-
- ///
- /// Resolves the provider settings associated with the client registration or
- /// if no provider information is attached to the registration or if
- /// the actual setting information doesn't match the specified .
- ///
- /// The type of the provider settings.
- /// The client registration.
- /// The provider settings, if applicable.
- /// is null.
- public static TSettings? GetProviderSettings(this OpenIddictClientRegistration registration)
- where TSettings : OpenIddictClientWebIntegrationSettings
- => registration.GetProviderSettings() is TSettings settings ? settings : null;
+ return registration.Properties.TryGetValue(Properties.ProviderOptions, out var value)
+ && value is TOptions options ? options : default;
- // Note: provider-specific helpers are automatically generated by the source generator.
+ // Note: provider-specific helpers are automatically generated by the source generator.
+ }
}
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationOptions.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationOptions.cs
index bf441112..62295e17 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationOptions.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationOptions.cs
@@ -9,10 +9,7 @@ namespace OpenIddict.Client.WebIntegration;
///
/// Provides various settings needed to configure the OpenIddict client Web integration.
///
-public class OpenIddictClientWebIntegrationOptions
+public partial class OpenIddictClientWebIntegrationOptions
{
- ///
- /// Gets the list of provider integrations enabled for this application.
- ///
- public List Providers { get; } = new();
+ // Note: provider options are automatically generated by the source generator.
}
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProvider.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProvider.cs
deleted file mode 100644
index 3df2792e..00000000
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProvider.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
- * See https://github.com/openiddict/openiddict-core for more information concerning
- * the license and the contributors participating to this project.
- */
-
-using System.Diagnostics;
-
-namespace OpenIddict.Client.WebIntegration;
-
-///
-/// Represents an OpenIddict client web integration provider.
-///
-[DebuggerDisplay("{Name,nq}")]
-public class OpenIddictClientWebIntegrationProvider
-{
- ///
- /// Creates a new instance of the class.
- ///
- /// The provider name.
- /// The provider settings.
- /// is null or empty.
- /// are null.
- public OpenIddictClientWebIntegrationProvider(
- string name,
- OpenIddictClientWebIntegrationSettings settings)
- {
- if (string.IsNullOrEmpty(name))
- {
- throw new ArgumentException(SR.GetResourceString(SR.ID0330), nameof(name));
- }
-
- Name = name;
- Settings = settings ?? throw new ArgumentNullException(nameof(settings));
- }
-
- ///
- /// Gets the provider name associated with the current instance.
- ///
- public string Name { get; }
-
- ///
- /// Gets the provider settings associated with the current instance.
- ///
- public OpenIddictClientWebIntegrationSettings Settings { get; }
-}
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml
index cd4eb360..c62120f7 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml
@@ -5,12 +5,12 @@
+ Description="The Elliptic Curve Digital Signature Algorithm (ECDSA) signing key associated with the developer account">
+ Description="The team ID associated with the developer account" />
@@ -36,7 +36,7 @@
+ Description="The tenant used to identify the Azure AD instance (by default, the common tenant is used)" />
@@ -79,12 +79,12 @@
+ Description="The list of data objects to expand from the userinfo endpoint (by default, all known expansions are requested)">
+ Description="The tweet fields that should be retrieved from the userinfo endpoint (by default, all known tweet fields are requested)">
@@ -108,7 +108,7 @@
+ Description="The user fields that should be retrieved from the userinfo endpoint (by default, all known user fields are requested)">
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationSettings.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationSettings.cs
deleted file mode 100644
index bfdb5a1b..00000000
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationSettings.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
- * See https://github.com/openiddict/openiddict-core for more information concerning
- * the license and the contributors participating to this project.
- */
-
-namespace OpenIddict.Client.WebIntegration;
-
-///
-/// Provides various settings needed to configure the OpenIddict client Web providers.
-///
-public abstract partial class OpenIddictClientWebIntegrationSettings
-{
- ///
- /// Gets or sets the client identifier.
- ///
- public virtual string? ClientId { get; set; }
-
- ///
- /// Gets or sets the client secret, if applicable.
- ///
- public virtual string? ClientSecret { get; set; }
-
- ///
- /// Gets or sets the product name used in the user agent header.
- ///
- public string? ProductName { get; set; }
-
- ///
- /// Gets or sets the product version used in the user agent header.
- ///
- public string? ProductVersion { get; set; }
-
- ///
- /// Gets or sets the redirection URL.
- ///
- public virtual Uri? RedirectUri { get; set; }
-
- ///
- /// Gets the scopes requested to the authorization server.
- ///
- public virtual HashSet Scopes { get; } = new(StringComparer.Ordinal);
-}
diff --git a/src/OpenIddict.Client/OpenIddictClientBuilder.cs b/src/OpenIddict.Client/OpenIddictClientBuilder.cs
index b8215768..71e2f408 100644
--- a/src/OpenIddict.Client/OpenIddictClientBuilder.cs
+++ b/src/OpenIddict.Client/OpenIddictClientBuilder.cs
@@ -1096,25 +1096,15 @@ public class OpenIddictClientBuilder
public OpenIddictClientBuilder SetStateTokenLifetime(TimeSpan? lifetime)
=> Configure(options => options.StateTokenLifetime = lifetime);
- ///
- /// Determines whether the specified object is equal to the current object.
- ///
- /// The object to compare with the current object.
- /// if the specified object is equal to the current object; otherwise, false.
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
- ///
- /// Serves as the default hash function.
- ///
- /// A hash code for the current object.
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => base.GetHashCode();
- ///
- /// Returns a string that represents the current object.
- ///
- /// A string that represents the current object.
+ ///
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString() => base.ToString();
}
diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs
index 6ce2d98c..c190758a 100644
--- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs
+++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpBuilder.cs
@@ -5,6 +5,7 @@
*/
using System.ComponentModel;
+using System.Net.Http.Headers;
using OpenIddict.Validation.SystemNetHttp;
using Polly;
@@ -51,9 +52,28 @@ public class OpenIddictValidationSystemNetHttpBuilder
///
/// The HTTP Polly error policy.
/// The .
- public OpenIddictValidationSystemNetHttpBuilder SetHttpErrorPolicy(IAsyncPolicy policy)
+ public OpenIddictValidationSystemNetHttpBuilder SetHttpErrorPolicy(IAsyncPolicy? policy)
=> Configure(options => options.HttpErrorPolicy = policy);
+ ///
+ /// Sets the product information used in the user agent header that is attached
+ /// to the backchannel HTTP requests sent to the authorization server.
+ ///
+ /// The product information.
+ /// The .
+ public OpenIddictValidationSystemNetHttpBuilder SetProductInformation(ProductInfoHeaderValue? information)
+ => Configure(options => options.ProductInformation = information);
+
+ ///
+ /// Sets the product information used in the user agent header that is attached
+ /// to the backchannel HTTP requests sent to the authorization server.
+ ///
+ /// The product name.
+ /// The product version.
+ /// The .
+ public OpenIddictValidationSystemNetHttpBuilder SetProductInformation(string? name, string? version)
+ => SetProductInformation(!string.IsNullOrEmpty(name) ? new ProductInfoHeaderValue(name, version) : null);
+
///
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => base.Equals(obj);
diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs
index f083b6bb..b19dd314 100644
--- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs
+++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpConfiguration.cs
@@ -35,8 +35,7 @@ public class OpenIddictValidationSystemNetHttpConfiguration : IConfigureOptions<
options.Handlers.AddRange(OpenIddictValidationSystemNetHttpHandlers.DefaultHandlers);
}
- public void Configure(HttpClientFactoryOptions options)
- => Debug.Fail("This infrastructure method shouldn't be called.");
+ public void Configure(HttpClientFactoryOptions options) => Configure(Options.DefaultName, options);
public void Configure(string name, HttpClientFactoryOptions options)
{
@@ -45,8 +44,8 @@ public class OpenIddictValidationSystemNetHttpConfiguration : IConfigureOptions<
throw new ArgumentNullException(nameof(options));
}
+ // Only amend the HTTP client factory options if the instance is managed by OpenIddict.
var assembly = typeof(OpenIddictValidationSystemNetHttpOptions).Assembly.GetName();
-
if (!string.Equals(name, assembly.Name, StringComparison.Ordinal))
{
return;
diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
index 9388f1ef..6fc8ecaa 100644
--- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
+++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
@@ -11,6 +11,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Headers;
using System.Text;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpConstants;
@@ -113,6 +114,11 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
///
public class AttachUserAgent : IOpenIddictValidationHandler where TContext : BaseExternalContext
{
+ private readonly IOptionsMonitor _options;
+
+ public AttachUserAgent(IOptionsMonitor options)
+ => _options = options ?? throw new ArgumentNullException(nameof(options));
+
///
/// Gets the default descriptor definition assigned to this handler.
///
@@ -139,9 +145,18 @@ public static partial class OpenIddictValidationSystemNetHttpHandlers
var request = context.Transaction.GetHttpRequestMessage() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0173));
- var assembly = typeof(OpenIddictValidationSystemNetHttpHandlers).Assembly.GetName();
+ // Some authorization servers are known to aggressively check user agents and encourage
+ // developers to use unique user agents. While a default user agent is always added,
+ // the default value doesn't differ accross applications. To reduce the risks of seeing
+ // requests blocked, a more specific user agent header can be configured by the developer.
+ // In this case, the value specified by the developer always appears first in the list.
+ if (_options.CurrentValue.ProductInformation is ProductInfoHeaderValue information)
+ {
+ request.Headers.UserAgent.Add(information);
+ }
// Attach a user agent based on the assembly version of the System.Net.Http integration.
+ var assembly = typeof(OpenIddictValidationSystemNetHttpHandlers).Assembly.GetName();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(
productName: assembly.Name!,
productVersion: assembly.Version!.ToString()));
diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs
index 1f11611a..aeb5caf5 100644
--- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs
+++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpOptions.cs
@@ -5,6 +5,7 @@
*/
using System.Net;
+using System.Net.Http.Headers;
using Polly;
using Polly.Extensions.Http;
@@ -22,4 +23,10 @@ public class OpenIddictValidationSystemNetHttpOptions
= HttpPolicyExtensions.HandleTransientHttpError()
.OrResult(response => response.StatusCode == HttpStatusCode.NotFound)
.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
+
+ ///
+ /// Gets or sets the product information used in the user agent header that is
+ /// attached to the backchannel HTTP requests sent to the authorization server.
+ ///
+ public ProductInfoHeaderValue? ProductInformation { get; set; }
}