I am using ASP.NET MVC, and I am using MS built-in template to allow users to login with Facebook and Google, external ID. The external ID does not work on my Production environment (works on Test) and I believe this is because of the Load Balancer... (I am offloading SSL on load balancer and the backend servers use HTTP).

I have seen This Question and I think this is my problem: The login request contains a returnurl and because the backend server is using http, the return url is also http (not https). This is the method that I am talking about in AccountController:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
    // Request a redirect to the external login provider
    return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}

When user tries to login, I can see that redirect_uri is http (not https):

enter image description here

I have checked the question mentioned above and also this one. They both suggest that I should use relative path for redirect url in Startup.Auth, so this is what I have added to my code (ConfigureAuth method) :

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
    {
        OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, long>(
            validateInterval: TimeSpan.FromMinutes(30),
            regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
            getUserIdCallback: id => id.GetUserId<long>()),

        /* THIS IS THE CHANGE THAT I HAVE ADDED */
        OnApplyRedirect = context =>
        {
            /* I have tried adding a breakpoint here, but it is never hit */
            var redirectUri = new Uri(context.RedirectUri, UriKind.Absolute);

            if (redirectUri.Scheme == "http" && redirectUri.Host == context.Request.Uri.Host)
            {
                context.RedirectUri = redirectUri.PathAndQuery;
            }

            context.Response.Redirect(context.RedirectUri);
        }
    }
});

But this change has no effect and the redirect_url remains http. If I put a break point on the change that I have added above, the breakpoint never hits... not sure where things are going wrong?

1 Answers

1
Hooman Bahreini On Best Solutions

I found a solution using this question & this github issue:

Load Balancer would terminate the SSL and communicate with the backend server using http, but it would forward the original protocol (https, in this case) to backend server. So we could use x-forwarded-proto to detect the original protocol in the backend server:

public partial class Startup
{
    public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the db context, user manager and signin manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

        /* I HAVE ADDED THIS CODE */
        app.Use((context, next) =>
        {
            if (context.Request.Headers["x-forwarded-proto"] == "https")
            {
                context.Request.Scheme = "https";
            }
            return next();
        });

        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, long>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
                    getUserIdCallback: id => id.GetUserId<long>())
            }
        });

        // more code...
}