diff --git a/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs b/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs
index 5aba8985..1da605f1 100644
--- a/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs
+++ b/gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs
@@ -63,7 +63,7 @@ public partial class OpenIddictClientWebIntegrationBuilder
///
/// Enables the {{ provider.name }} integration and registers the associated services in the DI container.
{{~ if provider.documentation ~}}
- /// For more information, visit the official website.
+ /// For more information, read the documentation.
///
{{~ end ~}}
/// This extension can be safely called multiple times.
@@ -85,7 +85,7 @@ public partial class OpenIddictClientWebIntegrationBuilder
///
/// Enables the {{ provider.name }} integration and registers the associated services in the DI container.
{{~ if provider.documentation ~}}
- /// For more information, visit the official website.
+ /// For more information, read the documentation.
///
{{~ end ~}}
/// This extension can be safely called multiple times.
@@ -231,11 +231,11 @@ public partial class OpenIddictClientWebIntegrationBuilder
{{~ end ~}}
{{~ for setting in provider.settings ~}}
- {{~ if setting.description ~}}
///
/// Configures {{ setting.description }}.
///
- {{~ end ~}}
+ /// {{ setting.description | string.capitalize }}.
+ /// The instance.
{{~ if setting.collection ~}}
public {{ provider.name }} Add{{ setting.property_name }}(params {{ setting.clr_type }}[] {{ setting.parameter_name }})
{
@@ -495,6 +495,8 @@ public partial class OpenIddictClientWebIntegrationConfiguration
var registration = new OpenIddictClientRegistration
{
+ ProviderName = Providers.{{ provider.name }},
+
Issuer = settings.Environment switch
{
{{~ for environment in provider.environments ~}}
@@ -597,7 +599,6 @@ public partial class OpenIddictClientWebIntegrationConfiguration
Properties =
{
- [Properties.ProviderName] = Providers.{{ provider.name }},
[Properties.ProviderOptions] = settings
}
};
@@ -796,11 +797,9 @@ public partial class OpenIddictClientWebIntegrationOptions
public string? Environment { get; set; } = OpenIddictClientWebIntegrationConstants.{{ provider.name }}.Environments.Production;
{{~ for setting in provider.settings ~}}
- {{~ if setting.description ~}}
///
/// Gets or sets {{ setting.description }}.
///
- {{~ end ~}}
{{~ if setting.collection ~}}
public HashSet<{{ setting.clr_type }}> {{ setting.property_name }} { get; } = new();
{{~ else ~}}
diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs
index 4bda9212..bdd0d2ae 100644
--- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs
+++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs
@@ -9,6 +9,7 @@ using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using OpenIddict.Client.Owin;
using static OpenIddict.Abstractions.OpenIddictConstants;
+using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants;
namespace OpenIddict.Sandbox.AspNet.Client.Controllers
{
@@ -19,46 +20,60 @@ namespace OpenIddict.Sandbox.AspNet.Client.Controllers
{
var context = HttpContext.GetOwinContext();
- var issuer = provider switch
- {
- "local" or "local-github" => "https://localhost:44349/",
- "github" => "https://github.com/",
- "google" => "https://accounts.google.com/",
- "twitter" => "https://twitter.com/",
-
- _ => null
- };
-
- if (string.IsNullOrEmpty(issuer))
+ // Note: OpenIddict always validates the specified provider name when handling the challenge operation,
+ // but the provider can also be validated earlier to return an error page or a special HTTP error code.
+ if (!string.Equals(provider, "Local", StringComparison.Ordinal) &&
+ !string.Equals(provider, "Local+GitHub", StringComparison.Ordinal) &&
+ !string.Equals(provider, Providers.GitHub, StringComparison.Ordinal) &&
+ !string.Equals(provider, Providers.Google, StringComparison.Ordinal) &&
+ !string.Equals(provider, Providers.Twitter, StringComparison.Ordinal))
{
return new HttpStatusCodeResult(400);
}
- var properties = new AuthenticationProperties(new Dictionary
- {
- // Note: when only one client is registered in the client options,
- // setting the issuer property is not required and can be omitted.
- [OpenIddictClientOwinConstants.Properties.Issuer] = issuer
- })
- {
- // Only allow local return URLs to prevent open redirect attacks.
- RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/"
- };
-
// The local authorization server sample allows the client to select the external
// identity provider that will be used to eventually authenticate the user. For that,
// a custom "identity_provider" parameter is sent to the authorization server so that
// the user is directly redirected to GitHub (in this case, no login page is shown).
- if (provider is "local-github")
+ if (string.Equals(provider, "Local+GitHub", StringComparison.Ordinal))
{
- // Note: the OWIN host requires appending the #string suffix to indicate
- // that the "identity_provider" property is a public string parameter.
- properties.Dictionary[Parameters.IdentityProvider + OpenIddictClientOwinConstants.PropertyTypes.String] = "github";
+ var properties = new AuthenticationProperties(new Dictionary
+ {
+ // Note: when only one client is registered in the client options,
+ // specifying the issuer URI or the provider name is not required.
+ [OpenIddictClientOwinConstants.Properties.ProviderName] = "Local",
+
+ // Note: the OWIN host requires appending the #string suffix to indicate
+ // that the "identity_provider" property is a public string parameter.
+ [Parameters.IdentityProvider + OpenIddictClientOwinConstants.PropertyTypes.String] = "GitHub"
+ })
+ {
+ // Only allow local return URLs to prevent open redirect attacks.
+ RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/"
+ };
+
+ // Ask the OpenIddict client middleware to redirect the user agent to the identity provider.
+ context.Authentication.Challenge(properties, OpenIddictClientOwinDefaults.AuthenticationType);
+ return new EmptyResult();
}
- // Ask the OpenIddict client middleware to redirect the user agent to the identity provider.
- context.Authentication.Challenge(properties, OpenIddictClientOwinDefaults.AuthenticationType);
- return new EmptyResult();
+ else
+ {
+ var properties = new AuthenticationProperties(new Dictionary
+ {
+ // Note: when only one client is registered in the client options,
+ // specifying the issuer URI or the provider name is not required.
+ [OpenIddictClientOwinConstants.Properties.ProviderName] = provider
+ })
+ {
+ // Only allow local return URLs to prevent open redirect attacks.
+ RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/"
+ };
+
+ // Ask the OpenIddict client middleware to redirect the user agent to the identity provider.
+ context.Authentication.Challenge(properties, OpenIddictClientOwinDefaults.AuthenticationType);
+ return new EmptyResult();
+ }
}
[HttpPost, Route("~/logout"), ValidateAntiForgeryToken]
diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs b/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs
index 55860da2..9d9b850f 100644
--- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs
+++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs
@@ -101,6 +101,7 @@ namespace OpenIddict.Sandbox.AspNet.Client
// Add a client registration matching the client application definition in the server project.
options.AddRegistration(new OpenIddictClientRegistration
{
+ ProviderName = "Local",
Issuer = new Uri("https://localhost:44349/", UriKind.Absolute),
ClientId = "mvc",
diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml b/sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml
index 6f35bc29..d996692f 100644
--- a/sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml
+++ b/sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml
@@ -39,14 +39,14 @@
{
Welcome, anonymous
@Html.ActionLink("Sign in using the local OIDC server", "Login", "Authentication",
- new { provider = "local" }, new { @class = "btn btn-lg btn-success" })
+ new { provider = "Local" }, new { @class = "btn btn-lg btn-success" })
@Html.ActionLink("Sign in using the local OIDC server (using GitHub delegation)", "Login", "Authentication",
- new { provider = "local-github" }, new { @class = "btn btn-lg btn-success" })
+ new { provider = "Local+GitHub" }, new { @class = "btn btn-lg btn-success" })
@Html.ActionLink("Sign in using GitHub", "Login", "Authentication",
- new { provider = "github" }, new { @class = "btn btn-lg btn-success" })
+ new { provider = "GitHub" }, new { @class = "btn btn-lg btn-success" })
@Html.ActionLink("Sign in using Google", "Login", "Authentication",
- new { provider = "google" }, new { @class = "btn btn-lg btn-success" })
+ new { provider = "Google" }, new { @class = "btn btn-lg btn-success" })
@Html.ActionLink("Sign in using Twitter", "Login", "Authentication",
- new { provider = "twitter" }, new { @class = "btn btn-lg btn-success" })
+ new { provider = "Twitter" }, new { @class = "btn btn-lg btn-success" })
}
\ No newline at end of file
diff --git a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs
index 2054222c..853cb078 100644
--- a/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs
+++ b/sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs
@@ -14,7 +14,6 @@ using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
-using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Security;
using OpenIddict.Abstractions;
using OpenIddict.Client.Owin;
@@ -23,6 +22,7 @@ using OpenIddict.Sandbox.AspNet.Server.ViewModels.Authorization;
using OpenIddict.Server.Owin;
using Owin;
using static OpenIddict.Abstractions.OpenIddictConstants;
+using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants;
namespace OpenIddict.Sandbox.AspNet.Server.Controllers
{
@@ -60,14 +60,7 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers
// that will be used to authenticate the user, the identity_provider parameter can be used for that.
if (!string.IsNullOrEmpty(request.IdentityProvider))
{
- var issuer = request.IdentityProvider switch
- {
- "github" => "https://github.com/",
-
- _ => null
- };
-
- if (string.IsNullOrEmpty(issuer))
+ if (!string.Equals(request.IdentityProvider, Providers.GitHub, StringComparison.Ordinal))
{
context.Authentication.Challenge(
authenticationTypes: OpenIddictServerOwinDefaults.AuthenticationType,
@@ -84,8 +77,8 @@ namespace OpenIddict.Sandbox.AspNet.Server.Controllers
var properties = new AuthenticationProperties(new Dictionary
{
// Note: when only one client is registered in the client options,
- // setting the issuer property is not required and can be omitted.
- [OpenIddictClientOwinConstants.Properties.Issuer] = issuer
+ // specifying the issuer URI or the provider name is not required.
+ [OpenIddictClientOwinConstants.Properties.ProviderName] = request.IdentityProvider
})
{
// Once the callback is handled, redirect the user agent to the ASP.NET Identity
diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs
index 53403da1..6ce2bc4b 100644
--- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs
+++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using OpenIddict.Client.AspNetCore;
using static OpenIddict.Abstractions.OpenIddictConstants;
+using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants;
namespace OpenIddict.Sandbox.AspNetCore.Client.Controllers;
@@ -12,44 +13,57 @@ public class AuthenticationController : Controller
[HttpGet("~/login")]
public ActionResult LogIn(string provider, string returnUrl)
{
- var issuer = provider switch
- {
- "local" or "local-github" => "https://localhost:44395/",
- "github" => "https://github.com/",
- "google" => "https://accounts.google.com/",
- "reddit" => "https://www.reddit.com/",
- "twitter" => "https://twitter.com/",
-
- _ => null
- };
-
- if (string.IsNullOrEmpty(issuer))
+ // Note: OpenIddict always validates the specified provider name when handling the challenge operation,
+ // but the provider can also be validated earlier to return an error page or a special HTTP error code.
+ if (!string.Equals(provider, "Local", StringComparison.Ordinal) &&
+ !string.Equals(provider, "Local+GitHub", StringComparison.Ordinal) &&
+ !string.Equals(provider, Providers.GitHub, StringComparison.Ordinal) &&
+ !string.Equals(provider, Providers.Google, StringComparison.Ordinal) &&
+ !string.Equals(provider, Providers.Reddit, StringComparison.Ordinal) &&
+ !string.Equals(provider, Providers.Twitter, StringComparison.Ordinal))
{
return BadRequest();
}
- var properties = new AuthenticationProperties(new Dictionary
- {
- // Note: when only one client is registered in the client options,
- // setting the issuer property is not required and can be omitted.
- [OpenIddictClientAspNetCoreConstants.Properties.Issuer] = issuer
- })
- {
- // Only allow local return URLs to prevent open redirect attacks.
- RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/"
- };
-
// The local authorization server sample allows the client to select the external
// identity provider that will be used to eventually authenticate the user. For that,
// a custom "identity_provider" parameter is sent to the authorization server so that
// the user is directly redirected to GitHub (in this case, no login page is shown).
- if (provider is "local-github")
+ if (string.Equals(provider, "Local+GitHub", StringComparison.Ordinal))
{
- properties.Parameters[Parameters.IdentityProvider] = "github";
+ var properties = new AuthenticationProperties(new Dictionary
+ {
+ // Note: when only one client is registered in the client options,
+ // specifying the issuer URI or the provider name is not required.
+ [OpenIddictClientAspNetCoreConstants.Properties.ProviderName] = "Local"
+ })
+ {
+ // Only allow local return URLs to prevent open redirect attacks.
+ RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/",
+
+ Parameters = { [Parameters.IdentityProvider] = "GitHub" }
+ };
+
+ // Ask the OpenIddict client middleware to redirect the user agent to the identity provider.
+ return Challenge(properties, OpenIddictClientAspNetCoreDefaults.AuthenticationScheme);
}
- // Ask the OpenIddict client middleware to redirect the user agent to the identity provider.
- return Challenge(properties, OpenIddictClientAspNetCoreDefaults.AuthenticationScheme);
+ else
+ {
+ var properties = new AuthenticationProperties(new Dictionary
+ {
+ // Note: when only one client is registered in the client options,
+ // specifying the issuer URI or the provider name is not required.
+ [OpenIddictClientAspNetCoreConstants.Properties.ProviderName] = provider
+ })
+ {
+ // Only allow local return URLs to prevent open redirect attacks.
+ RedirectUri = Url.IsLocalUrl(returnUrl) ? returnUrl : "/"
+ };
+
+ // Ask the OpenIddict client middleware to redirect the user agent to the identity provider.
+ return Challenge(properties, OpenIddictClientAspNetCoreDefaults.AuthenticationScheme);
+ }
}
[HttpPost("~/logout"), ValidateAntiForgeryToken]
diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs
index 698d0dda..0d3ee4dc 100644
--- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs
+++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs
@@ -111,6 +111,7 @@ public class Startup
// Add a client registration matching the client application definition in the server project.
options.AddRegistration(new OpenIddictClientRegistration
{
+ ProviderName = "Local",
Issuer = new Uri("https://localhost:44395/", UriKind.Absolute),
ClientId = "mvc",
diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml
index d511c1a0..d5393c95 100644
--- a/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml
+++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml
@@ -34,16 +34,16 @@
{
Welcome, anonymous
Sign in using the local OIDC server
+ asp-action="Login" asp-route-provider="Local">Sign in using the local OIDC server
Sign in using the local OIDC server (using GitHub delegation)
+ asp-action="Login" asp-route-provider="Local+GitHub">Sign in using the local OIDC server (using GitHub delegation)
Sign in using GitHub
+ asp-action="Login" asp-route-provider="GitHub">Sign in using GitHub
Sign in using Google
+ asp-action="Login" asp-route-provider="Google">Sign in using Google
Sign in using Reddit
+ asp-action="Login" asp-route-provider="Reddit">Sign in using Reddit
Sign in using Twitter
+ asp-action="Login" asp-route-provider="Twitter">Sign in using Twitter
}
\ No newline at end of file
diff --git a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs
index 497b2a27..f82559d1 100644
--- a/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs
+++ b/sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs
@@ -20,6 +20,7 @@ using OpenIddict.Sandbox.AspNetCore.Server.Models;
using OpenIddict.Sandbox.AspNetCore.Server.ViewModels.Authorization;
using OpenIddict.Server.AspNetCore;
using static OpenIddict.Abstractions.OpenIddictConstants;
+using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants;
namespace OpenIddict.Sandbox.AspNetCore.Server;
@@ -95,14 +96,7 @@ public class AuthorizationController : Controller
// that will be used to authenticate the user, the identity_provider parameter can be used for that.
if (!string.IsNullOrEmpty(request.IdentityProvider))
{
- var issuer = request.IdentityProvider switch
- {
- "github" => "https://github.com/",
-
- _ => null
- };
-
- if (string.IsNullOrEmpty(issuer))
+ if (!string.Equals(request.IdentityProvider, Providers.GitHub, StringComparison.Ordinal))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
@@ -115,15 +109,15 @@ public class AuthorizationController : Controller
}
var properties = _signInManager.ConfigureExternalAuthenticationProperties(
- provider: issuer,
+ provider: request.IdentityProvider,
redirectUrl: Url.Action("ExternalLoginCallback", "Account", new
{
ReturnUrl = Request.PathBase + Request.Path + QueryString.Create(parameters)
}));
// Note: when only one client is registered in the client options,
- // setting the issuer property is not required and can be omitted.
- properties.SetString(OpenIddictClientAspNetCoreConstants.Properties.Issuer, issuer);
+ // specifying the issuer URI or the provider name is not required.
+ properties.SetString(OpenIddictClientAspNetCoreConstants.Properties.ProviderName, request.IdentityProvider);
// Ask the OpenIddict client middleware to redirect the user agent to the identity provider.
return Challenge(properties, OpenIddictClientAspNetCoreDefaults.AuthenticationScheme);
diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx
index 29670068..668cefe5 100644
--- a/src/OpenIddict.Abstractions/OpenIddictResources.resx
+++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx
@@ -1177,7 +1177,7 @@ To apply redirection responses, create a class implementing 'IOpenIddictClientHa
No client registration was found in the client options. To add a registration, use 'services.AddOpenIddict().AddClient().AddRegistration()'.
- No issuer was specified in the challenge properties. When multiple clients are registered, an issuer must be specified in the challenge properties.
+ No issuer was specified in the challenge properties. When multiple clients are registered, an issuer (or a provider name) must be specified in the challenge properties.
The specified issuer is not a valid or absolute URL.
@@ -1320,10 +1320,10 @@ Alternatively, you can disable the token storage feature by calling 'services.Ad
The endpoint type associated with the state token cannot be resolved.
- No issuer was specified in the sign-out properties. When multiple clients are registered, an issuer must be specified in the sign-out properties.
+ No issuer was specified in the sign-out properties. When multiple clients are registered, an issuer (or a provider name) must be specified in the sign-out properties.
- Identical issuers cannot be used in multiple client registrations.
+ The same issuer cannot be used in multiple client registrations.
The request forgery protection claim cannot be resolved from the challenge context.
@@ -1337,6 +1337,15 @@ Alternatively, you can disable the token storage feature by calling 'services.Ad
The PEM-encoded key cannot be empty.
+
+ The same provider name cannot be used in multiple client registrations.
+
+
+ The issuer corresponding to the specified provider name cannot be found in the client options.
+
+
+ The issuer associated with the resolved client registration doesn't match the specified provider name.
+
The security token is missing.
diff --git a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreConstants.cs b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreConstants.cs
index 6451a776..11e56b08 100644
--- a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreConstants.cs
+++ b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreConstants.cs
@@ -24,6 +24,7 @@ public static class OpenIddictClientAspNetCoreConstants
public const string Error = ".error";
public const string ErrorDescription = ".error_description";
public const string ErrorUri = ".error_uri";
+ public const string ProviderName = ".provider_name";
public const string RefreshTokenPrincipal = ".refresh_token_principal";
public const string StateTokenPrincipal = ".state_token_principal";
public const string UserinfoTokenPrincipal = ".userinfo_token_principal";
diff --git a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs
index 0b9f33ea..5d0a410c 100644
--- a/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs
+++ b/src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs
@@ -459,6 +459,13 @@ public static partial class OpenIddictClientAspNetCoreHandlers
context.Issuer = uri;
}
+ // If a provider name was explicitly set, update the challenge context to use it.
+ if (properties.Items.TryGetValue(Properties.ProviderName, out string? provider) &&
+ !string.IsNullOrEmpty(provider))
+ {
+ context.ProviderName = provider;
+ }
+
// If a return URL was specified, use it as the target_link_uri claim.
if (!string.IsNullOrEmpty(properties.RedirectUri))
{
@@ -628,6 +635,13 @@ public static partial class OpenIddictClientAspNetCoreHandlers
context.Issuer = uri;
}
+ // If a provider name was explicitly set, update the sign-out context to use it.
+ if (properties.Items.TryGetValue(Properties.ProviderName, out string? provider) &&
+ !string.IsNullOrEmpty(provider))
+ {
+ context.ProviderName = provider;
+ }
+
// If a return URL was specified, use it as the target_link_uri claim.
if (!string.IsNullOrEmpty(properties.RedirectUri))
{
diff --git a/src/OpenIddict.Client.Owin/OpenIddictClientOwinConstants.cs b/src/OpenIddict.Client.Owin/OpenIddictClientOwinConstants.cs
index 808893b8..95ed6568 100644
--- a/src/OpenIddict.Client.Owin/OpenIddictClientOwinConstants.cs
+++ b/src/OpenIddict.Client.Owin/OpenIddictClientOwinConstants.cs
@@ -32,6 +32,7 @@ public static class OpenIddictClientOwinConstants
public const string Error = ".error";
public const string ErrorDescription = ".error_description";
public const string ErrorUri = ".error_uri";
+ public const string ProviderName = ".provider_name";
public const string RefreshTokenPrincipal = ".refresh_token_principal";
public const string StateTokenPrincipal = ".state_token_principal";
public const string UserinfoTokenPrincipal = ".userinfo_token_principal";
diff --git a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs
index d153d04d..16c8e197 100644
--- a/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs
+++ b/src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs
@@ -470,6 +470,13 @@ public static partial class OpenIddictClientOwinHandlers
context.Issuer = uri;
}
+ // If a provider name was explicitly set, update the challenge context to use it.
+ if (properties.Dictionary.TryGetValue(Properties.ProviderName, out string? provider) &&
+ !string.IsNullOrEmpty(provider))
+ {
+ context.ProviderName = provider;
+ }
+
// If a return URL was specified, use it as the target_link_uri claim.
if (!string.IsNullOrEmpty(properties.RedirectUri))
{
@@ -666,6 +673,13 @@ public static partial class OpenIddictClientOwinHandlers
context.Issuer = uri;
}
+ // If a provider name was explicitly set, update the sign-out context to use it.
+ if (properties.Dictionary.TryGetValue(Properties.ProviderName, out string? provider) &&
+ !string.IsNullOrEmpty(provider))
+ {
+ context.ProviderName = provider;
+ }
+
// If a return URL was specified, use it as the target_link_uri claim.
if (!string.IsNullOrEmpty(properties.RedirectUri))
{
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs
index 9e674402..fbbb21c5 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs
@@ -5,9 +5,12 @@
*/
using System.ComponentModel;
+using OpenIddict.Client.WebIntegration;
+
+#if SUPPORTS_PEM_ENCODED_KEY_IMPORT
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
-using OpenIddict.Client.WebIntegration;
+#endif
namespace Microsoft.Extensions.DependencyInjection;
@@ -59,7 +62,10 @@ public partial class OpenIddictClientWebIntegrationBuilder
/// Configures the Elliptic Curve Digital Signature Algorithm
/// (ECDSA) signing key associated with the developer account.
///
- /// The PEM-encoded ECDSA signing key.
+ ///
+ /// The PEM-encoded Elliptic Curve Digital Signature Algorithm
+ /// (ECDSA) signing key associated with the developer account.
+ ///
/// The instance.
public Apple SetSigningKey(string key) => SetSigningKey(key.AsMemory());
@@ -67,7 +73,10 @@ public partial class OpenIddictClientWebIntegrationBuilder
/// Configures the Elliptic Curve Digital Signature Algorithm
/// (ECDSA) signing key associated with the developer account.
///
- /// The PEM-encoded ECDSA signing key.
+ ///
+ /// The PEM-encoded Elliptic Curve Digital Signature Algorithm
+ /// (ECDSA) signing key associated with the developer account.
+ ///
/// The instance.
public Apple SetSigningKey(ReadOnlyMemory key) => SetSigningKey(key.Span);
@@ -75,7 +84,10 @@ public partial class OpenIddictClientWebIntegrationBuilder
/// Configures the Elliptic Curve Digital Signature Algorithm
/// (ECDSA) signing key associated with the developer account.
///
- /// The PEM-encoded ECDSA signing key.
+ ///
+ /// The PEM-encoded Elliptic Curve Digital Signature Algorithm
+ /// (ECDSA) signing key associated with the developer account.
+ ///
/// The instance.
public Apple SetSigningKey(ReadOnlySpan key)
{
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConstants.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConstants.cs
index 7f8796cb..440464fa 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConstants.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConstants.cs
@@ -15,7 +15,6 @@ public static partial class OpenIddictClientWebIntegrationConstants
public static class Properties
{
- public const string ProviderName = ".provider_name";
public const string ProviderOptions = ".provider_options";
}
}
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs
index c8ecbe59..e153c927 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs
@@ -51,7 +51,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// based on the client identity. As required by RFC8414, OpenIddict would automatically reject
// such responses as the issuer wouldn't match the expected value. To work around that, the issuer
// is replaced by this handler to always use "https://login.microsoftonline.com/common/v2.0".
- if (context.Registration.GetProviderName() is Providers.Microsoft)
+ if (context.Registration.ProviderName is Providers.Microsoft)
{
var options = context.Registration.GetMicrosoftOptions();
if (string.Equals(options.Tenant, "common", StringComparison.OrdinalIgnoreCase))
@@ -95,7 +95,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// is the same as private_key_jwt, the configuration is amended to assume Apple supports
// private_key_jwt and an event handler is responsible for populating the client_secret
// parameter using the client assertion token once it has been generated by OpenIddict.
- if (context.Registration.GetProviderName() is Providers.Apple)
+ if (context.Registration.ProviderName is Providers.Apple)
{
context.Configuration.TokenEndpointAuthMethodsSupported.Add(
ClientAuthenticationMethods.PrivateKeyJwt);
@@ -133,7 +133,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// don't list them in the server configuration metadata. To ensure the OpenIddict
// client uses Proof Key for Code Exchange for the Microsoft provider, the 2 methods
// are manually added to the list of supported code challenge methods by this handler.
- if (context.Registration.GetProviderName() is Providers.Microsoft)
+ if (context.Registration.ProviderName is Providers.Microsoft)
{
context.Configuration.CodeChallengeMethodsSupported.Add(CodeChallengeMethods.Plain);
context.Configuration.CodeChallengeMethodsSupported.Add(CodeChallengeMethods.Sha256);
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Protection.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Protection.cs
index a488f401..6f262059 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Protection.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Protection.cs
@@ -50,7 +50,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
return default;
}
- context.TokenValidationParameters.ValidateIssuer = context.Registration.GetProviderName() switch
+ context.TokenValidationParameters.ValidateIssuer = context.Registration.ProviderName switch
{
// When the Microsoft Account provider is configured to use the "common" tenant,
// the returned tokens include a dynamic issuer claim corresponding to the tenant
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
index eca50931..f12c7c97 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
@@ -56,7 +56,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// that determines what fields will be returned as part of the userinfo response. This handler is
// responsible for resolving the fields from the provider settings and attaching them to the request.
- if (context.Registration.GetProviderName() is Providers.Twitter)
+ if (context.Registration.ProviderName is Providers.Twitter)
{
var options = context.Registration.GetTwitterOptions();
@@ -100,7 +100,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// 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.
- var parameter = context.Registration.GetProviderName() switch
+ var parameter = context.Registration.ProviderName switch
{
Providers.Twitter => "data",
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
index 59625ba0..603b745e 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
@@ -64,7 +64,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
//
// For more information about the custom client authentication method implemented by Apple,
// see https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens.
- if (context.Registration.GetProviderName() is Providers.Apple)
+ if (context.Registration.ProviderName is Providers.Apple)
{
var options = context.Registration.GetAppleOptions();
context.ClientAssertionTokenPrincipal.SetClaim(Claims.Private.Issuer, options.TeamId);
@@ -109,7 +109,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// is the same as private_key_jwt, the configuration is amended to assume Apple supports
// private_key_jwt and an event handler is responsible for populating the client_secret
// parameter using the client assertion token once it has been generated by OpenIddict.
- if (context.Registration.GetProviderName() is Providers.Apple)
+ if (context.Registration.ProviderName is Providers.Apple)
{
context.TokenRequest.ClientSecret = context.TokenRequest.ClientAssertion;
context.TokenRequest.ClientAssertion = null;
@@ -146,7 +146,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
throw new ArgumentNullException(nameof(context));
}
- context.ResponseMode = context.Registration.GetProviderName() switch
+ context.ResponseMode = context.Registration.ProviderName switch
{
// Note: Apple requires using form_post when the "email" or "name" scopes are requested.
Providers.Apple when context.Scopes.Contains(Scopes.Email) || context.Scopes.Contains("name")
@@ -184,7 +184,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
throw new ArgumentNullException(nameof(context));
}
- context.Request.Scope = context.Registration.GetProviderName() switch
+ context.Request.Scope = context.Registration.ProviderName switch
{
// The following providers are known to use comma-separated scopes instead of
// the standard format (that requires using a space as the scope separator):
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHelpers.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHelpers.cs
index 35f95a9b..5335f935 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHelpers.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHelpers.cs
@@ -13,24 +13,6 @@ namespace OpenIddict.Client.WebIntegration;
///
public static partial class OpenIddictClientWebIntegrationHelpers
{
- ///
- /// Resolves the name of the provider associated with the client registration or
- /// if no provider information is attached to the registration.
- ///
- /// The client registration.
- /// The provider name, if applicable.
- /// is null.
- public static string? GetProviderName(this OpenIddictClientRegistration registration)
- {
- if (registration is null)
- {
- throw new ArgumentNullException(nameof(registration));
- }
-
- return registration.Properties.TryGetValue(Properties.ProviderName, out var provider)
- && provider is string value ? value : null;
- }
-
///
/// Resolves the provider options associated with the client registration or
/// if no provider information is attached to the registration or if
diff --git a/src/OpenIddict.Client/OpenIddictClientConfiguration.cs b/src/OpenIddict.Client/OpenIddictClientConfiguration.cs
index 8d14da58..cafd67a6 100644
--- a/src/OpenIddict.Client/OpenIddictClientConfiguration.cs
+++ b/src/OpenIddict.Client/OpenIddictClientConfiguration.cs
@@ -107,6 +107,20 @@ public class OpenIddictClientConfiguration : IPostConfigureOptions !string.IsNullOrEmpty(registration.ProviderName))
+ .Count() != options.Registrations.Select(registration => registration.ProviderName)
+ .Where(name => !string.IsNullOrEmpty(name))
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .Count())
+ {
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0347));
+ }
+
// Sort the handlers collection using the order associated with each handler.
options.Handlers.Sort((left, right) => left.Order.CompareTo(right.Order));
diff --git a/src/OpenIddict.Client/OpenIddictClientEvents.cs b/src/OpenIddict.Client/OpenIddictClientEvents.cs
index c8428500..ed7cdba2 100644
--- a/src/OpenIddict.Client/OpenIddictClientEvents.cs
+++ b/src/OpenIddict.Client/OpenIddictClientEvents.cs
@@ -693,6 +693,12 @@ public static partial class OpenIddictClientEvents
set => Transaction.Response = value;
}
+ ///
+ /// Gets or sets the name of the provider that will be
+ /// used to resolve the issuer identity, if applicable.
+ ///
+ public string? ProviderName { get; set; }
+
///
/// Gets the additional parameters returned to caller.
///
@@ -837,6 +843,12 @@ public static partial class OpenIddictClientEvents
set => Transaction.Response = value;
}
+ ///
+ /// Gets or sets the name of the provider that will be
+ /// used to resolve the issuer identity, if applicable.
+ ///
+ public string? ProviderName { get; set; }
+
///
/// Gets or sets the client identifier that will be used for the sign-out demand.
///
diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs
index e5adb13a..2c1f01c5 100644
--- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs
+++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs
@@ -3365,6 +3365,22 @@ public static partial class OpenIddictClientHandlers
throw new InvalidOperationException(SR.GetResourceString(SR.ID0296));
}
+ // If a provider name was specified, resolve the corresponding issuer.
+ if (!string.IsNullOrEmpty(context.ProviderName))
+ {
+ var registration = context.Options.Registrations.Find(registration => string.Equals(
+ registration.ProviderName, context.ProviderName, StringComparison.Ordinal)) ??
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0348));
+
+ // If an explicit issuer was also attached, ensure the two values point to the same instance.
+ if (context.Issuer is not null && context.Issuer != registration.Issuer)
+ {
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0349));
+ }
+
+ context.Issuer = registration.Issuer;
+ }
+
// If no issuer was explicitly attached and a single client is registered, use it.
// Otherwise, throw an exception to indicate that setting an explicit issuer
// is required when multiple clients are registered.
@@ -4468,6 +4484,22 @@ public static partial class OpenIddictClientHandlers
throw new InvalidOperationException(SR.GetResourceString(SR.ID0024));
}
+ // If a provider name was specified, resolve the corresponding issuer.
+ if (!string.IsNullOrEmpty(context.ProviderName))
+ {
+ var registration = context.Options.Registrations.Find(registration => string.Equals(
+ registration.ProviderName, context.ProviderName, StringComparison.Ordinal)) ??
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0348));
+
+ // If an explicit issuer was also attached, ensure the two values point to the same instance.
+ if (context.Issuer is not null && context.Issuer != registration.Issuer)
+ {
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0349));
+ }
+
+ context.Issuer = registration.Issuer;
+ }
+
// If no issuer was explicitly attached and a single client is registered, use it.
// Otherwise, throw an exception to indicate that setting an explicit issuer
// is required when multiple clients are registered.
diff --git a/src/OpenIddict.Client/OpenIddictClientRegistration.cs b/src/OpenIddict.Client/OpenIddictClientRegistration.cs
index 6a5b6c36..f3c88efd 100644
--- a/src/OpenIddict.Client/OpenIddictClientRegistration.cs
+++ b/src/OpenIddict.Client/OpenIddictClientRegistration.cs
@@ -4,6 +4,7 @@
* the license and the contributors participating to this project.
*/
+using System.Diagnostics;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Tokens;
@@ -12,6 +13,7 @@ namespace OpenIddict.Client;
///
/// Contains the properties used to configure a client/server link.
///
+[DebuggerDisplay("{Issuer,nq}")]
public class OpenIddictClientRegistration
{
///
@@ -93,6 +95,15 @@ public class OpenIddictClientRegistration
///
public Uri? Issuer { get; set; }
+ ///
+ /// Gets or sets the provider name, if applicable.
+ ///
+ ///
+ /// If a Web provider integration with the same name was enabled, the
+ /// provider-specific options will be automatically imported and applied.
+ ///
+ public string? ProviderName { get; set; }
+
///
/// Gets or sets the static server configuration, if applicable.
///