diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs
index 2b1f3003..d85a3464 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Exchange.cs
@@ -7,8 +7,10 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Net.Http.Headers;
+using System.Net.Http.Json;
using System.Text;
using OpenIddict.Extensions;
+using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpConstants;
using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpHandlerFilters;
using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpHandlers;
using static OpenIddict.Client.SystemNetHttp.OpenIddictClientSystemNetHttpHandlers.Exchange;
@@ -25,7 +27,9 @@ public static partial class OpenIddictClientWebIntegrationHandlers
* Token request preparation:
*/
AttachNonStandardBasicAuthenticationCredentials.Descriptor,
+ AttachNonStandardRequestHeaders.Descriptor,
AttachNonStandardQueryStringParameters.Descriptor,
+ AttachNonStandardRequestPayload.Descriptor,
/*
* Token response extraction:
@@ -99,6 +103,50 @@ public static partial class OpenIddictClientWebIntegrationHandlers
}
}
+ ///
+ /// Contains the logic responsible for attaching additional
+ /// headers to the request for the providers that require it.
+ ///
+ public sealed class AttachNonStandardRequestHeaders : IOpenIddictClientHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictClientHandlerDescriptor Descriptor { get; }
+ = OpenIddictClientHandlerDescriptor.CreateBuilder()
+ .AddFilter()
+ .UseSingletonHandler()
+ .SetOrder(AttachUserAgentHeader.Descriptor.Order + 250)
+ .SetType(OpenIddictClientHandlerType.BuiltIn)
+ .Build();
+
+ ///
+ public ValueTask HandleAsync(PrepareTokenRequestContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved,
+ // this may indicate that the request was incorrectly processed by another client stack.
+ var request = context.Transaction.GetHttpRequestMessage() ??
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0173));
+
+ // Trovo requires sending the client identifier in a non-standard "client-id" header and
+ // the client secret in the payload (formatted using JSON instead of the standard format).
+ if (context.Registration.ProviderName is Providers.Trovo)
+ {
+ request.Headers.Add("Client-ID", context.Request.ClientId);
+
+ // Remove the client identifier from the request payload to ensure it's not sent twice.
+ context.Request.ClientId = null;
+ }
+
+ return default;
+ }
+ }
+
///
/// Contains the logic responsible for attaching non-standard query string
/// parameters to the token request for the providers that require it.
@@ -147,6 +195,54 @@ public static partial class OpenIddictClientWebIntegrationHandlers
}
}
+ ///
+ /// Contains the logic responsible for attaching a non-standard payload for the providers that require it.
+ ///
+ public sealed class AttachNonStandardRequestPayload : IOpenIddictClientHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictClientHandlerDescriptor Descriptor { get; }
+ = OpenIddictClientHandlerDescriptor.CreateBuilder()
+ .AddFilter()
+ .UseSingletonHandler()
+ .SetOrder(AttachFormParameters.Descriptor.Order + 500)
+ .SetType(OpenIddictClientHandlerType.BuiltIn)
+ .Build();
+
+ ///
+ public ValueTask HandleAsync(PrepareTokenRequestContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008));
+
+ // This handler only applies to System.Net.Http requests. If the HTTP request cannot be resolved,
+ // this may indicate that the request was incorrectly processed by another client stack.
+ var request = context.Transaction.GetHttpRequestMessage() ??
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0173));
+
+ request.Content = context.Registration.ProviderName switch
+ {
+ // Trovo returns a 500 internal server error when using the standard
+ // "application/x-www-form-urlencoded" format and requires using JSON.
+ Providers.Trovo => JsonContent.Create(context.Transaction.Request,
+ new MediaTypeHeaderValue(MediaTypes.Json)
+ {
+ CharSet = Charsets.Utf8
+ }),
+
+ _ => request.Content
+ };
+
+ return default;
+ }
+ }
+
///
/// Contains the logic responsible for attaching non-standard query string
/// parameters to the token request for the providers that require it.
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
index 10e30d85..e7ef263c 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs
@@ -67,12 +67,16 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// (which is statically set to the last version known to be supported by the OpenIddict integration).
if (context.Registration.ProviderName is Providers.Trakt)
{
- var options = context.Registration.GetTraktOptions();
-
- request.Headers.Add("trakt-api-key", options.ClientId);
+ request.Headers.Add("trakt-api-key", context.Registration.ClientId);
request.Headers.Add("trakt-api-version", "2");
}
+ // Trovo requires sending the client identifier as a separate, non-standard header.
+ else if (context.Registration.ProviderName is Providers.Trovo)
+ {
+ request.Headers.Add("Client-ID", context.Registration.ClientId);
+ }
+
return default;
}
}
@@ -109,15 +113,21 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// By default, OpenIddict sends the access token as part of the Authorization header
// using the Bearer authentication scheme. Some providers don't support this method
- // and require sending the access token as part of the userinfo request payload.
+ // and require sending the access token as part of the userinfo request payload
+ // or using a non-standard authentication scheme (e.g OAuth instead of Bearer).
(context.Request.AccessToken, request.Headers.Authorization) = context.Registration.ProviderName switch
{
+ // These providers require sending the access token as part of the request payload.
Providers.Deezer or
Providers.Mixcloud or
Providers.StackExchange
=> (request.Headers.Authorization?.Parameter, null),
+ // Trovo requires using the "OAuth" scheme instead of the standard "Bearer" value.
+ Providers.Trovo
+ => (null, new AuthenticationHeaderValue("OAuth", request.Headers.Authorization?.Parameter)),
+
_ => (context.Request.AccessToken, request.Headers.Authorization)
};
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
index 7b47aa51..451b90ae 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
@@ -22,6 +22,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
* Authentication processing:
*/
HandleNonStandardFrontchannelErrorResponse.Descriptor,
+ OverrideTokenEndpoint.Descriptor,
AttachNonStandardClientAssertionTokenClaims.Descriptor,
AttachTokenRequestNonStandardClientCredentials.Descriptor,
AdjustRedirectUriInTokenRequest.Descriptor,
@@ -125,6 +126,46 @@ public static partial class OpenIddictClientWebIntegrationHandlers
}
}
+ ///
+ /// Contains the logic responsible for overriding the address
+ /// of the token endpoint for the providers that require it.
+ ///
+ public sealed class OverrideTokenEndpoint : IOpenIddictClientHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictClientHandlerDescriptor Descriptor { get; }
+ = OpenIddictClientHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(ResolveTokenEndpoint.Descriptor.Order + 500)
+ .SetType(OpenIddictClientHandlerType.BuiltIn)
+ .Build();
+
+ ///
+ public ValueTask HandleAsync(ProcessAuthenticationContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ context.TokenEndpoint = context.Registration.ProviderName switch
+ {
+ // Trovo uses a different token endpoint for the refresh token grant.
+ //
+ // For more information, see
+ // https://developer.trovo.live/docs/APIs.html#_4-3-refresh-access-token.
+ Providers.Trovo when context.GrantType is GrantTypes.RefreshToken
+ => new Uri("https://open-api.trovo.live/openplatform/refreshtoken", UriKind.Absolute),
+
+ _ => context.TokenEndpoint
+ };
+
+ return default;
+ }
+ }
+
///
/// Contains the logic responsible for amending the client
/// assertion methods for the providers that require it.
@@ -639,6 +680,10 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// the standard format (that requires using a space as the scope separator):
Providers.Deezer => 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):
+ Providers.Trovo => string.Join("+", context.Scopes),
+
_ => context.Request.Scope
};
diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml
index 05ed1439..daa7e5d7 100644
--- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml
+++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml
@@ -632,6 +632,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+