microsoft-identity-web: [Bug] Redirect URI is set to http instead of https when deploying to Azure App Service for Docker container (Linux)

Which Version of Microsoft Identity Web are you using ? Microsoft Identity Web 1.0.0-preview

Where is the issue?

  • Web App
    • [ x ] Sign-in users
    • Sign-in users and call web APIs
  • Web API
    • Protected web APIs (Validating tokens)
    • Protected web APIs (Validating scopes)
    • Protected web APIs call downstream web APIs
  • Token cache serialization
    • In Memory caches
    • Session caches
    • Distributed caches

Other? - please describe; Probably valid for most cases.

Is this a new or existing app?

This is an experiment to test an app with Microsoft.Identity.Web deployed to Azure App Service for Docker Containers.

Repro

  1. Open example 1-2-AnyOrg
  2. Add Docker support in Visual Studio (create standard Dockerfile)
  3. Build image
  4. Publish to container registry
  5. Create a new App Service using the container image
  6. Add configuration
  7. Browse new web app, and you will be redirected to login.microsoftonline.com as expected, but with redirect_uri=http%3A%2F%2F<your app service name>.azurewebsites.net%2Fsignin-oidc instead of redirect_uri=https%3A%2F%2F<your app service name>.azurewebsites.net%2Fsignin-oidc .
  8. Manually changing the redirect URI to HTTPS will enable authentication and the app will work as expected.

Expected behavior Expected to be redirected to the HTTPS-page.

Actual behavior Error AADSTS50011 is shown in the browser, as there is a mismatch between registered redirect-URIs.

Possible Solution Maybe there is a way to configure the App Service to avoid this issue? Or is there a way to configure Microsoft.Identity.Web to use HTTPS in the redirect URI?

Additional context/ Logs / Screenshots The application log shows the following: Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware[3]: Failed to determine the https port for redirect.

There is a hint here that SSL should not be enabled in the web app, but how is it possible to not have SSL enabled in the web app, but still have HTTPS in the redirect URI?

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 58 (1 by maintainers)

Commits related to this issue

Most upvoted comments

Spec

(updated on 07/05/2020)

Why?

In order to avoid customers to have to update the redirect URI in the code when they deploy their Web apps, the redirect URI is computed automatically by ASP.NET Core (part of the auth code flow), and also by Microsoft.Identity.Web (in TokenAcquisition.BuildConfidentialClientApplicationAsync).

Note that this does not prevent developers to add redirect URI in the app registration portal, but this allow them to have the same code for debugging locally and for deployed applications if they wish to.

The MSAL.NET part is here:

https://github.com/AzureAD/microsoft-identity-web/blob/c2f81fd6b7be240046350124311835885051972f/src/Microsoft.Identity.Web/TokenAcquisition.cs#L334-L338

Although this solves many cases, there are cases (like working in containers, or with reverse proxys), where this is not flexible enough

The openIDConnect redirect URI is computed by ASP.NET Core, but can be overriden by subscribing to the OpenIdConnect OnRedirectToIdentityProvider event and by setting the context.ProtocolMessage.RedirectUri property to the desired redirect URI.

Same problem for the post logout redirect URI used in global sign-out. It needs to be overridable, and that can be done but subscribing to the OnRedirectToIdentityProviderForSignOut openIdConnect event and setting the context.ProtocolMessage.PostLogoutRedirectUri property.

What?

Given the following config

  "CallbackPath": "/signin-oidc",
   "SignedOutCallbackPath ": "/signout-callback-oidc",

The proposal is to add new properties in MicrosoftIdentityOptions to override the redirect URI and the post logout redirect URI:

  • Add RedirectUri in MicrosoftIdentityOptions
  • Add PostLogoutRedirectUri in MicrosoftIdentityOptions

Given the following config

//  "CallbackPath": "/signin-oidc",
//   "SignedOutCallbackPath ": "/signout-callback-oidc",
  "RedirectUri ": "http://mywebapp.mycompany.com/signin-oidc",
   "PostLogoutRedirectUri": "http://mywebapp.mycompany.com/signout-callback-oidc",
  • Use these properties in the AddSignIn method:

