How can I implement login / authorization as per the sample with an HTTPS proxy in front of the application?

tl;dr:

CLIENT -----------> GATEWAY (https) -----------> APP (http)

/callback is http:// but being redirected to https:// in order to protect it / funnel it through the gateway; however, there’s an error processing the callback endpoint in the APP (shown below)


I’ve got a Service Fabric application that’s hosting an asp.net core site which is sitting behind an Application Gateway that I am using to provide TLS for the application as a whole. The gateway provides a place to manage TLS certs and forwards requests on to the underlying application / site via plain http.

Within the site itself, I’ve mostly followed the quickstart guide. For reference, here are the relevant portions of ConfigureServices

services.Configure<CookiePolicyOptions>(options =>
{
  options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
  options.OnAppendCookie = cookieContext => CheckSameSite(cookieContext.CookieOptions);
  options.OnDeleteCookie = cookieContext => CheckSameSite(cookieContext.CookieOptions);
});

services.AddAuthentication(options =>
{
  options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
  options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
  options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("Auth0", options =>
{
  options.Authority = $"https://{Settings.Auth.Domain}";
  options.ClientId = Settings.Auth.ClientId;
  options.ClientSecret = Settings.Auth.ClientSecret;

  options.ResponseType = OpenIdConnectResponseType.Code;

  options.Scope.Clear();
  options.Scope.Add("openid");

  options.ClaimsIssuer = "Auth0";

  options.CallbackPath = PathString.FromUriComponent("/callback");

  options.SaveTokens = true;

  options.TokenValidationParameters = new TokenValidationParameters
  {
    NameClaimType = "name",
    RoleClaimType = Settings.Auth.RoleClaimType
  };

  options.Events = new OpenIdConnectEvents
  {
    OnRedirectToIdentityProvider = (context) =>
    {
      context.ProtocolMessage.RedirectUri = Settings.Auth.RedirectUri;

      return Task.FromResult(0);
    },
    OnRedirectToIdentityProviderForSignOut = (context) =>
    {
      var logoutUri = $"https://{Settings.Auth.Domain}/v2/logout?client_id={Settings.Auth.ClientId}";

      var postLogoutUri = context.Properties.RedirectUri;
      if (!string.IsNullOrEmpty(postLogoutUri))
      {
        if (postLogoutUri.StartsWith("/"))
        {
          var request = context.Request;
          postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
        }
        logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri)}";
      }

      context.Response.Redirect(logoutUri);
      context.HandleResponse();

      return Task.CompletedTask;
    }
  };
});

Note the inclusion of OnRedirectToIdentityProvider that is used to provide a configuration driven redirect uri - this is, in practice, replacing the http:// protocol with https://.

Additionally, in an attempt to use forwarded headers, I’ve included this to the Configure method

var forwardOpts = new ForwardedHeadersOptions
{
  ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor
};

forwardOpts.KnownNetworks.Clear();
forwardOpts.KnownProxies.Clear();

app.UseForwardedHeaders(forwardOpts);

Per the documentation on the Azure Application Gateway the appropriate headers are meant to be included:

Modifications to the request

An application gateway inserts four additional headers to all requests before it forwards the requests to the backend. These headers are x-forwarded-for, x-forwarded-proto, x-forwarded-port, and x-original-host. The format for x-forwarded-for header is a comma-separated list of IP:port.

However, even with this configuration, authentication is still failing. The client is redirected to the appropriate https:// callback endpoint, but is met with a 500.

Are there any ways to work around this, or am I perhaps setting something up incorrectly?


edited for additional information

I’ve also attempted to perform this http:// to https:// redirect using the following code in Configure

app.Use((context, next) =>
{
  context.Request.Scheme = "https";
  return next();
});

which I’ve used instead of replacing the RedirectUri in OnRedirectToIdentityProvider - but this results in the same exception shown above. This happens both with and without the UseForwardedHeaders snippet from above.

1 Like

Ok, so I’ve realized that I had kind of gotten ahead of myself in a couple ways with this, and I’ve simplified things a little bit after some experimentation.

The way that things currently stand, I’ve removed the redirect code and used just the forwarded headers options code.

This means that my startup code is almost identical to the sample, with the addition below.

At the top of ConfigureServices

services.Configure<ForwardedHeadersOptions>(options =>
{
  options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;

  options.KnownNetworks.Clear();
  options.KnownProxies.Clear();
});

and later, at the top of Configure

app.UseForwardedHeaders();

This allows the https:// redirect url to be constructed, so I am in fact hitting the https://.../callback URL on my browser.

However - the same original exception (as above) is still being hit.

Any info on what might be causing the Exception: Unable to unprotect the message.State. issue?

1 Like

The answer, as it turns out, has to do with my service fabric app itself. The site I was using Auth0 on had, in fact, multiple instances backing it up. When redirected to the /callback endpoint it seems as though it was pushed to a different instance, which caused the message decryption to fail.

In my case, I can work around this by either forcing a single node of the site to exist or finding a way to share whatever underlying data protection key is being used (which, hopefully, is available via some middleware - though in my testing data protection did not seem to be the answer but I may be mistaken).

2 Likes

Hey there @jay.myers!

I don’t remember a single user doing such a massive research and implementing such part of our stack so quickly and at the same time sharing it with the rest of community! Thanks a lot for doing that!

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.