diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs index c964710d..55988aa9 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs @@ -597,50 +597,61 @@ public static partial class OpenIddictClientWebIntegrationHandlers // return the user information using custom/non-standard token response parameters. // To work around that, this handler is responsible for extracting these parameters // from the token response and creating a userinfo token principal containing them. - if (context.Registration.ProviderName is Providers.StripeConnect) + + var parameters = context.Registration.ProviderName switch + { + // For Strava, include all the parameters contained in the "athlete" object. + // + // Note: the "athlete" node is not returned for grant_type=refresh_token requests. + Providers.Strava => context.TokenResponse["athlete"]?.GetNamedParameters(), + + // For Stripe, only include "livemode" and the parameters that are prefixed with "stripe_": + Providers.StripeConnect => + from parameter in context.TokenResponse.GetParameters() + where string.Equals(parameter.Key, "livemode", StringComparison.OrdinalIgnoreCase) || + parameter.Key.StartsWith("stripe_", StringComparison.OrdinalIgnoreCase) + select parameter, + + _ => null + }; + + if (parameters is null) { - var identity = new ClaimsIdentity( - context.Registration.TokenValidationParameters.AuthenticationType, - context.Registration.TokenValidationParameters.NameClaimType, - context.Registration.TokenValidationParameters.RoleClaimType); + return default; + } - var issuer = context.Configuration.Issuer!.AbsoluteUri; + var identity = new ClaimsIdentity( + context.Registration.TokenValidationParameters.AuthenticationType, + context.Registration.TokenValidationParameters.NameClaimType, + context.Registration.TokenValidationParameters.RoleClaimType); - foreach (var parameter in context.TokenResponse.GetParameters()) + var issuer = context.Configuration.Issuer!.AbsoluteUri; + + foreach (var parameter in parameters) + { + // Note: in the typical case, the response parameters should be deserialized from a + // JSON response and thus natively stored as System.Text.Json.JsonElement instances. + // + // In the rare cases where the underlying value wouldn't be a JsonElement instance + // (e.g when custom parameters are manually added to the response), the static + // conversion operator would take care of converting the underlying value to a + // JsonElement instance using the same value type as the original parameter value. + switch ((JsonElement) parameter.Value) { - switch (context.Registration.ProviderName) - { - // For Stripe, only include "livemode" and the parameters that are prefixed with "stripe_": - case Providers.StripeConnect when - !string.Equals(parameter.Key, "livemode", StringComparison.OrdinalIgnoreCase) && - !parameter.Key.StartsWith("stripe_", StringComparison.OrdinalIgnoreCase): - continue; - } - - // Note: in the typical case, the response parameters should be deserialized from a - // JSON response and thus natively stored as System.Text.Json.JsonElement instances. - // - // In the rare cases where the underlying value wouldn't be a JsonElement instance - // (e.g when custom parameters are manually added to the response), the static - // conversion operator would take care of converting the underlying value to a - // JsonElement instance using the same value type as the original parameter value. - switch ((JsonElement) parameter.Value) - { - // Top-level claims represented as arrays are split and mapped to multiple CLR claims - // to match the logic implemented by IdentityModel for JWT token deserialization. - case { ValueKind: JsonValueKind.Array } value: - identity.AddClaims(parameter.Key, value, issuer); - break; - - case { ValueKind: _ } value: - identity.AddClaim(parameter.Key, value, issuer); - break; - } + // Top-level claims represented as arrays are split and mapped to multiple CLR claims + // to match the logic implemented by IdentityModel for JWT token deserialization. + case { ValueKind: JsonValueKind.Array } value: + identity.AddClaims(parameter.Key, value, issuer); + break; + + case { ValueKind: _ } value: + identity.AddClaim(parameter.Key, value, issuer); + break; } - - context.UserinfoTokenPrincipal = new ClaimsPrincipal(identity); } + context.UserinfoTokenPrincipal = new ClaimsPrincipal(identity); + return default; } } @@ -762,7 +773,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers { // 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.Deezer => string.Join(",", context.Scopes), + Providers.Deezer or Providers.Strava => string.Join(",", context.Scopes), // The following providers are known to use plus-separated scopes instead of // the standard format (that requires using a space as the scope separator): diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml index dba95b85..c15c5261 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml @@ -591,6 +591,29 @@ Description="The site specified in userinfo requests (by default, 'stackoverflow')" /> + + + + + + + + + + + + +