aspnet / BasicMiddleware

[Archived] Basic middleware components for ASP.NET Core. Project moved to https://github.com/aspnet/AspNetCore
Apache License 2.0
169 stars 84 forks source link

UseForwardedHeaders overrides authority Url of OpenIdConnect #353

Closed cranecx closed 5 years ago

cranecx commented 6 years ago

Hi,

I triying to config a netcore 2.1 MVC App in a reverse proxie (IIS), the things seems good, I've configure my proxie to add X-Forwarded-Proto and X-Forwarded-Host to request headers and works fine with UseForwardedHeaders middleware. The problem is that when I triying to auth the user by Auzure Active Directory I get redirected to the X-Forwarded-Host value + the query of Azure AD

Here is my Startup:

using System;
using System.Threading.Tasks;
using IdentityServer.Internal.Data;
using IdentityServer.Internal.Middlewares;
using IdentityServer.Internal.Models.Configurations;
using IdentityServer.Internal.Models.Entities;
using IdentityServer.Internal.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Ajax.Utilities;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.HttpOverrides;

namespace IdentityServer.Internal
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services
                .AddDbContext<IdentityServerContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("IdentityServer")))

                .AddDbContext<MemoryContext>(options =>
                options.UseInMemoryDatabase("IdentityServerMemory"));

            services
                .AddMemoryCache()
                .AddDistributedMemoryCache();

            services
                .Configure<ForwardedHeadersOptions>(Configuration.GetSection("ForwardedHeadersOptions"))
                .Configure<TokenIssuerConfiguration>(Configuration.GetSection("TokenIssuerConfiguration"));

            services
                .AddDataProtection();

            services
                .AddIdentityCore<IdentityUser>()
                .AddEntityFrameworkStores<IdentityServerContext>()
                .AddSignInManager();

            services
                .AddScoped<Minifier>()
                .AddScoped<IViewRenderService, ViewRenderService>()
                .AddScoped<ITokenIssuer, TokenIssuer>()
                .AddScoped<ICodeGenerator, CodeGenerator>()
                .AddSingleton<IActionContextAccessor, ActionContextAccessor>()
                .AddSingleton<IHostedService, CleanerMemoryService>();

            services
                .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddOpenIdConnect(options =>
                 {
                     Configuration.Bind("AzureAD", options);
                     options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                     options.ResponseType = OpenIdConnectResponseType.Code;
                     options.ResponseMode = OpenIdConnectResponseMode.FormPost;
                     options.RequireHttpsMetadata = true;
                     options.SaveTokens = false;
                     options.Events = new OpenIdConnectEvents
                     {
                         OnRedirectToIdentityProvider = OnRedirectToIdentityProvider
                     };
                 })
                 .AddCookie(options =>
                 {
                     options.LoginPath = "/Authorize/SignIn";
                 });

            services
                .AddMvc()
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

        private Task OnRedirectToIdentityProvider(RedirectContext context)
        {
            context.Options.MaxAge = TimeSpan.Zero;

            return Task.CompletedTask;
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseIdentityServerCors()
                .UseForwardedHeaders();
            if (!env.IsProduction())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error")
                    .UseHttpsRedirection()
                    .UseHsts();
            }

            app.UseStaticFiles()
                .UseAuthentication()
                .UseMvc(routes =>
                {
                    routes.MapRoute(
                        name: "default",
                        template: "{controller=Home}/{action=Index}/{id?}");
                });
        }
    }
}
My appsettings.json:

{
  "ConnectionStrings": {
    "IdentityServer": "******"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  //"AllowedHosts": "*",
  "ForwardedHeadersOptions": {
    "ForwardedHeaders": 7,
    "AllowedHosts": [ "localhost" ]
  },
  "AzureAD": {
    "ClientId": "*******",
    "Authority": "https://login.microsoftonline.com/******/",
    "CallbackPath": "/Azure/SignedOn",
    "ClientSecret": "EstVMpxyZcEAYJ/cqP8E3SNHcvFkNiIfhBllfzrqz3I=",
    "Resource": "https://graph.microsoft.com/"
  }
}

My challenge result:

[HttpGet]
        public IActionResult SignIn([FromQuery]Guid appId)
        {
            var action = Url.Action("SignedOn", "Authorize", new { appId }, Request.Scheme);
            var authProperties = _signInManager
            .ConfigureExternalAuthenticationProperties("AzureAD", action);

            return Challenge(authProperties, OpenIdConnectDefaults.AuthenticationScheme);
        }

The config of IIS:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="ReverseProxyInboundRule1" stopProcessing="true">
                    <match url="(.*)" />
                    <action type="Rewrite" url="http://localhost:53055/{R:1}" />
                    <serverVariables>
                        <set name="HTTP_X_Forwarded_Proto" value="https" />
                        <set name="HTTP_X_Forwarded_Host" value="localhost:53098" />
                    </serverVariables>
                </rule>
            </rules>
        </rewrite>
        <tracing>
            <traceFailedRequests>
                <add path="*">
                    <traceAreas>
                        <add provider="ASP" verbosity="Verbose" />
                        <add provider="ASPNET" areas="Infrastructure,Module,Page,AppServices" verbosity="Verbose" />
                        <add provider="ISAPI Extension" verbosity="Verbose" />
                        <add provider="WWW Server" areas="Authentication,Security,Filter,StaticFile,CGI,Compression,Cache,RequestNotifications,Module,FastCGI,WebSocket" verbosity="Verbose" />
                    </traceAreas>
                    <failureDefinitions statusCodes="100-999" />
                </add>
            </traceFailedRequests>
        </tracing>
    </system.webServer>
</configuration>

And I get this screen after challenge result:

is

The same implementation works fine without the reverse proxy.

Tratcher commented 6 years ago

You should not need a rewrite rule to add the x- headers with IIS, AspNetCoreModule does that for you. Rewrite rules can also interfere with responses.

/Azure/SignedOn doesn't refer to an MVC Controller, right? The CallbackPath is handled directly by the Auth middleware.

cranecx commented 6 years ago

I need the url rewrite because that "lives" in my "public" server (Local IIS), the code is executed in a private server (Kestrel Dev server).

That's right, Azure/SignedOn is handled by UseAuthentication Middleware, do yo have any idea why I get redirect to X-Forwarded-Host value instead of the authority URI at return Challenge?

Tratcher commented 6 years ago

Rewrite is the only thing I see here that could cause this.

cranecx commented 6 years ago

I found the problem, just like @Tratcher says, isn´t the middleware, it´s a IIS ARR feature, I will post how I corrected only in case some else had the same problem.

  1. Select the server node in IIS manager
  2. Go to Application Request routing Cache
  3. Click on Server proxy Settings
  4. UnCheck "Reverse rewritehost in response headers"