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
21 changes: 21 additions & 0 deletions BasicMiddleware.sln
Original file line number Diff line number Diff line change
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/"
}
}
}
71 changes: 71 additions & 0 deletions samples/HttpsPolicySample/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status301MovedPermanently;
options.TlsPort = 5001;
});

services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromDays(30);
options.Preload = true;
options.IncludeSubDomains = true;
});
}

public void Configure(IApplicationBuilder app, IHostingEnvironment environment)
{
if (!environment.IsDevelopment())
{
app.UseHsts();
}
app.UseHttpsRedirection();

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.
30 changes: 30 additions & 0 deletions src/Microsoft.AspNetCore.HttpsPolicy/HstsBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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.Extensions.Options;

namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Extension methods for the HSTS middleware.
/// </summary>
public static class HstsBuilderExtensions
{
/// <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>();
}
}
}
68 changes: 68 additions & 0 deletions src/Microsoft.AspNetCore.HttpsPolicy/HstsMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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.Globalization;
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 _strictTransportSecurityValue;

/// <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 maxAge = Convert.ToInt64(Math.Floor(hstsOptions.MaxAge.TotalSeconds))
.ToString(CultureInfo.InvariantCulture);
var includeSubdomains = hstsOptions.IncludeSubDomains ? IncludeSubDomains : StringSegment.Empty;
var preload = hstsOptions.Preload ? Preload : StringSegment.Empty;
_strictTransportSecurityValue = new StringValues($"max-age={maxAge}{includeSubdomains}{preload}");
}

/// <summary>
/// Invoke the middleware.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
/// <returns></returns>
public Task Invoke(HttpContext context)
{
if (context.Request.IsHttps)
{
context.Response.Headers[HeaderNames.StrictTransportSecurity] = _strictTransportSecurityValue;
}

return _next(context);
}
}
}
39 changes: 39 additions & 0 deletions src/Microsoft.AspNetCore.HttpsPolicy/HstsOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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;

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 30 days.
/// See: https://tools.ietf.org/html/rfc6797#section-6.1.1
/// </remarks>
public TimeSpan MaxAge { get; set; } = TimeSpan.FromDays(30);

/// <summary>
/// Enables 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; }
}
}
36 changes: 36 additions & 0 deletions src/Microsoft.AspNetCore.HttpsPolicy/HstsServicesExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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;

namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Extension methods for the HSTS middleware.
/// </summary>
public static class HstsServicesExtensions
{
/// <summary>
/// Adds HSTS services.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
/// <param name="configureOptions">A delegate to configure the <see cref="HstsOptions"/>.</param>
/// <returns></returns>
public static IServiceCollection AddHsts(this IServiceCollection services, Action<HstsOptions> configureOptions)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (configureOptions == null)
{
throw new ArgumentNullException(nameof(configureOptions));
}

services.Configure(configureOptions);
return services;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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.AspNetCore.Rewrite;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Builder
{
/// <summary>
/// Extension methods for the HttpsRedirection middleware.
/// </summary>
public static class HttpsPolicyBuilderExtensions
{
/// <summary>
/// Adds middleware for redirecting HTTP Requests to HTTPS.
/// </summary>
/// <param name="app">The <see cref="IApplicationBuilder"/> instance this method extends.</param>
/// <returns>The <see cref="IApplicationBuilder"/> for HttpsRedirection.</returns>
/// <remarks>
/// HTTPS Enforcement interanlly uses the UrlRewrite middleware to redirect HTTP requests to HTTPS.
/// </remarks>
public static IApplicationBuilder UseHttpsRedirection(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}

var options = app.ApplicationServices.GetRequiredService<IOptions<HttpsRedirectionOptions>>().Value;

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

app.UseRewriter(rewriteOptions);

return app;
}
}
}
Loading