openiddict-documentation/guides/contributing-a-new-web-provider.html

483 lines
29 KiB
HTML
Raw Normal View History

2022-06-20 01:12:03 +08:00
<!DOCTYPE html>
<!--[if IE]><![endif]-->
<html>
2023-05-03 03:58:39 +08:00
2022-06-20 01:12:03 +08:00
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Contributing a new Web provider </title>
<meta name="viewport" content="width=device-width">
<meta name="title" content="Contributing a new Web provider ">
2023-05-03 03:58:39 +08:00
<meta name="generator" content="docfx 2.56.7.0">
2022-06-20 01:12:03 +08:00
<link rel="shortcut icon" href="../images/favicon.ico">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/night-owl.min.css">
<link rel="stylesheet" href="../styles/colors.css">
<link rel="stylesheet" href="../styles/discord.css">
<link rel="stylesheet" href="../styles/main.css">
<meta property="docfx:navrel" content="../toc.html">
<meta property="docfx:tocrel" content="toc.html">
2023-05-03 03:58:39 +08:00
2022-06-20 01:12:03 +08:00
</head>
<body>
<div class="top-navbar">
<a href="javascript:void(0);" class="burger-icon" onclick="toggleMenu()">
<svg name="Hamburger" style="vertical-align: middle;" width="24" height="24" viewbox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M20 6H4V9H20V6ZM4 10.999H20V13.999H4V10.999ZM4 15.999H20V18.999H4V15.999Z"></path></svg>
</a>
2023-05-03 03:58:39 +08:00
2022-06-20 01:12:03 +08:00
<a class="brand" href="../index.html">
<img src="../images/logo.png" alt="OpenIddict" class="logomark">
<span class="brand-title">OpenIddict</span>
</a>
</div>
<div class="body-content">
<div id="blackout" class="blackout" onclick="toggleMenu()"></div>
<nav id="sidebar" role="navigation">
<div class="sidebar">
2023-05-03 03:58:39 +08:00
2022-06-20 01:12:03 +08:00
<div>
2023-05-03 03:58:39 +08:00
2022-06-20 01:12:03 +08:00
<a class="brand" href="../index.html">
<img src="../images/logo.png" alt="OpenIddict" class="logomark">
<span class="brand-title">OpenIddict</span>
</a>
<div id="navbar">
2023-05-03 03:58:39 +08:00
2022-06-20 01:12:03 +08:00
</div>
2023-05-03 03:58:39 +08:00
2022-06-20 01:12:03 +08:00
</div>
<div class="sidebar-item-separator"></div>
2023-05-03 03:58:39 +08:00
2022-06-20 01:12:03 +08:00
<div id="sidetoggle">
<div id="sidetoc"></div>
</div>
</div>
<div class="footer">
2023-05-03 03:58:39 +08:00
<span>Generated by <strong>DocFX</strong></span>
2022-06-20 01:12:03 +08:00
</div>
</nav>
<main class="main-panel">
<div role="main" class="hide-when-search">
2023-05-03 03:58:39 +08:00
2022-06-20 01:12:03 +08:00
<div class="subnav navbar navbar-default">
<div class="container hide-when-search" id="breadcrumb">
<ul class="breadcrumb">
<li></li>
</ul>
</div>
</div>
<article class="content wrap" id="_content" data-uid="">
<h1 id="contributing-a-new-web-provider">Contributing a new Web provider</h1>
<p>As part of the OpenIddict 4.0 effort, <a href="https://kevinchalet.com/2022/02/25/introducing-the-openiddict-client/">a new client stack has been added to OpenIddict</a>.
To simplify integrating with social and enterprise providers that offer OAuth 2.0 and OpenID Connect services, a companion package
(named <code>OpenIddict.Client.WebIntegration</code>) has been added to the client stack. While it shares some similarities with the
<a href="https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers/issues">existing aspnet-contrib OAuth 2.0 providers</a>, <strong>there are actually important technical differences</strong>:</p>
<ul>
<li><p>Unlike the ASP.NET Core OAuth 2.0 base handler by the aspnet-contrib providers, <strong>the OpenIddict client is a dual-protocol OAuth 2.0 + OpenID Connect stack</strong>,
which means it can support both protocols <em>while</em> enforcing all the security checks required by these protocols.</p>
</li>
2023-03-04 00:46:42 +08:00
<li><p>While the aspnet-contrib providers only work on ASP.NET Core, <strong>the OpenIddict providers can not only be used in ASP.NET Core
and OWIN/ASP.NET 4.x applications, but also in Windows and Linux desktop applications</strong> without requiring any platform-specific code.</p>
</li>
2022-06-20 01:12:03 +08:00
<li><p>Unlike the aspnet-contrib providers, <strong>the source code needed to materialize the OpenIddict web providers is created dynamically</strong> using
<a href="https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview">Roslyn Source Generators</a> and
<a href="https://github.com/openiddict/openiddict-core/blob/dev/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml">a XML file that includes all the supported providers</a>
with the configuration needed to properly generate them. By eliminating all the plumbing code, the OpenIddict web providers are much easier to maintain and update.</p>
</li>
<li><p>To guarantee interoperability and make the best security choices, <strong>the OpenIddict client heavily relies on server configuration metadata</strong>, which differs from
2023-05-03 03:58:39 +08:00
the approach used by the ASP.NET Core OAuth 2.0 base handler, that doesn&#39;t support the OpenID Connect discovery and OAuth 2.0 authorization server metadata specifications.</p>
2022-06-20 01:12:03 +08:00
</li>
</ul>
<p>Due to these differences, <strong>contributing a new provider to the OpenIddict stack is quite different from adding an aspnet-contrib provider</strong>:</p>
<h2 id="add-a-new-provider-node-for-the-new-provider">Add a new <code>&lt;Provider&gt;</code> node for the new provider</h2>
<p>To add a new OpenIddict web provider, <strong>the first step is to add a new <code>&lt;Provider&gt;</code> node</strong> to the <a href="https://github.com/openiddict/openiddict-core/blob/dev/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml">OpenIddictClientWebIntegrationProviders.xml</a> file. For instance:</p>
2023-06-19 22:36:05 +08:00
<pre><code class="lang-xml">&lt;Provider Name=&quot;Zendesk&quot; Id=&quot;89fdfe22-c796-4227-a44a-d9cd3c467bbb&quot;
Documentation=&quot;https://developer.zendesk.com/documentation/live-chat/getting-started/auth/&quot;&gt;
2022-06-20 01:12:03 +08:00
&lt;/Provider&gt;
2023-05-03 03:58:39 +08:00
</code></pre><p>If available, a link to the official documentation MUST be added. If multiple languages are available, the following order SHOULD be used:</p>
2022-06-20 01:12:03 +08:00
<ul>
<li>English</li>
<li>French</li>
<li>Spanish</li>
<li>Any other language</li>
</ul>
2023-05-03 03:58:39 +08:00
<div class="WARNING"><h5>Warning</h5><p>The added provider MUST be placed in the XML file such that the alphabetical order is respected.</p>
2022-06-20 01:12:03 +08:00
</div>
<h2 id="add-an-environment-node-per-supported-environment">Add an <code>&lt;Environment&gt;</code> node per supported environment</h2>
<p><strong>The second step is to determine whether the service offers multiple environments</strong> (e.g Production, Testing or Development).</p>
<ul>
<li>If the provider supports multiple environments, multiple <code>&lt;Environment&gt;</code> nodes - one per environment - MUST be added under <code>&lt;Provider&gt;</code>:</li>
</ul>
2023-06-19 22:36:05 +08:00
<pre><code class="lang-xml">&lt;Provider Name=&quot;Salesforce&quot; Id=&quot;ce5bc4bc-6133-4e87-85ad-626b3c0a4427&quot;&gt;
2022-06-20 01:12:03 +08:00
&lt;Environment Name=&quot;Production&quot; /&gt;
&lt;Environment Name=&quot;Development&quot; /&gt;
&lt;/Provider&gt;
2023-05-03 03:58:39 +08:00
</code></pre><div class="WARNING"><h5>Warning</h5><p>When specifying multiple environments, the production environment MUST always appear first.</p>
2022-06-20 01:12:03 +08:00
</div>
<ul>
2023-05-03 03:58:39 +08:00
<li>If the provider doesn&#39;t support multiple environment, a single <code>&lt;Environment&gt;</code> MUST be added (the <code>Name</code> attribute SHOULD be omitted):</li>
2022-06-20 01:12:03 +08:00
</ul>
2023-06-19 22:36:05 +08:00
<pre><code class="lang-xml">&lt;Provider Name=&quot;Google&quot; Id=&quot;e0e90ce7-adb5-4b05-9f54-594941e5d960&quot;&gt;
2022-06-20 01:12:03 +08:00
&lt;Environment /&gt;
&lt;/Provider&gt;
2023-05-03 03:58:39 +08:00
</code></pre><h2 id="add-the-appropriate-configuration-for-each-environment">Add the appropriate configuration for each environment</h2>
2022-06-20 01:12:03 +08:00
<p><strong>The third step is the most complicated one: adding the appropriate configuration for each of the added environments</strong>.</p>
<p>For that, you MUST first determine whether the environment supports OpenID Connect discovery or OAuth 2.0 authorization server metadata.
2023-05-03 03:58:39 +08:00
In some cases, this information will be mentioned in the official documentation, but it&#39;s not always true. By convention, the server metadata
2022-06-20 01:12:03 +08:00
is typically served from <code>https://provider/.well-known/openid-configuration</code>: if you get a valid JSON document from this endpoint, the server
supports OpenID Connect/OAuth 2.0 server metadata.</p>
<ul>
<li>If the server supports OpenID Connect/OAuth 2.0 server metadata, add an <code>Issuer</code> attribute to <code>&lt;Environment&gt;</code> corresponding to the provider address
without the <code>/.well-known/openid-configuration</code> part. For instance, Google exposes its discovery document at <code>https://accounts.google.com/.well-known/openid-configuration</code>
so the correct issuer to use is <code>https://accounts.google.com/</code>:</li>
</ul>
2023-06-19 22:36:05 +08:00
<pre><code class="lang-xml">&lt;Provider Name=&quot;Google&quot; Id=&quot;e0e90ce7-adb5-4b05-9f54-594941e5d960&quot;&gt;
2022-06-20 01:12:03 +08:00
&lt;Environment Issuer=&quot;https://accounts.google.com/&quot; /&gt;
&lt;/Provider&gt;
2023-05-03 03:58:39 +08:00
</code></pre><ul>
<li>If the server doesn&#39;t support OpenID Connect/OAuth 2.0 server metadata, you MUST add an <code>Issuer</code> attribute (corresponding to either
2022-06-20 01:12:03 +08:00
the value given in the documentation or the base address of the server) <strong>and</strong> a <code>&lt;Configuration&gt;</code> node with the static configuration needed by
the OpenIddict client to communicate with the remote authorization server. For instance:</li>
</ul>
2023-06-19 22:36:05 +08:00
<pre><code class="lang-xml">&lt;Provider Name=&quot;Reddit&quot; Id=&quot;01ae8033-935c-43b9-8568-eaf4d08c0613&quot;&gt;
2022-11-27 14:59:49 +08:00
&lt;Environment Issuer=&quot;https://www.reddit.com/&quot;&gt;
&lt;Configuration AuthorizationEndpoint=&quot;https://www.reddit.com/api/v1/authorize&quot;
TokenEndpoint=&quot;https://www.reddit.com/api/v1/access_token&quot;
UserinfoEndpoint=&quot;https://oauth.reddit.com/api/v1/me&quot;&gt;
2022-12-27 02:13:48 +08:00
&lt;GrantType Value=&quot;authorization_code&quot; /&gt;
&lt;GrantType Value=&quot;refresh_token&quot; /&gt;
2022-11-27 14:59:49 +08:00
&lt;/Configuration&gt;
&lt;/Environment&gt;
&lt;/Provider&gt;
2023-05-03 03:58:39 +08:00
</code></pre><div class="NOTE"><h5>Note</h5><p>If the provider doesn&#39;t support <code>grant_type=refresh_token</code> and only supports the authorization code flow
2022-11-27 14:59:49 +08:00
(typically with non-expiring access tokens), the <code>&lt;GrantType&gt;</code> nodes MUST be removed for clarity,
as the authorization code flow is always considered supported by default if no <code>&lt;GrantType&gt;</code> is present:</p>
2023-06-19 22:36:05 +08:00
<pre><code class="lang-xml">&lt;Provider Name=&quot;Reddit&quot; Id=&quot;01ae8033-935c-43b9-8568-eaf4d08c0613&quot;&gt;
2022-06-20 01:12:03 +08:00
&lt;Environment Issuer=&quot;https://www.reddit.com/&quot;&gt;
&lt;Configuration AuthorizationEndpoint=&quot;https://www.reddit.com/api/v1/authorize&quot;
TokenEndpoint=&quot;https://www.reddit.com/api/v1/access_token&quot;
UserinfoEndpoint=&quot;https://oauth.reddit.com/api/v1/me&quot; /&gt;
&lt;/Environment&gt;
&lt;/Provider&gt;
2023-05-03 03:58:39 +08:00
</code></pre></div>
<div class="CAUTION"><h5>Caution</h5><p>If the provider doesn&#39;t support server metadata but is known to support Proof Key for Code Exchange (PKCE), a <code>&lt;CodeChallengeMethod&gt;</code> node MUST
2022-06-20 01:12:03 +08:00
be added under <code>&lt;Configuration&gt;</code> to ensure the OpenIddict client will send appropriate <code>code_challenge</code>/<code>code_challenge_method</code> parameters:</p>
2023-06-19 22:36:05 +08:00
<pre><code class="lang-xml">&lt;Provider Name=&quot;Fitbit&quot; Id=&quot;10a558b9-8c81-47cc-8941-e54d0432fd51&quot;&gt;
2022-06-20 01:12:03 +08:00
&lt;Environment Issuer=&quot;https://www.fitbit.com/&quot;&gt;
&lt;Configuration AuthorizationEndpoint=&quot;https://www.fitbit.com/oauth2/authorize&quot;
TokenEndpoint=&quot;https://api.fitbit.com/oauth2/token&quot;
UserinfoEndpoint=&quot;https://api.fitbit.com/1/user/-/profile.json&quot;&gt;
&lt;CodeChallengeMethod Value=&quot;S256&quot; /&gt;
&lt;/Configuration&gt;
&lt;/Environment&gt;
&lt;/Provider&gt;
2023-05-03 03:58:39 +08:00
</code></pre></div>
<div class="NOTE"><h5>Note</h5><p>Some providers use a multitenant configuration that relies on a subdomain, a custom domain or a virtual path to discriminate tenant instances.
2022-12-10 22:46:03 +08:00
If the provider you want to support requires adding a dynamic part in one of its URIs, a <code>&lt;Setting&gt;</code> node MUST be added under <code>&lt;Provider&gt;</code> to
store the tenant name. Once added, the URIs can include a placeholder of the same name:</p>
2023-06-19 22:36:05 +08:00
<pre><code class="lang-xml">&lt;Provider Name=&quot;Zendesk&quot; Id=&quot;89fdfe22-c796-4227-a44a-d9cd3c467bbb&quot;&gt;
2022-06-20 01:12:03 +08:00
&lt;!--
Note: Zendesk is a multitenant provider that relies on subdomains to identify instances.
2022-12-10 22:46:03 +08:00
As such, the following URIs all include a {tenant} placeholder that will be dynamically
2022-06-20 01:12:03 +08:00
replaced by OpenIddict at runtime by the tenant configured in the Zendesk settings.
--&gt;
&lt;Environment Issuer=&quot;https://{tenant}.zendesk.com/&quot;&gt;
&lt;Configuration AuthorizationEndpoint=&quot;https://{tenant}.zendesk.com/oauth/authorizations/new&quot;
TokenEndpoint=&quot;https://{tenant}.zendesk.com/oauth/tokens&quot;
UserinfoEndpoint=&quot;https://{tenant}.zendesk.com/api/v2/users/me&quot; /&gt;
&lt;/Environment&gt;
2022-10-06 22:30:17 +08:00
&lt;Setting PropertyName=&quot;Tenant&quot; ParameterName=&quot;tenant&quot; Type=&quot;String&quot; Required=&quot;true&quot;
Description=&quot;The tenant used to identify the Zendesk instance&quot; /&gt;
2022-06-20 01:12:03 +08:00
&lt;/Provider&gt;
2023-05-03 03:58:39 +08:00
</code></pre></div>
2023-08-05 23:05:19 +08:00
<h2 id="unwrap-userinfo-responses-if-necessary">Unwrap userinfo responses if necessary</h2>
<p>If the provider returns wrapped or nested userinfo responses (e.g under a <code>response</code> or <code>data</code> node), the <code>UnwrapUserinfoResponse</code> handler in
<a href="https://github.com/openiddict/openiddict-core/blob/dev/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs">OpenIddictClientWebIntegrationHandlers.Userinfo.cs</a>
must be updated to unwrap the userinfo payload and allow OpenIddict to map them to flat CLR <code>Claim</code> instances:</p>
<pre><code class="lang-csharp">/// &lt;summary&gt;
/// Contains the logic responsible for extracting the userinfo response
/// from nested JSON nodes (e.g &quot;data&quot;) for the providers that require it.
/// &lt;/summary&gt;
public sealed class UnwrapUserinfoResponse : IOpenIddictClientHandler&lt;ExtractUserinfoResponseContext&gt;
{
/// &lt;summary&gt;
/// Gets the default descriptor definition assigned to this handler.
/// &lt;/summary&gt;
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder&lt;ExtractUserinfoResponseContext&gt;()
.UseSingletonHandler&lt;UnwrapUserinfoResponse&gt;()
.SetOrder(int.MaxValue - 50_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// &lt;inheritdoc/&gt;
public ValueTask HandleAsync(ExtractUserinfoResponseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
context.Response = context.Registration.ProviderType switch
{
// Fitbit returns a nested &quot;user&quot; object.
ProviderTypes.Fitbit =&gt; new(context.Response[&quot;user&quot;]?.GetNamedParameters() ??
throw new InvalidOperationException(SR.FormatID0334(&quot;user&quot;))),
// StackExchange returns an &quot;items&quot; array containing a single element.
ProviderTypes.StackExchange =&gt; new(context.Response[&quot;items&quot;]?[0]?.GetNamedParameters() ??
throw new InvalidOperationException(SR.FormatID0334(&quot;items/0&quot;))),
// SubscribeStar returns a nested &quot;user&quot; object that is itself nested in a GraphQL &quot;data&quot; node.
ProviderTypes.SubscribeStar =&gt; new(context.Response[&quot;data&quot;]?[&quot;user&quot;]?.GetNamedParameters() ??
throw new InvalidOperationException(SR.FormatID0334(&quot;data/user&quot;))),
_ =&gt; context.Response
};
return default;
}
}
</code></pre><div class="NOTE"><h5>Note</h5><p>If you&#39;re unsure whether the provider returns wrapped responses or not, the
received payload can be found in the logs after a successful authorization flow:</p>
<pre><code>OpenIddict.Client.OpenIddictClientDispatcher: Information: The userinfo response returned by https://contoso.com/users/me was successfully extracted: {
&quot;data&quot;: {
&quot;username&quot;: &quot;odile.donat&quot;,
&quot;name&quot;: &quot;Odile Donat&quot;,
&quot;email&quot;: &quot;odile.donat@fabrikam.com&quot;
}
}.
</code></pre></div>
2023-08-01 21:46:50 +08:00
<h2 id="if-the-provider-doesnt-support-standard-openid-connect-userinfo-map-the-provider-specific-claims-to-their-claimtypes-equivalent">If the provider doesn&#39;t support standard OpenID Connect userinfo, map the provider-specific claims to their <code>ClaimTypes</code> equivalent</h2>
<p>If the provider doesn&#39;t return an <code>id_token</code> and doesn&#39;t offer a standard userinfo endpoint, it is likely it uses custom parameters
2023-08-05 23:05:19 +08:00
to represent things like the user identifier. If so, update the <code>MapCustomWebServicesFederationClaims</code> event handler in
<a href="https://github.com/openiddict/openiddict-core/blob/dev/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs">OpenIddictClientWebIntegrationHandlers.cs</a>
to map these parameters to the usual WS-Federation claims exposed by the .NET BCL <code>ClaimTypes</code> class, which simplifies integration
with libraries like ASP.NET Core Identity:</p>
2023-08-01 21:46:50 +08:00
<pre><code class="lang-csharp">/// &lt;summary&gt;
/// Contains the logic responsible for mapping select custom claims to
/// their WS-Federation equivalent for the providers that require it.
/// &lt;/summary&gt;
public sealed class MapCustomWebServicesFederationClaims : IOpenIddictClientHandler&lt;ProcessAuthenticationContext&gt;
{
/// &lt;summary&gt;
/// Gets the default descriptor definition assigned to this handler.
/// &lt;/summary&gt;
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder&lt;ProcessAuthenticationContext&gt;()
.AddFilter&lt;RequireWebServicesFederationClaimMappingEnabled&gt;()
.UseSingletonHandler&lt;MapCustomWebServicesFederationClaims&gt;()
.SetOrder(MapStandardWebServicesFederationClaims.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// &lt;inheritdoc/&gt;
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
context.MergedPrincipal.SetClaim(ClaimTypes.Email, context.Registration.ProviderType switch
{
// ServiceChannel returns the user identifier as a custom &quot;Email&quot; node:
ProviderTypes.ServiceChannel =&gt; (string?) context.UserinfoResponse?[&quot;Email&quot;],
_ =&gt; context.MergedPrincipal.GetClaim(ClaimTypes.Email)
});
context.MergedPrincipal.SetClaim(ClaimTypes.Name, context.Registration.ProviderType switch
{
// ServiceChannel returns the user identifier as a custom &quot;UserName&quot; node:
ProviderTypes.ServiceChannel =&gt; (string?) context.UserinfoResponse?[&quot;UserName&quot;],
_ =&gt; context.MergedPrincipal.GetClaim(ClaimTypes.Name)
});
context.MergedPrincipal.SetClaim(ClaimTypes.NameIdentifier, context.Registration.ProviderType switch
{
// ServiceChannel returns the user identifier as a custom &quot;UserId&quot; node:
ProviderTypes.ServiceChannel =&gt; (string?) context.UserinfoResponse?[&quot;UserId&quot;],
_ =&gt; context.MergedPrincipal.GetClaim(ClaimTypes.NameIdentifier)
});
return default;
}
}
</code></pre><h2 id="test-the-generated-provider">Test the generated provider</h2>
2022-06-20 01:12:03 +08:00
<p>If the targeted service is fully standard-compliant, no additional configuration should be required at this point.
2023-06-19 22:36:05 +08:00
To confirm it, build the solution and add an instance of the new provider to the <code>OpenIddict.Sandbox.AspNetCore.Client</code> sandbox:</p>
2022-06-20 01:12:03 +08:00
<ul>
2022-12-27 02:13:48 +08:00
<li>Update <code>Startup.cs</code> to register your new provider:</li>
2022-06-20 01:12:03 +08:00
</ul>
2022-12-27 02:13:48 +08:00
<pre><code class="lang-csharp">// Register the Web providers integrations.
2022-06-20 01:12:03 +08:00
options.UseWebProviders()
// ... other providers...
2023-06-19 22:36:05 +08:00
.Add[provider name](options =&gt;
2022-06-20 01:12:03 +08:00
{
2022-10-06 22:30:17 +08:00
options.SetClientId(&quot;bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ&quot;);
options.SetClientSecret(&quot;VcohOgBp-6yQCurngo4GAyKeZh0D6SUCCSjJgEo1uRzJarjIUS&quot;);
2022-12-10 22:46:03 +08:00
options.SetRedirectUri(&quot;callback/login/[provider name]&quot;);
2022-06-20 01:12:03 +08:00
});
2023-05-03 03:58:39 +08:00
</code></pre><ul>
2022-06-20 01:12:03 +08:00
<li>Update <code>AuthenticationController.cs</code> to allow triggering challenges pointing to the new provider:</li>
</ul>
2022-10-10 23:04:12 +08:00
<pre><code class="lang-csharp">// 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, &quot;Local&quot;, StringComparison.Ordinal) &amp;&amp;
2022-06-20 01:12:03 +08:00
// ... other providers...
2022-10-10 23:04:12 +08:00
!string.Equals(provider, [provider name], StringComparison.Ordinal))
{
return BadRequest();
}
2023-05-03 03:58:39 +08:00
</code></pre><ul>
2022-06-20 01:12:03 +08:00
<li>Update <code>Index.cshtml</code> under <code>Views\Home</code> to include a login button for the new provider:</li>
</ul>
2022-10-10 23:04:12 +08:00
<pre><code class="lang-html">&lt;button class=&quot;btn btn-lg btn-success&quot; type=&quot;submit&quot; name=&quot;provider&quot; value=&quot;[provider name]&quot;&gt;
Sign in using [provider name]
&lt;/button&gt;
2023-05-03 03:58:39 +08:00
</code></pre><div class="NOTE"><h5>Note</h5><p>Unless you agree to share your sandbox credentials with the OpenIddict developers, the changes
made to the sandbox project don&#39;t need to be committed and included in your pull request.</p>
2022-06-20 01:12:03 +08:00
</div>
<h2 id="if-necessary-add-the-necessary-workarounds-for-the-provider-to-work-correctly">If necessary, add the necessary workarounds for the provider to work correctly</h2>
<p>If an error occurs during the authentication process, the provider MAY require one or multiple workarounds for the integration to work correctly:</p>
<ul>
<li>The provider MAY require sending the client credentials as part of the <code>Authorization</code> header using basic authentication (i.e <code>client_secret_basic</code>).
Providers that implement OpenID Connect discovery or OAuth 2.0 authorization server metadata will typically return the client authentication methods they support.
2023-05-03 03:58:39 +08:00
If the provider doesn&#39;t expose its metadata, the supported methods MUST be added manually to the static configuration using one or multiple <code>&lt;TokenEndpointAuthMethod&gt;</code>:</li>
2022-06-20 01:12:03 +08:00
</ul>
2023-06-19 22:36:05 +08:00
<pre><code class="lang-xml">&lt;Provider Name=&quot;Twitter&quot; Id=&quot;1fd20ab5-d3f2-40aa-8c91-094f71652c65&quot;&gt;
2022-06-20 01:12:03 +08:00
&lt;Environment Issuer=&quot;https://twitter.com/&quot;&gt;
&lt;Configuration AuthorizationEndpoint=&quot;https://twitter.com/i/oauth2/authorize&quot;
TokenEndpoint=&quot;https://api.twitter.com/2/oauth2/token&quot;
UserinfoEndpoint=&quot;https://api.twitter.com/2/users/me&quot;&gt;
&lt;CodeChallengeMethod Value=&quot;S256&quot; /&gt;
&lt;TokenEndpointAuthMethod Value=&quot;client_secret_basic&quot; /&gt;
&lt;/Configuration&gt;
&lt;/Environment&gt;
&lt;/Provider&gt;
2023-05-03 03:58:39 +08:00
</code></pre><ul>
2022-06-20 01:12:03 +08:00
<li>The provider MAY require sending one or multiple default or required scopes. If so, the default/required scopes MUST be added to the <code>&lt;Environment&gt;</code> node:</li>
</ul>
2023-06-19 22:36:05 +08:00
<pre><code class="lang-xml">&lt;Provider Name=&quot;Twitter&quot; Id=&quot;1fd20ab5-d3f2-40aa-8c91-094f71652c65&quot;&gt;
2022-06-20 01:12:03 +08:00
&lt;Environment Issuer=&quot;https://twitter.com/&quot;&gt;
&lt;Configuration AuthorizationEndpoint=&quot;https://twitter.com/i/oauth2/authorize&quot;
TokenEndpoint=&quot;https://api.twitter.com/2/oauth2/token&quot;
UserinfoEndpoint=&quot;https://api.twitter.com/2/users/me&quot;&gt;
&lt;CodeChallengeMethod Value=&quot;S256&quot; /&gt;
&lt;TokenEndpointAuthMethod Value=&quot;client_secret_basic&quot; /&gt;
&lt;/Configuration&gt;
&lt;!--
Note: Twitter requires requesting the &quot;tweet.read&quot; and &quot;users.read&quot; scopes for the
userinfo endpoint to work correctly. As such, these 2 scopes are marked as required
so they are always sent even if they were not explicitly added by the user.
--&gt;
&lt;Scope Name=&quot;tweet.read&quot; Default=&quot;true&quot; Required=&quot;true&quot; /&gt;
&lt;Scope Name=&quot;users.read&quot; Default=&quot;true&quot; Required=&quot;true&quot; /&gt;
&lt;/Environment&gt;
&lt;/Provider&gt;
2023-05-03 03:58:39 +08:00
</code></pre><ul>
2022-06-20 01:12:03 +08:00
<li>The provider MAY require sending the scopes using a different separator than the standard one. While the OAuth 2.0 specification requires using a space
2023-05-03 03:58:39 +08:00
to separate multiple scopes, some providers require using a different separator (typically, a comma). If the provider you&#39;re adding requires such a hack,
2022-06-20 01:12:03 +08:00
update the <code>FormatNonStandardScopeParameter</code> event handler present in
<a href="https://github.com/openiddict/openiddict-core/blob/dev/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs">OpenIddictClientWebIntegrationHandlers.cs</a> to use the correct separator required by the provider.</li>
</ul>
<pre><code class="lang-csharp">/// &lt;summary&gt;
/// Contains the logic responsible for overriding the standard &quot;scope&quot;
/// parameter for providers that are known to use a non-standard format.
/// &lt;/summary&gt;
public class FormatNonStandardScopeParameter : IOpenIddictClientHandler&lt;ProcessChallengeContext&gt;
{
/// &lt;summary&gt;
/// Gets the default descriptor definition assigned to this handler.
/// &lt;/summary&gt;
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder&lt;ProcessChallengeContext&gt;()
.AddFilter&lt;RequireInteractiveGrantType&gt;()
.UseSingletonHandler&lt;FormatNonStandardScopeParameter&gt;()
.SetOrder(AttachChallengeParameters.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// &lt;inheritdoc/&gt;
public ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
2023-06-19 22:36:05 +08:00
context.Request.Scope = context.Registration.ProviderType switch
2022-06-20 01:12:03 +08:00
{
// The following providers are known to use comma-separated scopes instead of
// the standard format (that requires using a space as the scope separator):
2023-06-19 22:36:05 +08:00
ProviderTypes.Reddit =&gt; string.Join(&quot;,&quot;, context.Scopes),
2022-06-20 01:12:03 +08:00
_ =&gt; context.Request.Scope
};
return default;
}
}
2023-05-03 03:58:39 +08:00
</code></pre><div class="NOTE"><h5>Note</h5><p>If the provider still doesn&#39;t work, it&#39;s unfortunately very likely more complex workarounds will be required.
If you&#39;re not familiar with the OpenIddict events model, open a ticket in the
2022-06-20 01:12:03 +08:00
<a href="https://github.com/openiddict/openiddict-core/issues"><code>openiddict-core</code></a> repository to get help.</p>
</div>
<h2 id="send-a-pull-request-against-the-openiddict-core-repository">Send a pull request against the <code>openiddict-core</code> repository</h2>
2023-05-03 03:58:39 +08:00
<p>Once you&#39;ve been able to confirm that your provider works correctly, all you need to do is send a PR so that it can be added to the
2022-06-20 01:12:03 +08:00
<a href="https://github.com/openiddict/openiddict-core/issues"><code>openiddict-repo</code></a> and ship with the already supported providers as part of the next update.</p>
</article>
</div>
</main>
</div>
2023-05-03 03:58:39 +08:00
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js"></script>
<script type="text/javascript" src="../styles/jquery.twbsPagination.js"></script>
<script type="text/javascript" src="../styles/url.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/anchor-js/anchor.min.js"></script>
<script type="text/javascript" src="../styles/docfx.js"></script>
<script type="text/javascript" src="../styles/main.js"></script>
2022-06-20 01:12:03 +08:00
</body>
</html>