Fixed bugs of external login in .Net Core website behind the reverse proxy

Recently, I was working on the memorization module for youzack and had some problems with the external logins.The website provides QQ login, Microsoft account and other ways, in the development environment, there is no problem, but when deployed to the production environment, these external login functions work abnormal. QQ login prompt “redirect uri is illegal”, Microsoft login prompt “invalid_request:The provided value for The input parameter ‘redirect_uri’ is not valid. The expected value is a URI which matches a redirect URI registered for this client application”.I found that the url in the redirect_uri parameter (which represents the callback URL that returned to our site after successful external login) was beginning with http://, not our site’s https://, but the callback URL registered in these external login platforms was beginning with https://, which caused the redirect_uri validation inconsistency.

Because our website has used SLB, which is load balancing and reverse proxy server.We configured the SSL certificate on the SLB, and to improve performance, the SLB uses HTTP communication to our Web server.When users visit our website, they are actually accessing the SLB server, and the SLB server forwards the request to our Web server, so the Web server thinks the web request is an HTTP request from the SLB server, so the scheme of the request recognized by the application is HTTP instead of HTTPS.The solution to this problem is very simple, the.net Core provides a good support, as long as on the SLB reverse proxy configuration to a Web server forwarding X-Forwarded-Proto (original request scheme), Web server can read X-Forwarded-Proto ,so it can know the original scheme. We just need add following codes in the Startup.cs:

ForwardedHeadersOptions options = new ForwardedHeadersOptions();

options.ForwardedHeaders =

ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;

options.KnownNetworks.Clear();

options.KnownProxies.Clear();

app.UseForwardedHeaders(options);

ForwardedHeaders middleware will automatically read X-Forwarded-Forand X-Forwarded- Proto from the request, and assign them to HttpContext.Connection. RemoteIPAddress and HttpContext. Request.Scheme.

Even if you’re using a reverse proxy server that does not support the forwarding X-Forwarded-Proto, you can write a custom middleware to change the scheme manually, the code is as follows:

app.Use((context, next) =>

{

context.Request.Scheme = “https”;

context.Request.IsHttps = true;

return next();

});

Once the code is deployed, there is no problem when redirecting to an external login site. Unfortunately, I found a new problem:

There is no problem with QQ login on PC, but QQ login on mobile will report an error when the login completes and redirect to the callback page:signin-qq.

The same error happened on Microsoft login. The error message is: AADSTS50011: The reply URL specified in The request does not match The reply URLs configured for The application.

This bothered me for a long time, as I watched the redirect_uri address being passed from browser to browser, and it was already HTTPS.It suddenly occurred to me that in OAuth, sometimes, after our website gets the code parameter returned from the external website, our website server will take the code directly in the backend server(not browser-side redirection) and the server will request the token from the external website server. Will there be any problems here? I want to check the request message of OAuth, but no matter how to adjust logging configuration, I cannot see the request logs. So I decided to intercept the request messages directly through the code instead.Through the code. the code is as follows:

public class LoggingHttpHandler : HttpClientHandler

{

private readonly ILogger logger;

public LoggingHttpHandler(ILogger<LoggingHttpHandler> logger)

{

this.logger = logger;

}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

{

var uri = request.RequestUri;

StringBuilder sb = new StringBuilder();

sb.AppendLine($”uri={uri}”);

var headers = string.Join(“\r\n”, request.Headers.Select(h=>h.Key+”=”+string.Join(“,”, h.Value)));

sb.AppendLine($”headers={headers}”);

if (request.Method==HttpMethod.Post)

{

string content = await request.Content.ReadAsStringAsync();

sb.AppendLine($”content={content}”);

}

logger.LogDebug(sb.ToString());

return await base.SendAsync(request, cancellationToken);

}

}

The code above logs the requested URL, header,body, and so on.

Then make it work with the following code:

.AddMicrosoftAccount(opt =>

{

Configuration.GetSection(“Authentication:Microsoft”).Bind(opt);

using(var sp = services.BuildServiceProvider())

{

var logger = sp.GetRequiredService<ILogger<LoggingHttpHandler>>();

opt.BackchannelHttpHandler = new LoggingHttpHandler(logger);

}

}

)

So OAuth sends the Http request through our LoggingHttpHandler.

Run the code, look at the log, and found the following request body:

client_id=32e66666-fdb8–41ff-ac12-cb4aceabcde&redirect_uri=http%3A%2F%2Fbdc.youzack.com%2Fsignin-microsoft&client_secret=

It is noticable that the value of the redirect_uri is http://, not https://.

It’s strange that the scheme we get in our website is already HTTPS, and the redirect_uri in the request that redirects to the external website is also HTTPS. Why does the request in the /signin-microsoft callback still get HTTP?

Writing an Action and printing scheme is also the correct HTTPS.

Does the UseForwardedHeaders sometimes work and sometimes it doesn’t? Microsoft wouldn’t have such a low-level Bug, would it?

The only difference between the normal web page access and /signin-microsoft callback request is that /signin-microsoft is the request address processed by Authentication middleware. Does the UseForwardedHeaders middleware request not work when requesting /signin-microsoft?

Looking through the Startup code, I realized that I made a stupid mistake by writing UseForwardedHeaders after UseAuthentication. As we know, the middleware in.net Core is executed from front to back, and the previous middleware can interrupt execution so that the subsequent middleware will not be executed. /signin-Microsoft is processed by UseAuthentication middleware, ButI put UseForwardedHeaders behind it, as a result, codes in /signin-Microsoft cannot read the real scheme:HTTPS. After adjusting the order everything is done!

When registering a service in ConfigureServices, you generally do not need to pay attention to the order of the services, but when registering the middleware in Configure, you must pay attention to the order of the middlewares.

After studying this, I also found a bonus.My website is going to provide the external login function of Facebook, Google for the convenience of the users out of China.But my server is in China.As we know, the computers in China cannot access the servers of Facebook and Google, so the step of “trade code for token” will fail.Since we can customize the Backchannel and BackchannelHttpHandler of OAuth, we can enable the proxy setting for their Backchannel and BackchannelHttpHandler in Facebook and Google’s OAuth configuration, and forward the request to a proxy server that can access overseas servers, so that the problem can be solved perfectly.

A happy developer