15

I am facing the following problem. I have an ASP Net Core 2 web app that I want to deploy to Azure. The app authentication is integrated with the Azure Active Directory, so when I try to login the following requests happen:

GET https://login.microsoftonline.com/ecf3f643-27e5-4aa7-9d56-fd350e1e9c37/oauth2/authorize?client_id=20a2bcb5-0433-4bb4-bba3-d7dc4c533e85&redirect_uri=http://myapplication.mydomain.com/account/signin [...]  200 OK
POST http://myapplication.mydomain.com/account/signin 301 Redirect --> https://myapplication.mydomain.com/account/signin
GET https://myapplication.mydomain.com/account/signin 500 Internal Server Error

The first GET is the normal Azure Active Directory login request. Notice the redirect_uri parameter has protocol http.

The second request is the redirection to the redirect_uri, a POST with some parameters. Since I have configured Azure to allow only HTTPS traffic, then IIS redirects to the same URL with HTTPS. That's the third request. Notice this third request is a GET request, since HTTP redirection is always a GET request all the paremeters of the POST request are lost, and the authentication fails giving a HTTP 500 error in the backend.

I have tried to manually change the protocol in the redirect_uri parameter manually to HTTPS, and it works as expected. So, the only thing I need is to make ASP Net Core aware that the protocol is HTTPS.

How can that be done? I've searched tons of pages in the Internet without a clear answer.

Note: the redirect_uri is set by Kestrel. Since Azure App Service puts an IIS in front of my Kestrel and does the SSL termination there, Kestrel and my app do not know the protocol is HTTPS, and therefore use HTTP in the redirect uri.

UPDATE 1

Following the advice of @Bruce I've tried the example here, cloning the repository and configuring the application and the AD as stated there, and I am able to reproduce the error.

The redirect URI continues to be with http protocol. If I only add in the AD app configuration the https endpoint as reply URL, I get the error The reply address 'http://testloginad.azurewebsites.net/signin-oidc' does not match the reply addresses configured for the application. If I add the http protocol endpoint as reply URL, then I get an HTTP 500 error like the following:

System.Exception: Correlation failed.
   at Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.<HandleRequestAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.<Invoke>d__6.MoveNext()

I am still thinking the problem is related to Kestrel not knowing the connection is being done through HTTPS, but I do not know how to convey that information to it.

UPDATE 2

The configuration of the Azure web app I used:

  • Wep App Type: Linux
  • Application Settings:
    • Stack: .NET Core 2.0
    • Startup file: dotnet ./WebApp-OpenIDConnect-DotNet.dll
    • WEBSITE_HTTPLOGGING_RETENTION_DAYS: 5
    • ASPNETCORE_ENVIRONMENT: Development
    • Always ON: On
    • ARR Affinity: On
  • Custom domains:
    • HTTPS Only: On
  • Diagnostic logs:
    • Docker Container Logging: File System
    • Quota (MB): 35
    • Retention Period (Days): 5

In the web.config file I modified the following line to read like this:

<aspNetCore processPath="dotnet" arguments="./WebApp-OpenIDConnect-DotNet.dll" stdoutLogEnabled="false" stdoutLogFile="./stdout.log" />

Basically I put forward slashes instead of back slashes to avoid problems with Linux paths.

Everything else is configured using default settings.

