Browse Source

Update the Apple integration to support token revocation

pull/1986/head
Kévin Chalet 2 years ago
parent
commit
2078c7c8c4
  1. 66
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs
  2. 104
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
  3. 15
      src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs

66
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Discovery.cs

@ -23,8 +23,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
AmendGrantTypes.Descriptor,
AmendCodeChallengeMethods.Descriptor,
AmendScopes.Descriptor,
AmendDeviceAuthorizationEndpointClientAuthenticationMethods.Descriptor,
AmendTokenEndpointClientAuthenticationMethods.Descriptor,
AmendClientAuthenticationMethods.Descriptor,
AmendEndpoints.Descriptor
];
@ -230,6 +229,7 @@ public static partial class OpenIddictClientWebIntegrationHandlers
/// Contains the logic responsible for amending the client authentication methods
/// supported by the device authorization endpoint for the providers that require it.
/// </summary>
[Obsolete("This class is obsolete and will be removed in a future version.", error: true)]
public sealed class AmendDeviceAuthorizationEndpointClientAuthenticationMethods : IOpenIddictClientHandler<HandleConfigurationResponseContext>
{
/// <summary>
@ -244,32 +244,14 @@ public static partial class OpenIddictClientWebIntegrationHandlers
/// <inheritdoc/>
public ValueTask HandleAsync(HandleConfigurationResponseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Google doesn't properly implement the device authorization grant, doesn't support
// basic client authentication for the device authorization endpoint and returns a
// generic "invalid_request" request when using "client_secret_basic" instead of
// sending the client identifier in the request form. To work around this limitation,
// "client_secret_post" is listed as the only supported client authentication method.
if (context.Registration.ProviderType is ProviderTypes.Google)
{
context.Configuration.DeviceAuthorizationEndpointAuthMethodsSupported.Clear();
context.Configuration.DeviceAuthorizationEndpointAuthMethodsSupported.Add(
ClientAuthenticationMethods.ClientSecretPost);
}
return default;
}
=> throw new NotSupportedException(SR.GetResourceString(SR.ID0403));
}
/// <summary>
/// Contains the logic responsible for amending the client authentication
/// methods supported by the token endpoint for the providers that require it.
/// </summary>
[Obsolete("This class is obsolete and will be removed in a future version.", error: true)]
public sealed class AmendTokenEndpointClientAuthenticationMethods : IOpenIddictClientHandler<HandleConfigurationResponseContext>
{
/// <summary>
@ -282,6 +264,27 @@ public static partial class OpenIddictClientWebIntegrationHandlers
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(HandleConfigurationResponseContext context)
=> throw new NotSupportedException(SR.GetResourceString(SR.ID0403));
}
/// <summary>
/// Contains the logic responsible for amending the supported client
/// authentication methods for the providers that require it.
/// </summary>
public sealed class AmendClientAuthenticationMethods : IOpenIddictClientHandler<HandleConfigurationResponseContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<HandleConfigurationResponseContext>()
.UseSingletonHandler<AmendClientAuthenticationMethods>()
.SetOrder(ExtractTokenEndpointClientAuthenticationMethods.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(HandleConfigurationResponseContext context)
{
@ -290,8 +293,8 @@ public static partial class OpenIddictClientWebIntegrationHandlers
throw new ArgumentNullException(nameof(context));
}
// Apple implements a non-standard client authentication method for the token endpoint
// that is inspired by the standard private_key_jwt method but doesn't use the standard
// Apple implements a non-standard client authentication method for its endpoints that
// is inspired by the standard private_key_jwt method but doesn't use the standard
// client_assertion/client_assertion_type parameters. Instead, the client assertion
// must be sent as a "dynamic" client secret using client_secret_post. Since the logic
// is the same as private_key_jwt, the configuration is amended to assume Apple supports
@ -299,10 +302,25 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// parameter using the client assertion once it has been generated by OpenIddict.
if (context.Registration.ProviderType is ProviderTypes.Apple)
{
context.Configuration.RevocationEndpointAuthMethodsSupported.Add(
ClientAuthenticationMethods.PrivateKeyJwt);
context.Configuration.TokenEndpointAuthMethodsSupported.Add(
ClientAuthenticationMethods.PrivateKeyJwt);
}
// Google doesn't properly implement the device authorization grant, doesn't support
// basic client authentication for the device authorization endpoint and returns
// a generic "invalid_request" error when using "client_secret_basic" instead of
// sending the client identifier in the request form. To work around this limitation,
// "client_secret_post" is listed as the only supported client authentication method.
else if (context.Registration.ProviderType is ProviderTypes.Google)
{
context.Configuration.DeviceAuthorizationEndpointAuthMethodsSupported.Clear();
context.Configuration.DeviceAuthorizationEndpointAuthMethodsSupported.Add(
ClientAuthenticationMethods.ClientSecretPost);
}
// LinkedIn doesn't support sending the client credentials using basic authentication but
// doesn't return a "token_endpoint_auth_methods_supported" node containing alternative
// authentication methods, making basic authentication the default authentication method.

104
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs

@ -49,6 +49,12 @@ public static partial class OpenIddictClientWebIntegrationHandlers
IncludeStateParameterInRedirectUri.Descriptor,
AttachAdditionalChallengeParameters.Descriptor,
/*
* Revocation processing:
*/
AttachNonStandardRevocationClientAssertionClaims.Descriptor,
AttachRevocationRequestNonStandardClientCredentials.Descriptor,
..Authentication.DefaultHandlers,
..Device.DefaultHandlers,
..Discovery.DefaultHandlers,
@ -371,8 +377,8 @@ public static partial class OpenIddictClientWebIntegrationHandlers
}
/// <summary>
/// Contains the logic responsible for amending the client
/// assertion methods for the providers that require it.
/// Contains the logic responsible for adding non-standard claims to the client
/// assertions used for the token endpoint for the providers that require it.
/// </summary>
public sealed class AttachNonStandardClientAssertionClaims : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
@ -491,8 +497,8 @@ public static partial class OpenIddictClientWebIntegrationHandlers
Debug.Assert(context.TokenRequest is not null, SR.GetResourceString(SR.ID4008));
// Apple implements a non-standard client authentication method for the token endpoint
// that is inspired by the standard private_key_jwt method but doesn't use the standard
// Apple implements a non-standard client authentication method for its endpoints that
// is inspired by the standard private_key_jwt method but doesn't use the standard
// client_assertion/client_assertion_type parameters. Instead, the client assertion
// must be sent as a "dynamic" client secret using client_secret_post. Since the logic
// is the same as private_key_jwt, the configuration is amended to assume Apple supports
@ -1536,4 +1542,94 @@ public static partial class OpenIddictClientWebIntegrationHandlers
return default;
}
}
/// <summary>
/// Contains the logic responsible for adding non-standard claims to the client
/// assertions used for the revocation endpoint for the providers that require it.
/// </summary>
public sealed class AttachNonStandardRevocationClientAssertionClaims : IOpenIddictClientHandler<ProcessRevocationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRevocationContext>()
.AddFilter<RequireClientAssertionGenerated>()
.UseSingletonHandler<AttachNonStandardRevocationClientAssertionClaims>()
.SetOrder(PrepareRevocationClientAssertionPrincipal.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessRevocationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.ClientAssertionPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
// For client assertions to be considered valid by the Apple ID authentication service,
// the team identifier associated with the developer account MUST be used as the issuer
// and the static "https://appleid.apple.com" URL MUST be used as the token audience.
//
// For more information about the custom client authentication method implemented by Apple,
// see https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens.
if (context.Registration.ProviderType is ProviderTypes.Apple)
{
var settings = context.Registration.GetAppleSettings();
context.ClientAssertionPrincipal.SetClaim(Claims.Private.Issuer, settings.TeamId);
context.ClientAssertionPrincipal.SetAudiences("https://appleid.apple.com");
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for attaching custom client credentials
/// parameters to the revocation request for the providers that require it.
/// </summary>
public sealed class AttachRevocationRequestNonStandardClientCredentials : IOpenIddictClientHandler<ProcessRevocationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRevocationContext>()
.AddFilter<RequireRevocationRequest>()
.UseSingletonHandler<AttachRevocationRequestNonStandardClientCredentials>()
.SetOrder(AttachRevocationRequestClientCredentials.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessRevocationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.RevocationRequest is not null, SR.GetResourceString(SR.ID4008));
// Apple implements a non-standard client authentication method for its endpoints that
// is inspired by the standard private_key_jwt method but doesn't use the standard
// client_assertion/client_assertion_type parameters. Instead, the client assertion
// must be sent as a "dynamic" client secret using client_secret_post. Since the logic
// is the same as private_key_jwt, the configuration is amended to assume Apple supports
// private_key_jwt and an event handler is responsible for populating the client_secret
// parameter using the client assertion once it has been generated by OpenIddict.
if (context.Registration.ProviderType is ProviderTypes.Apple)
{
context.RevocationRequest.ClientSecret = context.RevocationRequest.ClientAssertion;
context.RevocationRequest.ClientAssertion = null;
context.RevocationRequest.ClientAssertionType = null;
}
return default;
}
}
}

15
src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs

@ -116,7 +116,9 @@ public static class OpenIddictClientHandlerFilters
/// <summary>
/// Represents a filter that excludes the associated handlers if no client assertion is generated.
/// </summary>
public sealed class RequireClientAssertionGenerated : IOpenIddictClientHandlerFilter<ProcessAuthenticationContext>
public sealed class RequireClientAssertionGenerated :
IOpenIddictClientHandlerFilter<ProcessAuthenticationContext>,
IOpenIddictClientHandlerFilter<ProcessRevocationContext>
{
/// <inheritdoc/>
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context)
@ -128,6 +130,17 @@ public static class OpenIddictClientHandlerFilters
return new(context.GenerateClientAssertion);
}
/// <inheritdoc/>
ValueTask<bool> IOpenIddictClientHandlerFilter<ProcessRevocationContext>.IsActiveAsync(ProcessRevocationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(context.GenerateClientAssertion);
}
}
/// <summary>

Loading…
Cancel
Save