Skip to content
This repository has been archived by the owner on Dec 20, 2018. It is now read-only.

Adds Https Redirection and Hsts Middlewares #264

Merged
merged 14 commits into from
Oct 13, 2017
23 changes: 22 additions & 1 deletion BasicMiddleware.sln
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26817.0
VisualStudioVersion = 15.0.26815.3
MinimumVisualStudioVersion = 15.0.26730.03
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpOverrides", "src\Microsoft.AspNetCore.HttpOverrides\Microsoft.AspNetCore.HttpOverrides.csproj", "{517308C3-B477-4B01-B461-CAB9C10B6928}"
EndProject
Expand Down Expand Up @@ -57,6 +57,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
version.xml = version.xml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpsPolicy", "src\Microsoft.AspNetCore.HttpsPolicy\Microsoft.AspNetCore.HttpsPolicy.csproj", "{4D39C29B-4EC8-497C-B411-922DA494D71B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpsPolicySample", "samples\HttpsPolicySample\HttpsPolicySample.csproj", "{AC424AEE-4883-49C6-945F-2FC916B8CA1C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.HttpsPolicy.Tests", "test\Microsoft.AspNetCore.HttpsEnforcement.Tests\Microsoft.AspNetCore.HttpsPolicy.Tests.csproj", "{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -111,6 +117,18 @@ Global
{B2A3CE38-51B2-4486-982C-98C380AF140E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2A3CE38-51B2-4486-982C-98C380AF140E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2A3CE38-51B2-4486-982C-98C380AF140E}.Release|Any CPU.Build.0 = Release|Any CPU
{4D39C29B-4EC8-497C-B411-922DA494D71B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4D39C29B-4EC8-497C-B411-922DA494D71B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D39C29B-4EC8-497C-B411-922DA494D71B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D39C29B-4EC8-497C-B411-922DA494D71B}.Release|Any CPU.Build.0 = Release|Any CPU
{AC424AEE-4883-49C6-945F-2FC916B8CA1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AC424AEE-4883-49C6-945F-2FC916B8CA1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AC424AEE-4883-49C6-945F-2FC916B8CA1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AC424AEE-4883-49C6-945F-2FC916B8CA1C}.Release|Any CPU.Build.0 = Release|Any CPU
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -128,6 +146,9 @@ Global
{45308A9D-F4C6-46A8-A24F-E73D995CC223} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
{3360A5D1-70C0-49EE-9051-04A6A6B836DC} = {8437B0F3-3894-4828-A945-A9187F37631D}
{B2A3CE38-51B2-4486-982C-98C380AF140E} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
{4D39C29B-4EC8-497C-B411-922DA494D71B} = {A5076D28-FA7E-4606-9410-FEDD0D603527}
{AC424AEE-4883-49C6-945F-2FC916B8CA1C} = {9587FE9F-5A17-42C4-8021-E87F59CECB98}
{1C67B0F1-6E70-449E-A2F1-98B9D5C576CE} = {8437B0F3-3894-4828-A945-A9187F37631D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4518E9CE-3680-4E05-9259-B64EA7807158}
Expand Down
17 changes: 17 additions & 0 deletions samples/HttpsPolicySample/HttpsPolicySample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netcoreapp2.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.HttpsPolicy\Microsoft.AspNetCore.HttpsPolicy.csproj" />
</ItemGroup>

</Project>
27 changes: 27 additions & 0 deletions samples/HttpsPolicySample/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:31894/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"HttpsSample": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:31895/"
}
}
}
61 changes: 61 additions & 0 deletions samples/HttpsPolicySample/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.DependencyInjection;

namespace HttpsSample
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.Configure<HttpsPolicyOptions>(options => {
options.SetHsts = true;
options.StatusCode = 302;
options.TlsPort = 5001;
});
}


public void Configure(IApplicationBuilder app)
{
app.UseHttpsPolicy();

app.Run(async context =>
{
await context.Response.WriteAsync("Hello world!");
});
}

// Entry point for the application.
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel(
options =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 5001), listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
});
options.Listen(new IPEndPoint(IPAddress.Loopback, 5000), listenOptions =>
{
});
})
.UseContentRoot(Directory.GetCurrentDirectory()) // for the cert file
.UseStartup<Startup>()
.Build();

host.Run();
}
}
}
Binary file added samples/HttpsPolicySample/testCert.pfx
Binary file not shown.
29 changes: 29 additions & 0 deletions src/Microsoft.AspNetCore.HttpsPolicy/HstsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.HttpsPolicy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder namespace

{
/// <summary>
/// Extension methods for the HSTS middleware.
/// </summary>
public static class HstsExtensions
{
/// <summary>
/// Adds middleware for using HSTS, which adds the Strict-Transport-Security header.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
public static IApplicationBuilder UseHsts(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}

return app.UseMiddleware<HstsMiddleware>();
}
}
}
66 changes: 66 additions & 0 deletions src/Microsoft.AspNetCore.HttpsPolicy/HstsMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;