UPDATE 3 As requested by @Tratcher, I add here the headers of the server reponses (for the sake of brevity I include only the headers I consider relevant, if you want to see any other one, feel free to ask me to add it):

  • First request (GET https://login.microsoftonline.com/ecf...):
    • Server: Microsoft-IIS/10.0
    • Set-Cookie: ESTSAUTHPERSISTENT=AQAFCCEADDB…sts; path=/; secure; HttpOnly
    • Strict-Transport-Security: max-age=31536000; includeSubDomains
  • Second request (POST http://testloginad.azurewebsites.net/signin-oidc):
    • Location: https://testloginad.azurewebsites.net/signin-oidc
    • Server: Microsoft-IIS/10.0
  • Third request (GET https://testloginad.azurewebsites.net/signin-oidc):
    • Server: Kestrel

No x-forwarded-proto header appears in any of the requests.

Note that one the root of the problem may be in the redirect of the second request, that is redirecting the HTTP POST to an HTTPS GET. That redirect should not happen since the POST should have been requested through HTTPS in the first place, but that did not happen because of the wrong http protocol in the redirect_uri of the first request.

UPDATE 4

I have confirmed this issue only happens if the chosen service plan is a Linux one. The issue does not happen at all if the service plan is a Windows one (using exactly the same code and configuration from the example of UPDATE 1). This may be a workaround, but not a solution, to the problem. The Linux app service seem to be flawed.

joanlofe
  • 3,360
  • 2
  • 26
  • 44
  • 1
    Web.Config doesn't apply to linux, only IIS. – Tratcher Mar 12 '18 at 11:53
  • 1
    Your idea that the app doesn't know the first request is https is likely correct. In a reverse proxy setup like this https is terminated at the proxy. The proxy should forward you the original scheme as an x-forwarded-proto header. Echo out the request headers to confirm. Then you can use the UseForwardedHeaders middleware to process the x-forwarded-proto header and fix up the request. – Tratcher Mar 12 '18 at 11:59
  • [@Tratcher](https://stackoverflow.com/users/2588374/tratcher), I think so. I've checked it, no `x-forwarded-proto` header in any of the server responses. However, there server response seems to be sent sometimes by Kestrel, sometimes by IIS, see above my update with the headers of the responses. – joanlofe Mar 12 '18 at 12:15
  • It's a request header, not a response header. The proxy adds it on the way in. You'd need to log or write out the request headers seen by your app. – Tratcher Mar 12 '18 at 14:07
  • I've found [this thread](https://github.com/aspnet/Docs/issues/2384), which is related to this issue, and also the threads linked there. Although it seems this issue was solved for Windows hosts, my feeling is that it is not working in Linux app service. By the way, I am using the UseIISIntegration middleware, which seems to include the UseForwardedHeaders one. I will try to get the request headers, to see if the x-forwarded-proto is there. – joanlofe Mar 14 '18 at 08:19
  • 1
    UseIISIntigration only adds UseForwardedHeaders when it detects IIS. For Linux you'd have to add it yourself. – Tratcher Mar 14 '18 at 09:53

8 Answers8

15

I had the problem myself. I took a deep dive into Microsoft's Microsoft.AspNetCore.Authentication and found out how they constructed the redirect url:

protected string BuildRedirectUri(string targetPath)
        => Request.Scheme + "://" + Request.Host + OriginalPathBase + targetPath;

Because the Web App already forces HTTPS this can be solved with the following code in the Startup.cs

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
      ForwardedHeaders = ForwardedHeaders.XForwardedProto
});

You only have to add this reference:

using Microsoft.AspNetCore.HttpOverrides;
FerronSW
  • 505
  • 4
  • 18
  • [Link to mentioned code](https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Security/Authentication/Core/src/AuthenticationHandler.cs#L175) – TeamDman Apr 27 '22 at 15:27
5

By consulting these links:

And applying 3 changes to the configuration, I got everything working on a Linux App Plan.

Step 1 : configure the ForwardedHeadersOptions

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

    // TODO : it's a bit unsafe to allow all Networks and Proxies...
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();
});

Step 2 : UseForwardedHeaders in the public void Configure(IApplicationBuilder app, IHostingEnvironment env) method

app.UseForwardedHeaders();

Step 3 : Only use UseHttpsRedirection for production

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();

    // Forward http to https (only needed for local development because the Azure Linux App Service already enforces https)
    app.UseHttpsRedirection();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
Stef Heyenrath
  • 9,335
  • 12
  • 66
  • 121
2

Here is a link to the recommended solution from Chris Ross (aka Tratcher), the ASP.NET Identity and IdentityServer guy.

The code you need is

services.Configure<ForwardedHeadersOptions>(options =>
{
   options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
   // Only loopback proxies are allowed by default. Clear that restriction because forwarders are
   // being enabled by explicit configuration.
   options.KnownNetworks.Clear();
   options.KnownProxies.Clear();
});

app.UseForwardedHeaders();

It seems to be applicable to .NET Core v2.x only and is fixed in 3.0.

Alex Klaus
  • 8,168
  • 8
  • 71
  • 87
1

I had the same problem in .NET 6, after solving the issue with http instead of https redirect like suggested above;

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
      ForwardedHeaders = ForwardedHeaders.XForwardedProto
});

My application is hosted in azure through container registry. And I ran in to the problem of "redirect_uri_mismatch" even after this fix.

I got it solved it by setting ASPNETCORE_FORWARDEDHEADERS_ENABLED=true in application settings.

enter image description here

Also added in azure devops pipeline like:

- task: AzureAppServiceSettings@1
  inputs:
    azureSubscription: 'zzz'
    appName: 'xxx'
    resourceGroupName: 'yyy'
    appSettings: |
      [
          {
          "name": "ASPNETCORE_ENVIRONMENT",
          "value": "$(environment)",
          "slotSetting": false
          },
          {
          "name": "ASPNETCORE_FORWARDEDHEADERS_ENABLED",
          "value": "true",
          "slotSetting": false
          }
      ]
