My app runs on Google Compute Engine. Nginx used as a proxy server. Nginx was configured to use SSL. Below is the content of /etc/nginx/sites-available/default:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name mywebapp.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
include snippets/ssl-mywebapp.com.conf;
include snippets/ssl-params.conf;
root /home/me/MyWebApp/wwwroot;
location /.well-known/ {
}
location / {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
In Startup.cs I have:
app.UseGoogleAuthentication(new GoogleOptions()
{
ClientId = Configuration["Authentication:Google:ClientId"],
ClientSecret = Configuration["Authentication:Google:ClientSecret"],
});
Now in Google Cloud Platform I need to specify Authorized redirect URIs. If I enter the following, my web app works as expected:
http://mywebapp.com/signin-google
But, it won't work if https
is used; browser displays the following error:
The redirect URI in the request, http://mywebapp.com/signin-google, does
not match the ones authorized for the OAuth client.
In this case, is it safe to use http as authorized redirect uri? What configuration do I need if I want it to be https?
In ASP.NET Core, authentication is handled by the authentication service, IAuthenticationService, which is used by authentication middleware. The authentication service uses registered authentication handlers to complete authentication-related actions.
This happens because your application which is running behind a reverse proxy server doesn't have any idea that originally request came over HTTPS.
SSL/TLS Termination Proxy
The configuration of the reverse proxy described in the question is called SSL/TLS Termination reverse proxy. That means that secure traffic is established between a client and a proxy server. The proxy server decrypts a request and then forwards it to an application over HTTP protocol.
The issue with this configuration is that an application behind it is not aware that client sent request over HTTPS. So when it comes to redirect to itself it uses HttpContext.Request.Scheme
, HttpContext.Request.Host
and HttpContext.Request.Port
to build a valid URL for redirect.
X-Forwarded-* HTTP Headers
This is where X-Forwarded-*
headers come into play. To let the application know that request is originally coming through a proxy server over HTTPS we have to configure the proxy server to set X-Forwarded-For
and X-Forwarded-Proto
HTTP headers.
location / {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
OK, now if we get back to ASP.NET Core application and take a look at incoming HTTP request we will see both X-Forwarded-*
headers set, however a redirect URL still uses HTTP scheme.
Forwarded Headers Middleware
Basically this middleware overrides HttpContext.Request.Scheme
and HttpContext.Connection.RemoteIpAddress
to values which were provided by X-Forwarded-Proto
and X-Forwarded-For
headers appropriately. To make it happen let's add it to pipeline by adding the following line somewhere in the beginning of the Startup.Configure()
method.
var forwardedHeadersOptions = new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,
RequireHeaderSymmetry = false
};
forwardedHeadersOptions.KnownNetworks.Clear();
forwardedHeadersOptions.KnownProxies.Clear();
app.UseForwardedHeaders(forwardedHeadersOptions);
This should eventually make your application construct valid URLs with HTTPS scheme.
My Story
The code above looks different to what Microsoft suggests. If we take a look in documentation their code looks a bit shorter:
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
However this didn't work for me. Also according to the comments under this issue I'm not alone.
I have a nginx set up as reverse proxy for ASP.NET Core application running in Docker container. It became more complicated after I put everything behind Amazon Load Balancer (ELB).
I followed advice from the documentation first, but it didn't work for me. I have got the following warning in my app:
Parameter count mismatch between X-Forwarded-For and X-Forwarded-Proto
Then I looked at my X-Forwarded-*
headers and realized that they had different length. X-Forwarded-For
header was containing 2 records (comma separated IP addresses), while X-Forwarded-Proto
only one record https
. This is how I came up to setting the property RequireHeaderSymmetry
to false
.
Well, I got rid of 'Parameter count...' warning message, but immediately after that I faced another odd debug message:
Unknown proxy: 172.17.0.6:44624
After looking into the source code of ForwardedHeadersMiddleware I have finally figured out that I have to either clean up both KnownNetworks
and KnownProxies
collections of the ForwardedHeadersOptions
or add my docker network 172.17.0.1/16
to the list of known networks. Right after that I have finally got it working.
PS: For those who sets up a SSL/TLS termination on load balancer (e.g. Amazon Load Balancer or ELB) DON'T set header X-Forwarded-Proto
in nginx configuration. This will override correct https
value which came from load balance to the http
scheme and redirect url will be wrong. I have not found yet how to just append scheme used in nginx to the header instead of overriding it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With