Browse Source

Support resolving client registrations based on the provider name

pull/1522/head
Kévin Chalet 4 years ago
parent
commit
8342dd20ce
  1. 13
      gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs
  2. 73
      sandbox/OpenIddict.Sandbox.AspNet.Client/Controllers/AuthenticationController.cs
  3. 1
      sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs
  4. 10
      sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml
  5. 15
      sandbox/OpenIddict.Sandbox.AspNet.Server/Controllers/AuthorizationController.cs
  6. 68
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/Controllers/AuthenticationController.cs
  7. 1
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/Startup.cs
  8. 12
      sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml
  9. 16
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Controllers/AuthorizationController.cs
  10. 15
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  11. 1
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreConstants.cs
  12. 14
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs
  13. 1
      src/OpenIddict.Client.Owin/OpenIddictClientOwinConstants.cs
  14. 14
      src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs
  15. 20
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationBuilder.cs
  16. 1
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationConstants.cs
  17. 6
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs
  18. 2
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Protection.cs
  19. 4
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
  20. 8
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
  21. 18
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHelpers.cs
  22. 14
      src/OpenIddict.Client/OpenIddictClientConfiguration.cs
  23. 12
      src/OpenIddict.Client/OpenIddictClientEvents.cs
  24. 32
      src/OpenIddict.Client/OpenIddictClientHandlers.cs
  25. 11
      src/OpenIddict.Client/OpenIddictClientRegistration.cs

13
gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs

@ -63,7 +63,7 @@ public partial class OpenIddictClientWebIntegrationBuilder
/// <summary>
/// Enables the {{ provider.name }} integration and registers the associated services in the DI container.
{{~ if provider.documentation ~}}
/// For more information, visit <see href=""{{ provider.documentation }}"">the official website</see>.
/// For more information, read <see href=""{{ provider.documentation }}"">the documentation</see>.
/// </summary>
{{~ end ~}}
/// <remarks>This extension can be safely called multiple times.</remarks>
@ -85,7 +85,7 @@ public partial class OpenIddictClientWebIntegrationBuilder
/// <summary>
/// Enables the {{ provider.name }} integration and registers the associated services in the DI container.
{{~ if provider.documentation ~}}
/// For more information, visit <see href=""{{ provider.documentation }}"">the official website</see>.
/// For more information, read <see href=""{{ provider.documentation }}"">the documentation</see>.
/// </summary>
{{~ end ~}}
/// <remarks>This extension can be safely called multiple times.</remarks>
@ -231,11 +231,11 @@ public partial class OpenIddictClientWebIntegrationBuilder
{{~ end ~}}
{{~ for setting in provider.settings ~}}
{{~ if setting.description ~}}
/// <summary>
/// Configures {{ setting.description }}.
/// </summary>
{{~ end ~}}
/// <param name=""{{ setting.parameter_name }}"">{{ setting.description | string.capitalize }}.</param>
/// <returns>The <see cref=""OpenIddictClientWebIntegrationBuilder.{{ provider.name }}""/> instance.</returns>
{{~ 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 ~}}
/// <summary>
/// Gets or sets {{ setting.description }}.
/// </summary>
{{~ end ~}}
{{~ if setting.collection ~}}
public HashSet<{{ setting.clr_type }}> {{ setting.property_name }} { get; } = new();
{{~ else ~}}