https://github.com/AzureAD/microsoft-identity-web/blob/27391236c35d037ebcd2e48f5c9a909b6c0e1bbc/src/Microsoft.Identity.Web/WebAppAuthenticationBuilderExtensions.cs#L62-L68

In the builder.AddOpenIdConnect(openIdConnectScheme, options => { bloc:

            var redirectToIdpHandler = options.Events.OnRedirectToIdentityProvider;
            options.Events.OnRedirectToIdentityProvider = async context =>
            {
                // Call what Microsoft.Identity.Web is doing
                await redirectToIdpHandler(context);
                // Override the redirect URI to be what you want
                if(microsoftIdentityOptions.RedirectUri != null)
                {
                    context.ProtocolMessage.RedirectUri = (microsoftIdentityOptions.RedirectUri;
                }
            };
            var redirectToIdpForSignOutHandler = options.Events.OnRedirectToIdentityProviderForSignOut;
            options.Events.OnRedirectToIdentityProviderForSignOut = async context =>
            {
                // Call what Microsoft.Identity.Web is doing
                await redirectToIdpForSignOutHandler(context);
                // Override the redirect URI to be what you want
                if (microsoftIdentityOptions.PostLogoutRedirectUri )
                {
                    context.ProtocolMessage.PostLogoutRedirectUri = microsoftIdentityOptions.PostLogoutRedirectUri;
                }
            };
        });

__ @jennyf19 @pmaytak @bgavrilMS @henrik-me : what do you think?

Thanks for the suggestion! I tested it and found that I needed to add the post logout URI to the OnRedirectToIdentityProviderForSignOut event. This solution works as expected with an added WebAppURI parameter in the configuration, but an integrated solution using absolute or relative CallbackPath/SignedOutCallbackPath would of course be preferable.

https://demoopenid.azurewebsites.net/ has been updated with the code below.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSignIn(Configuration);
    services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme,
        options => {
            var redirectToIdpHandler = options.Events.OnRedirectToIdentityProvider;
            options.Events.OnRedirectToIdentityProvider = async context =>
            {
                // Call what Microsoft.Identity.Web is doing
                await redirectToIdpHandler(context);

                // Override the redirect URI to be what you want
                if(Configuration["AzureAd:WebAppURI"] != null)
                {
                    context.ProtocolMessage.RedirectUri = Configuration["AzureAd:WebAppURI"] + Configuration["AzureAd:CallbackPath"];
                }
            };

            var redirectToIdpForSignOutHandler = options.Events.OnRedirectToIdentityProviderForSignOut;
            options.Events.OnRedirectToIdentityProviderForSignOut = async context =>
            {
                // Call what Microsoft.Identity.Web is doing
                await redirectToIdpForSignOutHandler(context);

                // Override the redirect URI to be what you want
                if (Configuration["AzureAd:WebAppURI"] != null)
                {
                    context.ProtocolMessage.PostLogoutRedirectUri = Configuration["AzureAd:WebAppURI"] + Configuration["AzureAd:SignedOutCallbackPath"];
                }
            };
        });

    // More code here ...
}

@BurritoSmith @goshmiller @graemeWT @TimThaens @krispenner

We’ve been directed by the ASP .NET Core team to remove the MicrosoftIdentityOptions values of RedirectUri, PostLogoutRedirectUri, and ForceHttpsRedirectUris from the public API. These will be removed in the next release (0.2.0-preview).

The AspNetCore guidance for working with proxies is here

Address the issue centrally by using UseForwardedHeaders to fix up the request fields like scheme.

The container scenario should have been addressed by default in .NET Core 3.0

If there are issues with this for you, please contact the ASP .NET Core team, as they will be the right team to assist with this.

More info here.

cc: @jmprieur @Tratcher

Yes, @BurritoSmith it will work on the first leg of the authentication process. Thanks for your feedback. We appreciate much!

@jennyf19 @jmprieur Thanks so much guys for your work on this. I haven’t been following this super closely since I’ve been pretty crazy busy with several projects but I’m curious if this release will work on the first leg of the authentication process. @jmprieur mentioned above that a prior version would not and that’s actually the only place I need it as I’m not using MSAL for my client-side stuff.

