<h1id="contributing-a-new-web-provider">Contributing a new Web provider</h1>
<p>As part of the OpenIddict 4.0 effort, <ahref="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
<ahref="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><p>Unlike the aspnet-contrib providers, <strong>the source code needed to materialize the OpenIddict web providers is created dynamically</strong> using
<ahref="https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview">Roslyn Source Generators</a> and
<ahref="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
the approach used by the ASP.NET Core OAuth 2.0 base handler, that doesn't support the OpenID Connect discovery and OAuth 2.0 authorization server metadata specifications.</p>
<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>
<h2id="add-a-new-provider-node-for-the-new-provider">Add a new <code><Provider></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><Provider></code> node</strong> to the <ahref="https://github.com/openiddict/openiddict-core/blob/dev/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml">OpenIddictClientWebIntegrationProviders.xml</a> file. For instance:</p>
</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>
<h2id="add-an-environment-node-per-supported-environment">Add an <code><Environment></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><Environment></code> nodes - one per environment - MUST be added under <code><Provider></code>:</li>
<li>If the provider doesn't support multiple environment, a single <code><Environment></code> MUST be added (the <code>Name</code> attribute SHOULD be omitted):</li>
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><Environment></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>
the value given in the documentation or the base address of the server) <strong>and</strong> a <code><Configuration></code> node with the static configuration needed by
the OpenIddict client to communicate with the remote authorization server. For instance:</li>
</code></pre><divclass="NOTE"><h5>Note</h5><p>If the provider doesn't support <code>grant_type=refresh_token</code> and only supports the authorization code flow
<divclass="CAUTION"><h5>Caution</h5><p>If the provider doesn't support server metadata but is known to support Proof Key for Code Exchange (PKCE), a <code><CodeChallengeMethod></code> node MUST
be added under <code><Configuration></code> to ensure the OpenIddict client will send appropriate <code>code_challenge</code>/<code>code_challenge_method</code> parameters:</p>
<divclass="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.
If the provider you want to support requires adding a dynamic part in one of its URIs, a <code><Setting></code> node MUST be added under <code><Provider></code> to
store the tenant name. Once added, the URIs can include a placeholder of the same name:</p>
<h2id="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
throw new InvalidOperationException(SR.FormatID0334("data/user"))),
_ => context.Response
};
return default;
}
}
</code></pre><divclass="NOTE"><h5>Note</h5><p>If you'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: {
<h2id="if-the-provider-doesnt-support-standard-openid-connect-userinfo-map-the-provider-specific-claims-to-their-claimtypes-equivalent">If the provider doesn't support standard OpenID Connect userinfo, map the provider-specific claims to their <code>ClaimTypes</code> equivalent</h2>
<p>If the provider doesn't return an <code>id_token</code> and doesn't offer a standard userinfo endpoint, it is likely it uses custom parameters
<h2id="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.
If the provider doesn't expose its metadata, the supported methods MUST be added manually to the static configuration using one or multiple <code><TokenEndpointAuthMethod></code>:</li>
<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><Environment></code> node:</li>
<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
to separate multiple scopes, some providers require using a different separator (typically, a comma). If the provider you're adding requires such a hack,
update the <code>FormatNonStandardScopeParameter</code> event handler present in
<ahref="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><codeclass="lang-csharp">/// <summary>
/// Contains the logic responsible for overriding the standard "scope"
/// parameter for providers that are known to use a non-standard format.
/// </summary>
public class FormatNonStandardScopeParameter : IOpenIddictClientHandler<ProcessChallengeContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
</code></pre><divclass="NOTE"><h5>Note</h5><p>If the provider still doesn't work, it's unfortunately very likely more complex workarounds will be required.
If you're not familiar with the OpenIddict events model, open a ticket in the
<ahref="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>