73
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<string, string>
{
// 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<string, string>
{
// 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<string, string>
{
// 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]

1
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",

10
sandbox/OpenIddict.Sandbox.AspNet.Client/Views/Home/Index.cshtml

@ -39,14 +39,14 @@
{
<h1>Welcome, anonymous</h1>
@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" })
}
</div>

15
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<string, string>
{
// 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

68
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<string, string>
{
// 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<string, string>
{
// 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<string, string>
{
// 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]

1
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",

12
sandbox/OpenIddict.Sandbox.AspNetCore.Client/Views/Home/Index.cshtml

@ -34,16 +34,16 @@
{
<h1>Welcome, anonymous</h1>
<a class="btn btn-lg btn-success" asp-controller="Authentication"
asp-action="Login" asp-route-provider="local">Sign in using the local OIDC server</a>
asp-action="Login" asp-route-provider="Local">Sign in using the local OIDC server</a>
<a class="btn btn-lg btn-success" asp-controller="Authentication"
asp-action="Login" asp-route-provider="local-github">Sign in using the local OIDC server (using GitHub delegation)</a>
asp-action="Login" asp-route-provider="Local+GitHub">Sign in using the local OIDC server (using GitHub delegation)</a>
<a class="btn btn-lg btn-success" asp-controller="Authentication"
asp-action="Login" asp-route-provider="github">Sign in using GitHub</a>
asp-action="Login" asp-route-provider="GitHub">Sign in using GitHub</a>
<a class="btn btn-lg btn-success" asp-controller="Authentication"
asp-action="Login" asp-route-provider="google">Sign in using Google</a>
asp-action="Login" asp-route-provider="Google">Sign in using Google</a>
<a class="btn btn-lg btn-success" asp-controller="Authentication"
asp-action="Login" asp-route-provider="reddit">Sign in using Reddit</a>
asp-action="Login" asp-route-provider="Reddit">Sign in using Reddit</a>
<a class="btn btn-lg btn-success" asp-controller="Authentication"
asp-action="Login" asp-route-provider="twitter">Sign in using Twitter</a>
asp-action="Login" asp-route-provider="Twitter">Sign in using Twitter</a>
}
</div>

16
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);

15
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -1177,7 +1177,7 @@ To apply redirection responses, create a class implementing 'IOpenIddictClientHa
<value>No client registration was found in the client options. To add a registration, use 'services.AddOpenIddict().AddClient().AddRegistration()'.</value>
</data>
<data name="ID0305" xml:space="preserve">
<value>No issuer was specified in the challenge properties. When multiple clients are registered, an issuer must be specified in the challenge properties.</value>
<value>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.</value>
</data>
<data name="ID0306" xml:space="preserve">
<value>The specified issuer is not a valid or absolute URL.</value>
@ -1320,10 +1320,10 @@ Alternatively, you can disable the token storage feature by calling 'services.Ad
<value>The endpoint type associated with the state token cannot be resolved.</value>
</data>
<data name="ID0341" xml:space="preserve">
<value>No issuer was specified in the sign-out properties. When multiple clients are registered, an issuer must be specified in the sign-out properties.</value>
<value>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.</value>
</data>
<data name="ID0342" xml:space="preserve">
<value>Identical issuers cannot be used in multiple client registrations.</value>
<value>The same issuer cannot be used in multiple client registrations.</value>
</data>
<data name="ID0343" xml:space="preserve">
<value>The request forgery protection claim cannot be resolved from the challenge context.</value>
@ -1337,6 +1337,15 @@ Alternatively, you can disable the token storage feature by calling 'services.Ad
<data name="ID0346" xml:space="preserve">
<value>The PEM-encoded key cannot be empty.</value>
</data>
<data name="ID0347" xml:space="preserve">
<value>The same provider name cannot be used in multiple client registrations.</value>
</data>
<data name="ID0348" xml:space="preserve">
<value>The issuer corresponding to the specified provider name cannot be found in the client options.</value>
</data>
<data name="ID0349" xml:space="preserve">
<value>The issuer associated with the resolved client registration doesn't match the specified provider name.</value>
</data>
<data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value>
</data>

1
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";

14
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))
{

1
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";

14
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))
{

20
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.
/// </summary>
/// <param name="key">The PEM-encoded ECDSA signing key.</param>
/// <param name="key">
/// The PEM-encoded Elliptic Curve Digital Signature Algorithm
/// (ECDSA) signing key associated with the developer account.
/// </param>
/// <returns>The <see cref="Apple"/> instance.</returns>
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.
/// </summary>
/// <param name="key">The PEM-encoded ECDSA signing key.</param>
/// <param name="key">
/// The PEM-encoded Elliptic Curve Digital Signature Algorithm
/// (ECDSA) signing key associated with the developer account.
/// </param>
/// <returns>The <see cref="Apple"/> instance.</returns>
public Apple SetSigningKey(ReadOnlyMemory<char> 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.
/// </summary>
/// <param name="key">The PEM-encoded ECDSA signing key.</param>
/// <param name="key">
/// The PEM-encoded Elliptic Curve Digital Signature Algorithm
/// (ECDSA) signing key associated with the developer account.
/// </param>
/// <returns>The <see cref="Apple"/> instance.</returns>
public Apple SetSigningKey(ReadOnlySpan<char> key)
{

1
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";
}
}

6
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);

2
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

4
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",

8
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):

18
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHelpers.cs

@ -13,24 +13,6 @@ namespace OpenIddict.Client.WebIntegration;
/// </summary>
public static partial class OpenIddictClientWebIntegrationHelpers
{
/// <summary>
/// Resolves the name of the provider associated with the client registration or
/// <see langword="null" /> if no provider information is attached to the registration.
/// </summary>
/// <param name="registration">The client registration.</param>
/// <returns>The provider name, if applicable.</returns>
/// <exception cref="ArgumentNullException"><paramref name="registration"/> is null.</exception>
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;
}
/// <summary>
/// Resolves the provider options associated with the client registration or
/// <see langword="null" /> if no provider information is attached to the registration or if

14
src/OpenIddict.Client/OpenIddictClientConfiguration.cs

@ -107,6 +107,20 @@ public class OpenIddictClientConfiguration : IPostConfigureOptions<OpenIddictCli
throw new InvalidOperationException(SR.GetResourceString(SR.ID0342));
}
// Ensure provider names are not used in multiple client registrations.
//
// Note: a string comparer ignoring casing is deliberately used to prevent
// two providers using the same name with a different casing from being added.
if (options.Registrations
.Where(registration => !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));

12
src/OpenIddict.Client/OpenIddictClientEvents.cs

@ -693,6 +693,12 @@ public static partial class OpenIddictClientEvents
set => Transaction.Response = value;
}
/// <summary>
/// Gets or sets the name of the provider that will be
/// used to resolve the issuer identity, if applicable.
/// </summary>
public string? ProviderName { get; set; }
/// <summary>
/// Gets the additional parameters returned to caller.
/// </summary>
@ -837,6 +843,12 @@ public static partial class OpenIddictClientEvents
set => Transaction.Response = value;
}
/// <summary>
/// Gets or sets the name of the provider that will be
/// used to resolve the issuer identity, if applicable.
/// </summary>
public string? ProviderName { get; set; }
/// <summary>
/// Gets or sets the client identifier that will be used for the sign-out demand.
/// </summary>

32
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.

11
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;
/// <summary>
/// Contains the properties used to configure a client/server link.
/// </summary>
[DebuggerDisplay("{Issuer,nq}")]
public class OpenIddictClientRegistration
{
/// <summary>
@ -93,6 +95,15 @@ public class OpenIddictClientRegistration
/// </summary>
public Uri? Issuer { get; set; }
/// <summary>
/// Gets or sets the provider name, if applicable.
/// </summary>
/// <remarks>
/// If a Web provider integration with the same name was enabled, the
/// provider-specific options will be automatically imported and applied.
/// </remarks>
public string? ProviderName { get; set; }
/// <summary>
/// Gets or sets the static server configuration, if applicable.
/// </summary>

Loading…
Cancel
Save