Update the documentation pages

This commit is contained in:
OpenIddict Bot 2022-06-19 17:12:03 +00:00
parent 8c93e119b3
commit 91e907797e
3 changed files with 367 additions and 2 deletions

View File

@ -0,0 +1,349 @@
<!DOCTYPE html>
<!--[if IE]><![endif]-->
<html>
<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 ">
<meta name="generator" content="docfx 2.56.7.0">
<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">
</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>
<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">
<div>
<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">
</div>
</div>
<div class="sidebar-item-separator"></div>
<div id="sidetoggle">
<div id="sidetoc"></div>
</div>
</div>
<div class="footer">
<span>Generated by <strong>DocFX</strong></span>
</div>
</nav>
<main class="main-panel">
<div role="main" class="hide-when-search">
<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>
<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
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>
</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>
<pre><code class="lang-xml">&lt;Provider Name=&quot;Zendesk&quot; Documentation=&quot;https://developer.zendesk.com/documentation/live-chat/getting-started/auth/&quot;&gt;
&lt;/Provider&gt;
</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>
<ul>
<li>English</li>
<li>French</li>
<li>Spanish</li>
<li>Any other language</li>
</ul>
<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>
</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>
<pre><code class="lang-xml">&lt;Provider Name=&quot;Salesforce&quot;&gt;
&lt;Environment Name=&quot;Production&quot; /&gt;
&lt;Environment Name=&quot;Development&quot; /&gt;
&lt;/Provider&gt;
</code></pre><div class="WARNING"><h5>Warning</h5><p>When specifying multiple environments, the production environment MUST always appear first.</p>
</div>
<ul>
<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>
</ul>
<pre><code class="lang-xml">&lt;Provider Name=&quot;Google&quot;&gt;
&lt;Environment /&gt;
&lt;/Provider&gt;
</code></pre><h2 id="add-the-appropriate-configuration-for-each-environment">Add the appropriate configuration for each environment</h2>
<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.
In some cases, this information will be mentioned in the official documentation, but it&#39;s not always true. By convention, the server metadata
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>
<pre><code class="lang-xml">&lt;Provider Name=&quot;Google&quot;&gt;
&lt;Environment Issuer=&quot;https://accounts.google.com/&quot; /&gt;
&lt;/Provider&gt;
</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
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>
<pre><code class="lang-xml">&lt;Provider Name=&quot;Reddit&quot;&gt;
&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;
</code></pre><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
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>
<pre><code class="lang-xml">&lt;Provider Name=&quot;Fitbit&quot;&gt;
&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;
</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.
If the provider you want to support requires adding a dynamic part in one of its URLs, a <code>&lt;Setting&gt;</code> node MUST be added under <code>&lt;Provider&gt;</code> to
store the tenant name. Once added, the URLs can include a placeholder of the same name:</p>
<pre><code class="lang-xml">&lt;Provider Name=&quot;Zendesk&quot;&gt;
&lt;!--
Note: Zendesk is a multitenant provider that relies on subdomains to identify instances.
As such, the following URLs all include a {tenant} placeholder that will be dynamically
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;
&lt;Setting Name=&quot;Tenant&quot; Type=&quot;String&quot; Required=&quot;true&quot;
Description=&quot;Gets or sets the tenant used to identify the Zendesk instance.&quot; /&gt;
&lt;/Provider&gt;
</code></pre></div>
<h2 id="test-the-generated-provider">Test the generated provider</h2>
<p>If the targeted service is fully standard-compliant, no additional configuration should be required at this point.
To confirm it, build the solution and add the new provider to the <code>OpenIddict.Sandbox.AspNetCore.Client</code> sandbox:</p>
<ul>
<li>Update <code>Startup.cs</code> to use your new provider:</li>
</ul>
<pre><code class="lang-csharp">options.SetRedirectionEndpointUris(
// ... other providers...
&quot;/signin-[provider name]&quot;);
</code></pre><pre><code class="lang-csharp">// Register the Web providers integrations.
options.UseWebProviders()
// ... other providers...
.Add[provider name](new()
{
ClientId = &quot;bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ&quot;,
ClientSecret = &quot;VcohOgBp-6yQCurngo4GAyKeZh0D6SUCCSjJgEo1uRzJarjIUS&quot;,
RedirectUri = new Uri(&quot;https://localhost:44381/signin-[provider name]&quot;, UriKind.Absolute)
});
</code></pre><ul>
<li>Update <code>AuthenticationController.cs</code> to allow triggering challenges pointing to the new provider:</li>
</ul>
<pre><code class="lang-csharp">var issuer = provider switch
{
// ... other providers...
&quot;[provider name]&quot; =&gt; &quot;https://[provider issuer]/&quot;,
_ =&gt; null
};
</code></pre><ul>
<li>Update <code>Index.cshtml</code> under <code>Views\Home</code> to include a login button for the new provider:</li>
</ul>
<pre><code class="lang-html">&lt;a class=&quot;btn btn-lg btn-success&quot; asp-controller=&quot;Authentication&quot;
asp-action=&quot;Login&quot; asp-route-provider=&quot;[provider name]&quot;&gt;Sign in using [provider name]&lt;/a&gt;
</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>
</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.
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>
</ul>
<pre><code class="lang-xml">&lt;Provider Name=&quot;Twitter&quot;&gt;
&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;
</code></pre><ul>
<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>
<pre><code class="lang-xml">&lt;Provider Name=&quot;Twitter&quot; Documentation=&quot;https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code&quot;&gt;
&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;
</code></pre><ul>
<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&#39;re adding requires such a hack,
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));
}
context.Request.Scope = context.Registration.GetProviderName() 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):
Providers.Reddit =&gt; string.Join(&quot;,&quot;, context.Scopes),
_ =&gt; context.Request.Scope
};
return default;
}
}
</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
<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>
<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
<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>
<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>
</body>
</html>

View File

@ -8,6 +8,10 @@
<li>
<a href="index.html" class="sidebar-item" name="" title="Introduction">Introduction</a>
</li>
<li>
<a href="contributing-a-new-web-provider.html" class="sidebar-item" name="" title="Contributing a new Web provider">Contributing a new Web provider</a>
</li>
<li>
<a href="getting-started.html" class="sidebar-item" name="" title="Getting started">Getting started</a>

View File

@ -1594,6 +1594,18 @@
"is_incremental": false,
"version": ""
},
{
"type": "Conceptual",
"source_relative_path": "guides/contributing-a-new-web-provider.md",
"output": {
".html": {
"relative_path": "guides/contributing-a-new-web-provider.html",
"hash": "dssOySLr9rHvHRUmI0sn8w=="
}
},
"is_incremental": false,
"version": ""
},
{
"type": "Conceptual",
"source_relative_path": "guides/getting-started.md",
@ -1636,7 +1648,7 @@
"output": {
".html": {
"relative_path": "guides/toc.html",
"hash": "Q2Rq3xUOIstDdNnNV/CmBA=="
"hash": "graDxHuH3xaTHg5/QHY/zQ=="
}
},
"is_incremental": false,
@ -2445,7 +2457,7 @@
"ConceptualDocumentProcessor": {
"can_incremental": false,
"incrementalPhase": "build",
"total_file_count": 135,
"total_file_count": 136,
"skipped_file_count": 0
},
"ResourceDocumentProcessor": {