Browse Source

Introduce a new DisableTransportSecurityRequirement() option in the OpenIddict client ASP.NET Core and OWIN hosts

pull/1604/head
Kévin Chalet 3 years ago
parent
commit
73e6efe4ac
  1. 3
      sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs
  2. 6
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  3. 7
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreBuilder.cs
  4. 1
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreExtensions.cs
  5. 21
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlerFilters.cs
  6. 134
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs
  7. 7
      src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreOptions.cs
  8. 7
      src/OpenIddict.Client.Owin/OpenIddictClientOwinBuilder.cs
  9. 1
      src/OpenIddict.Client.Owin/OpenIddictClientOwinExtensions.cs
  10. 21
      src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlerFilters.cs
  11. 134
      src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs
  12. 7
      src/OpenIddict.Client.Owin/OpenIddictClientOwinOptions.cs
  13. 1
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs
  14. 1
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs

3
sandbox/OpenIddict.Sandbox.AspNetCore.Server/Startup.cs

@ -142,8 +142,7 @@ public class Startup
.EnableLogoutEndpointPassthrough()
.EnableTokenEndpointPassthrough()
.EnableUserinfoEndpointPassthrough()
.EnableVerificationEndpointPassthrough()
.DisableTransportSecurityRequirement(); // During development, you can disable the HTTPS requirement.
.EnableVerificationEndpointPassthrough();
// Note: if you don't want to specify a client_id when sending
// a token or revocation request, uncomment the following line:

6
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -1384,6 +1384,12 @@ Consider registering a certificate using 'services.AddOpenIddict().AddClient().A
<data name="ID0363" xml:space="preserve">
<value>The specified grant type ({0}) is not listed as a supported grant type in the server configuration. If the error persists, ensure the supported grant types listed in the authorization server configuration are appropriate.</value>
</data>
<data name="ID0364" xml:space="preserve">
<value>Challenge operations cannot be triggered from non-HTTPS endpoints when the transport security requirement is enforced. While not recommended (as HTTPS is required for SameSite=None cookies to work correctly in most browsers), the transport security requirement can be disabled in the ASP.NET Core or OWIN options via 'services.AddOpenIddict().AddClient().UseAspNetCore().DisableTransportSecurityRequirement()' or 'services.AddOpenIddict().AddClient().UseOwin().DisableTransportSecurityRequirement()'.</value>
</data>
<data name="ID0365" xml:space="preserve">
<value>Sign-out operations cannot be triggered from non-HTTPS endpoints when the transport security requirement is enforced. While not recommended (as HTTPS is required for SameSite=None cookies to work correctly in most browsers), the transport security requirement can be disabled in the ASP.NET Core or OWIN options via 'services.AddOpenIddict().AddClient().UseAspNetCore().DisableTransportSecurityRequirement()' or 'services.AddOpenIddict().AddClient().UseOwin().DisableTransportSecurityRequirement()'.</value>
</data>
<data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value>
</data>

7
src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreBuilder.cs

@ -47,6 +47,13 @@ public sealed class OpenIddictClientAspNetCoreBuilder
return this;
}
/// <summary>
/// Disables the transport security requirement (HTTPS).
/// </summary>
/// <returns>The <see cref="OpenIddictClientAspNetCoreBuilder"/> instance.</returns>
public OpenIddictClientAspNetCoreBuilder DisableTransportSecurityRequirement()
=> Configure(options => options.DisableTransportSecurityRequirement = true);
/// <summary>
/// Enables the pass-through mode for the OpenID Connect post-logout redirection endpoint.
/// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict.

1
src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreExtensions.cs

@ -43,6 +43,7 @@ public static class OpenIddictClientAspNetCoreExtensions
builder.Services.TryAddSingleton<RequirePostLogoutRedirectionEndpointPassthroughEnabled>();
builder.Services.TryAddSingleton<RequireRedirectionEndpointPassthroughEnabled>();
builder.Services.TryAddSingleton<RequireStatusCodePagesIntegrationEnabled>();
builder.Services.TryAddSingleton<RequireTransportSecurityRequirementEnabled>();
// Register the option initializer used by the OpenIddict ASP.NET Core client integration services.
// Note: TryAddEnumerable() is used here to ensure the initializers are only registered once.