namespace Microsoft.AspNetCore.HttpsPolicy
{
/// <summary>
/// Enables HTTP Strict Transport Security (HSTS)
/// See https://tools.ietf.org/html/rfc6797.
/// </summary>
public class HstsMiddleware
{
private const string IncludeSubDomains = "; includeSubDomains";
private const string Preload = "; preload";

private readonly RequestDelegate _next;
private readonly StringValues _nameValueHeaderValue;

/// <summary>
/// Initialize the HSTS middleware.
/// </summary>
/// <param name="next"></param>
/// <param name="options"></param>
public HstsMiddleware(RequestDelegate next, IOptions<HstsOptions> options)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}

if (options == null)
{
throw new ArgumentNullException(nameof(options));
}

_next = next;

var hstsOptions = options.Value;
var includeSubdomains = hstsOptions.IncludeSubDomains ? IncludeSubDomains : StringSegment.Empty;
var preload = hstsOptions.Preload ? Preload : StringSegment.Empty;

_nameValueHeaderValue = new StringValues($"max-age={hstsOptions.MaxAge}{includeSubdomains}{preload}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_stsValue

}

/// <summary>
/// Invoke the middleware.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
/// <returns></returns>
public async Task Invoke(HttpContext context)
{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've got a blank line here.

if (context.Request.IsHttps)
{
context.Response.Headers[HeaderNames.StrictTransportSecurity] = _nameValueHeaderValue;
}
await _next(context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no async/await required

}
}
}
37 changes: 37 additions & 0 deletions src/Microsoft.AspNetCore.HttpsPolicy/HstsOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.AspNetCore.HttpsPolicy
{
/// <summary>
/// Options for the Hsts Middleware
/// </summary>
public class HstsOptions
{
/// <summary>
/// Sets the max-age parameter of the Strict-Transport-Security header.
/// </summary>
/// <remarks>
/// Max-age is required; defaults to 0.
/// See: https://tools.ietf.org/html/rfc6797#section-6.1.1
/// </remarks>
public int MaxAge { get; set; }

/// <summary>
/// Sets the includeSubDomain parameter of the Strict-Transport-Security header.
/// </summary>
/// <remarks>
/// See: https://tools.ietf.org/html/rfc6797#section-6.1.2
/// </remarks>
public bool IncludeSubDomains { get; set; }

/// <summary>
/// Sets the preload parameter of the Strict-Transport-Security header.
/// </summary>
/// <remarks>
/// Preload is not part of the RFC specification, but is supported by web browsers
/// to preload HSTS sites on fresh install. See https://hstspreload.org/.
/// </remarks>
public bool Preload { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Extension methods for the HttpsPolicy middleware.
/// </summary>
public static class HttpsPolicyBuilderExtensions
{
/// <summary>
/// Adds middleware for enforcing HTTPS for all HTTP Requests, including redirecting HTTP to HTTPS
/// and adding the HTTP Strict-Transport-Header.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
/// <returns>The <see cref="IApplicationBuilder"/> with HttpsPolicy.</returns>
/// <remarks>
/// HTTPS Enforcement interanlly uses the UrlRewrite middleware to redirect HTTP requests to HTTPS
/// </remarks>
public static IApplicationBuilder UseHttpsPolicy(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
var options = app.ApplicationServices.GetRequiredService<IOptions<HttpsPolicyOptions>>().Value;

if (options.SetHsts)
{
app.UseHsts();
}

var rewriteOptions = new RewriteOptions();
rewriteOptions.AddRedirectToHttps(
options.StatusCode,
options.TlsPort);

app.UseRewriter(rewriteOptions);

return app;
}

public static IApplicationBuilder UseHttpsPolicy(this IApplicationBuilder app, bool enableHsts)
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why empty? Do we need a comment explaining it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry redesign for this. I need to do a bit of work still!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, OK, sorry didn't notice the [WIP] in the title.

}
}
}
28 changes: 28 additions & 0 deletions src/Microsoft.AspNetCore.HttpsPolicy/HttpsPolicyOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Http;

namespace Microsoft.AspNetCore.HttpsPolicy
{
/// <summary>
/// Options for the HttpsPolicyMiddleware middleware
/// </summary>
public class HttpsPolicyOptions
{
/// <summary>
/// Whether to use HTTP Strict-Transport-Security (HSTS) on all HTTPS requests.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

responses

/// </summary>
public bool SetHsts { get; set; }

/// <summary>
/// The status code to be used for Url Redirection
/// </summary>
public int StatusCode { get; set; } = StatusCodes.Status301MovedPermanently; // TODO throw ArgumentOutOfRangeException from UrlRewrite redirect rule?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's ok as it is. I don't think we need to over protect against a non redirect status code. I would change the property name and the doc comment to reflect that is a redirect status code. Property would be RedirectStatusCode. What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes RedirectStatusCode.


/// <summary>
/// The TLS port to be added to the redirected URL.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default port (443) will be used if not provided.

/// </summary>
public int? TlsPort { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Extension methods for the HttpsPolicy middleware.
/// </summary>
public static class HttpsPolicyServiceCollectionExtensions
{
}
}
Loading