I could not get the automatic redirection from HTTP to HTTPS to work correctly when the app is published to App Engine.
When i access the website through example.com the site got routed to http://www.example.com and show that the connection is unsecured. When i access the website through https://www.example.com the website is then correctly secured with google-managed SSL. However the automatic redirection from HTTP to HTTPS does not occurs.
I also got an error in Log Viewer warning that Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware was throwing Failed to determine the https port for redirect.
I have followed the documentation from MSDN and only get it to work locally, but not when the app is published to App Engine. https://docs.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-2.1&tabs=visual-studio
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory logger)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseStatusCodePages();
app.UseExceptionHandler("/Error");
app.UseHsts(); // This was added by the template
}
app.UseHttpsRedirection(); // This was added by the template
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc();
}
Here is the Program.cs. Basically default from the project template
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.CaptureStartupErrors(true)
.UseStartup<Startup>();
}
The app.yaml used for the deployment
runtime: aspnetcore
env: flexible
automatic_scaling:
min_num_instances: 1
max_num_instances: 20
cpu_utilization:
target_utilization: 0.8
readiness_check:
path: "/readinesscheck"
check_interval_sec: 5
timeout_sec: 4
failure_threshold: 2
success_threshold: 2
app_start_timeout_sec: 300
liveness_check:
path: "/livenesscheck"
check_interval_sec: 30
timeout_sec: 4
failure_threshold: 2
success_threshold: 2
skip_files:
- node_modules/
- wwwroot/src/vendor/
- ^(.*/)?.*\.pdb$
- ^(.*/)?.*\.log$
Which ended up with app being inaccessible (502 Server Error).
services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect;
options.HttpsPort = 443;
});
Also ended up with app being inaccessible (502 Server Error).
env_variables:
ASPNETCORE_HTTPS_PORT: "443"
Also ended up with app being inaccessible (502 Server Error).
WebHost.CreateDefaultBuilder(args)
.UseSetting("https_port", "8080") // also return 502 when port is 443
App is accessible but no automatic HTTP/HTTPS redirection.
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
app.UseForwardedHeaders();
App is accessible but no automatic HTTP/HTTPS redirection. I understand that when runtime in app.yaml is set to aspnetcore. The publish process automatically generated it's own Dockerfile which is used to deploy the app to App Engine.
EXPOSE 443
EXPOSE 8080
Somehow I got this to work after creating my own middleware that look for "X-Forwarded-Proto" header according to this hint on Microsoft and App Engine documentation.
Microsoft: Forwarded Headers Middleware must be enabled for an app to process forwarded headers with UseForwardedHeaders.
App Engine: SSL connections are terminated at the load balancer. Traffic from the load balancer is sent to the instance over an encrypted channel, and then forwarded to the application server over HTTP. The X-Forwarded-Proto header lets you understand if the origin request was HTTP or HTTPs.
Microsoft requires the Middleware to be activated first before the app start to process the forwarded headers
So configure the middleware options in ConfigureServices method
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
and use it in Configure method before anything else
app.UseForwardedHeaders();
Then writing the custom middleware that reads the forwarded headers and redirect to HTTPS including queries.
In Configure method
app.Use(async (context, next) =>
{
if (context.Request.IsHttps || context.Request.Headers["X-Forwarded-Proto"] == Uri.UriSchemeHttps)
{
await next();
}
else
{
string queryString = context.Request.QueryString.HasValue ? context.Request.QueryString.Value : string.Empty;
var https = "https://" + context.Request.Host + context.Request.Path + queryString;
context.Response.Redirect(https);
}
});
In the end the Configure method looks like this
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseForwardedHeaders();
app.Use(async (context, next) =>
{
if (context.Request.IsHttps || context.Request.Headers["X-Forwarded-Proto"] == Uri.UriSchemeHttps)
{
await next();
}
else
{
string queryString = context.Request.QueryString.HasValue ? context.Request.QueryString.Value : string.Empty;
var https = "https://" + context.Request.Host + context.Request.Path + queryString;
context.Response.Redirect(https);
}
});
if (env.IsDevelopment())
{
// code removed for clarity
}
else
{
// code removed for clarity
app.UseHsts();
}
app.UseHttpsRedirection();
// code removed for clarity
app.UseMvc();
}
Now navigating to example.com redirect me directly https://www.example.com
This is a great answer and it works for me. I would just like to add that you should use
context.Response.Redirect(https, true)
instead ofcontext.Response.Redirect(https)
since the former will redirect with HTTP status code 301 instead of 302. And Google recommends using 301 for this purpose: support.google.com/webmasters/answer/6073543Thank you so much for this! I have been battling this. Still kind of annoyed, it sounds like the instructions here docs.microsoft.com/en-us/aspnet/core/host-and-deploy/… should cover this use case without the need for the custom redirect.