From 848e943249679433b2c2d448243befddde655fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= <kevinchalet@gmail.com> Date: Tue, 1 Aug 2023 15:36:37 +0200 Subject: [PATCH] Update the web providers guide to include a section about claims mapping --- guides/contributing-a-new-web-provider.md | 62 +++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/guides/contributing-a-new-web-provider.md b/guides/contributing-a-new-web-provider.md index c9e6757..6a21526 100644 --- a/guides/contributing-a-new-web-provider.md +++ b/guides/contributing-a-new-web-provider.md @@ -156,6 +156,68 @@ the OpenIddict client to communicate with the remote authorization server. For i > </Provider> > ``` +## If the provider doesn't support standard OpenID Connect userinfo, map the provider-specific claims to their `ClaimTypes` equivalent + +If the provider doesn't return an `id_token` and doesn't offer a standard userinfo endpoint, it is likely it uses custom parameters +to represent things like the user identifier. If so, update the `MapCustomWebServicesFederationClaims` event handler to map these +parameters to the usual WS-Federation claims exposed by the .NET BCL `ClaimTypes` class, which simplifies integration with libraries +like ASP.NET Core Identity: + +```csharp +/// <summary> +/// Contains the logic responsible for mapping select custom claims to +/// their WS-Federation equivalent for the providers that require it. +/// </summary> +public sealed class MapCustomWebServicesFederationClaims : IOpenIddictClientHandler<ProcessAuthenticationContext> +{ + /// <summary> + /// Gets the default descriptor definition assigned to this handler. + /// </summary> + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() + .AddFilter<RequireWebServicesFederationClaimMappingEnabled>() + .UseSingletonHandler<MapCustomWebServicesFederationClaims>() + .SetOrder(MapStandardWebServicesFederationClaims.Descriptor.Order + 1_000) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// <inheritdoc/> + 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 "Email" node: + ProviderTypes.ServiceChannel => (string?) context.UserinfoResponse?["Email"], + + _ => context.MergedPrincipal.GetClaim(ClaimTypes.Email) + }); + + context.MergedPrincipal.SetClaim(ClaimTypes.Name, context.Registration.ProviderType switch + { + // ServiceChannel returns the user identifier as a custom "UserName" node: + ProviderTypes.ServiceChannel => (string?) context.UserinfoResponse?["UserName"], + + _ => context.MergedPrincipal.GetClaim(ClaimTypes.Name) + }); + + context.MergedPrincipal.SetClaim(ClaimTypes.NameIdentifier, context.Registration.ProviderType switch + { + // ServiceChannel returns the user identifier as a custom "UserId" node: + ProviderTypes.ServiceChannel => (string?) context.UserinfoResponse?["UserId"], + + _ => context.MergedPrincipal.GetClaim(ClaimTypes.NameIdentifier) + }); + + return default; + } +} +``` + ## Test the generated provider If the targeted service is fully standard-compliant, no additional configuration should be required at this point.