Thanks again for all your efforts!

@jmprieur @jennyf19 I can confirm your suggested additions to the startup.cs file are also working in my above described environment. I agree with @mochr that your suggested change is more ideal however so I will wait to wait to re-implement this for our .net core 2.2 to .net core 3.1 upgrade when you have things finalized. Thanks so much for your work on this!

@mochr : thanks for confirming. You can also the post logout URI with context.ProtocolMessage.PostLogoutRedirectUri = "your post logout URI";

Updating the code snippet:

 public void ConfigureServices(IServiceCollection services)
 {
  services.AddSignIn(Configuration);
  services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, 
                                         options => {
 
     var redirectToIdpHandler = options.Events.OnRedirectToIdentityProvider;
     options.Events.OnRedirectToIdentityProvider = async context =>
     {
      // Call what Microsoft.Identity.Web is doing
      await redirectToIdpHandler(context);

     // Override the redirect URI to be what you want
     context.ProtocolMessage.RedirectUri = "your redirect URI";
     context.ProtocolMessage.PostLogoutRedirectUri = "your post logout URI";
    };
   });

 // More code here ...
}

@jmprieur yes, the redirect URIs in the app registration are set to https. I actually mis-informed you yesterday when I said my app was hosted on azure (it was late in the day and I was tired and confused about which particular one this was…). This particular app is an internal one that is hosted behind a load balancer. The nodes host the application on http using ports to separate each application in IIS but the load balancer accepts https traffic then forwards that on to the nodes over http. So in this model the nodes seem to be checking themselves and determining that they are being hosted over http and therefore changing the reply url that gets passed up accordingly. I haven’t sniffed the traffic out, but it’s possible that in the flow, they’re actually passing the machine name that’s hosting them rather than the host.domain name we’re using to reach our LB. IE http://mymachinename:44200 instead of https://myhost.mydomain.com. I have seen a few different errors from the MS login about mismatched reply URLs when the machine names are not included.

In any case, being able to override the behavior you described above would be a must for our situation. We can not rely on our machines accurately obtaining a computed return URL. I assumed that it was using the “domain” property in the JSON config as the reply url. To me, you could use that in conjuction with the signin-oidc, or just add another property in the json config for us to manipulate what the reply url should be.

@Tratcher, I do not have access to logs, some internal policies is required to take it.

But a I’ve been solved the problem, removing the first nginx block (:80 to :443 redirect) and leaving the application do the https redirection, no code changed.