21
src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlerFilters.cs

@ -117,4 +117,25 @@ public static class OpenIddictClientAspNetCoreHandlerFilters
return new(_options.CurrentValue.EnableStatusCodePagesIntegration);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if the HTTPS requirement was disabled.
/// </summary>
public sealed class RequireTransportSecurityRequirementEnabled : IOpenIddictClientHandlerFilter<BaseContext>
{
private readonly IOptionsMonitor<OpenIddictClientAspNetCoreOptions> _options;
public RequireTransportSecurityRequirementEnabled(IOptionsMonitor<OpenIddictClientAspNetCoreOptions> options)
=> _options = options ?? throw new ArgumentNullException(nameof(options));
public ValueTask<bool> IsActiveAsync(BaseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(!_options.CurrentValue.DisableTransportSecurityRequirement);
}
}
}

134
src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreHandlers.cs

@ -34,6 +34,7 @@ public static partial class OpenIddictClientAspNetCoreHandlers
* Top-level request processing:
*/
InferEndpointType.Descriptor,
ValidateTransportSecurityRequirement.Descriptor,
/*
* Authentication processing:
@ -45,12 +46,14 @@ public static partial class OpenIddictClientAspNetCoreHandlers
* Challenge processing:
*/
ResolveHostChallengeProperties.Descriptor,
ValidateTransportSecurityRequirementForChallenge.Descriptor,
GenerateLoginCorrelationCookie.Descriptor,
/*
* Sign-out processing:
*/
ResolveHostSignOutProperties.Descriptor,
ValidateTransportSecurityRequirementForSignOut.Descriptor,
GenerateLogoutCorrelationCookie.Descriptor)
.AddRange(Authentication.DefaultHandlers)
.AddRange(Session.DefaultHandlers);
@ -144,6 +147,57 @@ public static partial class OpenIddictClientAspNetCoreHandlers
}
}
/// <summary>
/// Contains the logic responsible for rejecting OpenID Connect requests that don't use transport security.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary>
public sealed class ValidateTransportSecurityRequirement : IOpenIddictClientHandler<ProcessRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireHttpRequest>()
.AddFilter<RequireTransportSecurityRequirementEnabled>()
.UseSingletonHandler<ValidateTransportSecurityRequirement>()
.SetOrder(InferEndpointType.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var request = context.Transaction.GetHttpRequest() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0114));
// Don't require that the host be present if the request is not handled by OpenIddict.
if (context.EndpointType is OpenIddictClientEndpointType.Unknown)
{
return default;
}
if (!request.IsHttps)
{
context.Reject(
error: Errors.InvalidRequest,
description: SR.GetResourceString(SR.ID2083),
uri: SR.FormatID8000(SR.ID2083));
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for extracting OpenID Connect requests from GET or POST HTTP requests.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
@ -551,6 +605,46 @@ public static partial class OpenIddictClientAspNetCoreHandlers
}
}
/// <summary>
/// Contains the logic responsible for preventing challenge operations from being triggered from non-HTTPS endpoints.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary>
public sealed class ValidateTransportSecurityRequirementForChallenge : IOpenIddictClientHandler<ProcessChallengeContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireHttpRequest>()
.AddFilter<RequireTransportSecurityRequirementEnabled>()
.UseSingletonHandler<ValidateTransportSecurityRequirementForChallenge>()
.SetOrder(ValidateChallengeDemand.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var request = context.Transaction.GetHttpRequest() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0114));
if (!request.IsHttps)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0364));
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for creating a correlation cookie that serves as a
/// protection against state token injection, forged requests and session fixation attacks.
@ -744,6 +838,46 @@ public static partial class OpenIddictClientAspNetCoreHandlers
}
}
/// <summary>
/// Contains the logic responsible for preventing sign-out operations from being triggered from non-HTTPS endpoints.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary>
public sealed class ValidateTransportSecurityRequirementForSignOut : IOpenIddictClientHandler<ProcessSignOutContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
.AddFilter<RequireHttpRequest>()
.AddFilter<RequireTransportSecurityRequirementEnabled>()
.UseSingletonHandler<ValidateTransportSecurityRequirementForSignOut>()
.SetOrder(ValidateSignOutDemand.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignOutContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var request = context.Transaction.GetHttpRequest() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0114));
if (!request.IsHttps)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0365));
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for creating a correlation cookie that serves as a
/// protection against state token injection, forged requests and denial of service attacks.

