diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs index 0756ba75..57216ce3 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs @@ -19,6 +19,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers /* * Authentication processing: */ + HandleNonStandardFrontchannelErrorResponse.Descriptor, AttachNonStandardClientAssertionTokenClaims.Descriptor, AttachTokenRequestNonStandardClientCredentials.Descriptor, AttachAdditionalUserinfoRequestParameters.Descriptor, @@ -32,6 +33,61 @@ public static partial class OpenIddictClientWebIntegrationHandlers .AddRange(Protection.DefaultHandlers) .AddRange(Userinfo.DefaultHandlers); + /// + /// Contains the logic responsible for handling non-standard + /// authorization errors for the providers that require it. + /// + public class HandleNonStandardFrontchannelErrorResponse : IOpenIddictClientHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(HandleFrontchannelErrorResponse.Descriptor.Order - 500) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + // Note: some providers are known to return non-standard errors. + // To normalize the set of errors handled by the OpenIddict client, + // the non-standard errors are mapped to their standard equivalent. + // + // Errors that are not handled here will be automatically handled + // by the standard handler present in the core OpenIddict client. + + if (context.Registration.ProviderName is Providers.LinkedIn) + { + var error = (string?) context.Request[Parameters.Error]; + if (string.IsNullOrEmpty(error)) + { + return default; + } + + if (string.Equals(error, "user_cancelled_authorize", StringComparison.Ordinal) || + string.Equals(error, "user_cancelled_login", StringComparison.Ordinal)) + { + context.Reject( + error: Errors.AccessDenied, + description: SR.GetResourceString(SR.ID2149), + uri: SR.FormatID8000(SR.ID2149)); + + return default; + } + } + + return default; + } + } + /// /// Contains the logic responsible for amending the client /// assertion methods for the providers that require it. @@ -148,7 +204,17 @@ public static partial class OpenIddictClientWebIntegrationHandlers Debug.Assert(context.UserinfoRequest is not null, SR.GetResourceString(SR.ID4008)); - if (context.Registration.ProviderName is Providers.StackExchange) + if (context.Registration.ProviderName is Providers.LinkedIn) + { + var options = context.Registration.GetLinkedInOptions(); + + // By default, LinkedIn returns all the basic fields except the profile image. + // To retrieve the profile image, a projection parameter must be sent with + // all the parameters that should be returned from the userinfo endpoint. + context.UserinfoRequest["projection"] = string.Concat("(", string.Join(",", options.Fields), ")"); + } + + else if (context.Registration.ProviderName is Providers.StackExchange) { var options = context.Registration.GetStackExchangeOptions(); diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml index 6ba621ff..222d930b 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml @@ -25,6 +25,31 @@ + + + + + + + + + + + + + + + + + +