Sgedda
  • 1,323
  • 1
  • 18
  • 27
0

I am facing the following problem. I have an ASP Net Core 2 web app that I want to deploy to Azure. The app authentication is integrated with the Azure Active Directory.

Since you did not mention that how did you integrate the AAD authentication into your web application. Moreover, I have checked that when accessing your application via http://analytics.lantek360.com or https://analytics.lantek360.com, the redirect_uri query string would the same: http://analytics.lantek360.com/account/signin. You could provide more details (e.g how did you build the authorize request) for us to narrow this issue.

Since I have configured Azure to allow only HTTPS traffic

The HTTPS Only setting uses a URL Rewrite rule for you to redirect HTTP to HTTPS. Details, you could follow How to make an Azure App Service HTTPS only.

For your requirement, I assume that you could manually use the middileware Microsoft.AspNetCore.Authentication.OpenIdConnect to integrate Azure AD into your .Net Core web application. For this approach, you could follow the tutorials below:

Integrating Azure AD (v1.0 endpoint) into an ASP.NET Core web app

Integrating Azure AD (v2.0 endpoint) into an ASP.NET Core web app

Note:

The redirect_uri for OpenID Connect would look like http(s)://<your-appname>.azurewebsites.net/signin-oidc. Since you need to Https only, you just need to add the redirect URI (https://{your-appname}.azurewebsites.net/signin-oidc) for your AAD app.

Moreover, you could also leverage App Service Authentication / Authorization for enable AAD authentication without changing code in your web application. Details, you could follow Configure your App Service app to use Azure Active Directory login in Azure Portal.

Bruce Chen
  • 18,207
  • 2
  • 21
  • 35
  • [@Bruce](https://stackoverflow.com/users/6755924/bruce-chen), I've followed the [Integrating Azure AD v1.0 endpoint](https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect-aspnetcore/blob/d55b22bb475f8cb101cb45ca5dcc27453069185c/README.md) and I am able to reproduce the error using that example. If I only add the https reply URL I get the error `The reply address 'http://testloginad.azurewebsites.net/signin-oidc' does not match the reply addresses configured for the application`. If I add the http reply url I get an error 500 Correlation failed. I will add details above. – joanlofe Mar 12 '18 at 09:16
  • I also used the v1.0 github sample code and deployed to my web app (`http://brucewebapp01.azurewebsites.net/` and `https://brucewebapp01.azurewebsites.net/`), I just modified the appsettings.json file about AAD settings. I found it could work as expected, and I used fiddler to capture the network traces when accessing my web app. – Bruce Chen Mar 12 '18 at 10:10
  • That's the same I did, can't imagine what's the difference. I will update above with the configuration I used, please take a look to see if there is any difference between your config and mine. Thanks a lot for your help, Bruce. – joanlofe Mar 12 '18 at 10:27
  • I used Windows OS app service plan. – Bruce Chen Mar 13 '18 at 01:43
0

The way to fix the issue is exactly as follows:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // ...

    /***
        * Forwarded Headers were required for nginx at some point.
        * https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.1#nginx-configuration
    ***/
    app.UseForwardedHeaders(new ForwardedHeadersOptions
    {
        RequireHeaderSymmetry = false,
        ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor
    });

    // ...
}

unfortunately I went to look at the code on how I fixed it but I don't remember why was it that way :) (the comment I left isn't very helpful either)

hope it helps.

Bart Calixto
  • 19,210
  • 11
  • 78
  • 114
0

Made it working by combination of the following ForwardedHeadersOptions configuration:

Options.ForwardedHeaders = ForwardedHeaders.All;
Options.ForwardLimit = null;
Options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8));
Options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("172.16.0.0"), 12));
Options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("192.168.0.0"), 16));
AndrewG
  • 186
  • 4
0

Azure uses the incoming Request.Schema to build the reply URL even if you want it to be https. If you're using TLS termination anywhere it can cause the schema to be read as 'http'

I tried X-Forwarded with my Kubernetes cluster and couldn't get that configured correctly but it was much easier to just fix the outgoing response with a custom middleware.

app.Use(async (c, next) =>
{
    await next();

    if (c.Response.Headers.ContainsKey("Location"))
    {
        var location = c.Response.Headers["Location"];
        if (!location.Any(c => c.Contains("redirect_uri=https")))
        {
            var newLocation = location.Select(c => 
            c.Replace("redirect_uri=http", "redirect_uri=https"));
            c.Response.Headers["Location"] = new StringValues(newLocation.ToArray());
        }
    }
});
mrmichaeldev
  • 329
  • 3
  • 11