7
src/OpenIddict.Client.AspNetCore/OpenIddictClientAspNetCoreOptions.cs

@ -13,6 +13,13 @@ namespace OpenIddict.Client.AspNetCore;
/// </summary>
public sealed class OpenIddictClientAspNetCoreOptions : AuthenticationSchemeOptions
{
/// <summary>
/// Gets or sets a boolean indicating whether incoming requests arriving on insecure endpoints should be
/// rejected and whether challenge and sign-out operations can be triggered from non-HTTPS endpoints.
/// By default, this property is set to <see langword="false"/> to help mitigate man-in-the-middle attacks.
/// </summary>
public bool DisableTransportSecurityRequirement { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the pass-through mode is enabled for the post-logout redirection endpoint.
/// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict.

7
src/OpenIddict.Client.Owin/OpenIddictClientOwinBuilder.cs

@ -47,6 +47,13 @@ public sealed class OpenIddictClientOwinBuilder
return this;
}
/// <summary>
/// Disables the transport security requirement (HTTPS).
/// </summary>
/// <returns>The <see cref="OpenIddictClientOwinBuilder"/> instance.</returns>
public OpenIddictClientOwinBuilder DisableTransportSecurityRequirement()
=> Configure(options => options.DisableTransportSecurityRequirement = true);
/// <summary>
/// Enables the pass-through mode for the OpenID Connect post-logout redirection endpoint.
/// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict.

1
src/OpenIddict.Client.Owin/OpenIddictClientOwinExtensions.cs

@ -45,6 +45,7 @@ public static class OpenIddictClientOwinExtensions
builder.Services.TryAddSingleton<RequireOwinRequest>();
builder.Services.TryAddSingleton<RequirePostLogoutRedirectionEndpointPassthroughEnabled>();
builder.Services.TryAddSingleton<RequireRedirectionEndpointPassthroughEnabled>();
builder.Services.TryAddSingleton<RequireTransportSecurityRequirementEnabled>();
// Register the option initializer used by the OpenIddict OWIN client integration services.
// Note: TryAddEnumerable() is used here to ensure the initializers are only registered once.

21
src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlerFilters.cs

@ -96,4 +96,25 @@ public static class OpenIddictClientOwinHandlerFilters
return new(context.Transaction.GetOwinRequest() is not null);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if the HTTPS requirement was disabled.
/// </summary>
public sealed class RequireTransportSecurityRequirementEnabled : IOpenIddictClientHandlerFilter<BaseContext>
{
private readonly IOptionsMonitor<OpenIddictClientOwinOptions> _options;
public RequireTransportSecurityRequirementEnabled(IOptionsMonitor<OpenIddictClientOwinOptions> options)
=> _options = options ?? throw new ArgumentNullException(nameof(options));
public ValueTask<bool> IsActiveAsync(BaseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(!_options.CurrentValue.DisableTransportSecurityRequirement);
}
}
}

134
src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs

@ -30,6 +30,7 @@ public static partial class OpenIddictClientOwinHandlers
* Top-level request processing:
*/
InferEndpointType.Descriptor,
ValidateTransportSecurityRequirement.Descriptor,
/*
* Authentication processing:
@ -41,12 +42,14 @@ public static partial class OpenIddictClientOwinHandlers
* Challenge processing:
*/
ResolveHostChallengeProperties.Descriptor,
ValidateTransportSecurityRequirementForChallenge.Descriptor,
GenerateLoginCorrelationCookie.Descriptor,
/*
* Sign-out processing:
*/
ResolveHostSignOutProperties.Descriptor,
ValidateTransportSecurityRequirementForSignOut.Descriptor,
GenerateLogoutCorrelationCookie.Descriptor)
.AddRange(Authentication.DefaultHandlers)
.AddRange(Session.DefaultHandlers);
@ -143,6 +146,57 @@ public static partial class OpenIddictClientOwinHandlers
}
}
/// <summary>
/// Contains the logic responsible for rejecting OpenID Connect requests that don't use transport security.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public sealed class ValidateTransportSecurityRequirement : IOpenIddictClientHandler<ProcessRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireOwinRequest>()
.AddFilter<RequireTransportSecurityRequirementEnabled>()
.UseSingletonHandler<ValidateTransportSecurityRequirement>()
.SetOrder(InferEndpointType.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to OWIN requests. If The OWIN request cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var request = context.Transaction.GetOwinRequest() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0120));
// Don't require that the host be present if the request is not handled by OpenIddict.
if (context.EndpointType is OpenIddictClientEndpointType.Unknown)
{
return default;
}
if (!request.IsSecure)
{
context.Reject(
error: Errors.InvalidRequest,
description: SR.GetResourceString(SR.ID2083),
uri: SR.FormatID8000(SR.ID2083));
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for extracting OpenID Connect requests from GET or POST HTTP requests.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
@ -580,6 +634,46 @@ public static partial class OpenIddictClientOwinHandlers
}
}
/// <summary>
/// Contains the logic responsible for preventing challenge operations from being triggered from non-HTTPS endpoints.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public sealed class ValidateTransportSecurityRequirementForChallenge : IOpenIddictClientHandler<ProcessChallengeContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireOwinRequest>()
.AddFilter<RequireTransportSecurityRequirementEnabled>()
.UseSingletonHandler<ValidateTransportSecurityRequirementForChallenge>()
.SetOrder(ValidateChallengeDemand.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to OWIN requests. If The OWIN request cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var request = context.Transaction.GetOwinRequest() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0120));
if (!request.IsSecure)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0364));
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for creating a correlation cookie that serves as a
/// protection against state token injection, forged requests and session fixation attacks.
@ -799,6 +893,46 @@ public static partial class OpenIddictClientOwinHandlers
}
}
/// <summary>
/// Contains the logic responsible for preventing sign-out operations from being triggered from non-HTTPS endpoints.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public sealed class ValidateTransportSecurityRequirementForSignOut : IOpenIddictClientHandler<ProcessSignOutContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
.AddFilter<RequireOwinRequest>()
.AddFilter<RequireTransportSecurityRequirementEnabled>()
.UseSingletonHandler<ValidateTransportSecurityRequirementForSignOut>()
.SetOrder(ValidateSignOutDemand.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignOutContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to OWIN requests. If The OWIN request cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var request = context.Transaction.GetOwinRequest() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0120));
if (!request.IsSecure)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0365));
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for creating a correlation cookie that serves as a
/// protection against state token injection, forged requests and denial of service attacks.

7
src/OpenIddict.Client.Owin/OpenIddictClientOwinOptions.cs

@ -20,6 +20,13 @@ public sealed class OpenIddictClientOwinOptions : AuthenticationOptions
: base(OpenIddictClientOwinDefaults.AuthenticationType)
=> AuthenticationMode = AuthenticationMode.Passive;
/// <summary>
/// Gets or sets a boolean indicating whether incoming requests arriving on insecure endpoints should be
/// rejected and whether challenge and sign-out operations can be triggered from non-HTTPS endpoints.
/// By default, this property is set to <see langword="false"/> to help mitigate man-in-the-middle attacks.
/// </summary>
public bool DisableTransportSecurityRequirement { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the pass-through mode is enabled for the post-logout redirection endpoint.
/// When the pass-through mode is used, OpenID Connect requests are initially handled by OpenIddict.

1
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs

@ -262,7 +262,6 @@ public static partial class OpenIddictServerAspNetCoreHandlers
return default;
}
// Reject authorization requests sent without transport security.
if (!request.IsHttps)
{
context.Reject(

1
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs

@ -260,7 +260,6 @@ public static partial class OpenIddictServerOwinHandlers
return default;
}
// Reject authorization requests sent without transport security.
if (!request.IsSecure)
{
context.Reject(

Loading…
Cancel
Save