nginx.conf (server block)

    server {
        listen              80;
        listen              [::]:80;
        listen              443       ssl;
        listen              [::]:443  ssl;
        server_name        sts.des.mycompany.com;
        ssl_certificate     /etc/nginx/ssl/bundle-sts.crt;
        ssl_certificate_key /etc/nginx/ssl/private-sts.key;
        ssl_protocols       SSLv3 TLSv1.1 TLSv1.2 TLSv1.3;
        ssl_ciphers         HIGH:!aNULL:!MD5;
        ssl_session_timeout 1d;
        ssl_session_cache   shared:SSL:10m;
        ssl_session_tickets off;
        ssl_stapling        off;

        gzip            on;
        gzip_vary       on;
        gzip_min_length 10240;
        gzip_proxied    expired no-cache no-store private auth;
        gzip_types      application/atom+xml application/geo+json application/javascript application/x-javascript application/json application/ld+json application/manifest+json application/rdf+xml application/rss+xml application/xhtml+xml application/xml font/eot font/otf font/ttf image/svg+xml text/css text/javascript text/plain text/xml;
        gzip_disable    "MSIE [1-6]\.";

        location /diag {
            alias         /usr/share/nginx/html/diag.txt;
            default_type  text/plain;
        }

        location / {
            proxy_pass                  http://sts-service:80;
            proxy_set_header            Host                    $host;
            proxy_set_header            Referer                 "https://$server_name$request_uri";
            proxy_set_header            X-Real-IP               $remote_addr;
            proxy_set_header            X-Forwarded-For         $proxy_add_x_forwarded_for;
            proxy_set_header            X-Forwarded-Proto       "https";
            proxy_set_header            X-Forwarded-Host        $host;
            proxy_set_header            X-Forwarded-Port        443;
            proxy_set_header            X-Original-For          $remote_addr;
            proxy_set_header            X-Original-Proto        "https";
            proxy_set_header            X-Original-Host         $host;
            proxy_set_header            X-Original-Port         443;
            proxy_http_version          1.1;  
            proxy_buffering             on; 
            proxy_buffer_size           256k; 
            proxy_buffers               4                       256k;
            proxy_set_header            Upgrade                 $http_upgrade;
            proxy_set_header            Connection              $http_connection;
            proxy_cache_bypass                                  $http_upgrade;
            proxy_redirect              off;
            port_in_redirect            off;
        }

        # Media: images, icons, video, audio, HTC
        location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|mp3|ogg|ogv|webm|htc|woff2|woff)$ {
            expires                     1d;
            access_log                  off;
            add_header                  Cache-Control           "public";
            proxy_set_header            X-Forwarded-For         $proxy_add_x_forwarded_for;
            proxy_set_header            X-Forwarded-Proto       "https";
            proxy_set_header            X-Forwarded-Host        $host;
            proxy_set_header            X-Forwarded-Port        443;
            proxy_pass                  http://sts-service:80;
        }

        # CSS and Javascript
        location ~* \.(?:css|js)$ {
            expires                     1h;
            access_log                  off;
            add_header                  Cache-Control           "public";
            proxy_set_header            X-Forwarded-For         $proxy_add_x_forwarded_for;
            proxy_set_header            X-Forwarded-Proto       "https";
            proxy_set_header            X-Forwarded-Host        $host;
            proxy_set_header            X-Forwarded-Port        443;
            proxy_pass                  http://sts-service:80;
        }
    }

My bad, @krispenner, I thought I had linked it. It’s https://github.com/AzureAD/microsoft-identity-web/issues/175

It’s assigned to @pmaytak

@BurritoSmith @mochr Included in 0.1.3-preview release @krispenner - so you know @jmprieur will be opening a new issue for the feature you asked for here.

For many people who only want to ensure https is used (instead of http) like myself as I’m running in a Docker container hosted in Azure App Service. I think a simpler option such as ForceHttpsRedirectUris = true in the configuration/options would be simpler. It would remove the need to specify the full absolute URI just to ensure https is used allowing the computed redirect URI to stay and just upgrade it to https. I’m concerned with managing the absolute URIs across configuration files and environments - relative paths as so much friendlier.

Here is what I’ve currently done which solved my issue of http being used when in a Docker container:

services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme,
    options => {
        var redirectToIdpHandler = options.Events.OnRedirectToIdentityProvider;
        options.Events.OnRedirectToIdentityProvider = async context =>
        {
            // Call what Microsoft.Identity.Web is doing
            await redirectToIdpHandler(context);

            // Override the redirect URI to be what you want
            if (context.ProtocolMessage?.RedirectUri?.StartsWith("http://") ?? false)
            {
                context.ProtocolMessage.RedirectUri = context.ProtocolMessage.RedirectUri.Replace("http://", "https://");
            }
        };

        var redirectToIdpForSignOutHandler = options.Events.OnRedirectToIdentityProviderForSignOut;
        options.Events.OnRedirectToIdentityProviderForSignOut = async context =>
        {
            // Call what Microsoft.Identity.Web is doing
            await redirectToIdpForSignOutHandler(context);

            // Override the redirect URI to be what you want
            if (context.ProtocolMessage?.PostLogoutRedirectUri?.StartsWith("http://") ?? false)
            {
                context.ProtocolMessage.PostLogoutRedirectUri = context.ProtocolMessage.PostLogoutRedirectUri.Replace("http://", "https://");
            }
        };
    });

No, there is no need to rush the next release for me. I guess the next release will be out soon anyway (within 2-3 weeks).

@BurritoSmith : did you add the right redirect URIs (with https:) in the app registration for your application?