Search code examples
asp.net-coreidentityserver4

Including a Subdirectory in URI - ASP.NET Core 3.1 Identity Server


I am trying to add a subdirectory to my identity server, so I can use it with nginx.

To note, this is an identity server with the UI, see (quickstart ui)

After perusing the github issues for identity server, I managed to find the code to actually add the subdirectory.

Here is my configure:

public void Configure(IApplicationBuilder app)
{
    if (Environment.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.Map("/auth", app =>
    {
        app.UseRouting();

        app.UseStaticFiles();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
            endpoints.MapRazorPages();
        });


        app.UseIdentityServer();
    });           
}

However, when I navigate to http://xxx:8888/auth/account/login and attempt to login and receive an identity cookie, the URL stays the same and I am presented with a blank screen and no cookies. What should happen is that I should be redirected back to the homepage with a particular user logged in.

This seems to happen only when I add a subdirectory.

To note, the well known endpoints work fine when getting an access token as password or resource owner with the /auth.

Here is my configuration services, is there something missing in here?:

public void ConfigureServices(IServiceCollection services)
{
    string connectionString = Configuration.GetConnectionString("AzureConnection");
    var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

    services.AddCors(options =>
    {
        options.AddPolicy("CorsPolicy",
            builder => builder.AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader());
    });

    services.AddControllersWithViews().AddRazorRuntimeCompilation();

    services.AddRazorPages()
        .AddRazorPagesOptions(options => 
            {                        
                options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
            });

    services.AddDbContext<IdentityDbContext>(options => options.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)));
    services.AddDbContext<ConfigurationDbContext>(options => options.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)));

    services.AddIdentity<ApplicationUser, IdentityRole>(options =>
    {
        options.SignIn.RequireConfirmedEmail = true;
    })
        .AddEntityFrameworkStores<IdentityDbContext>()
        .AddDefaultTokenProviders();

    services.AddAuthentication()
        .AddOpenIdConnect("azuread", "Azure AD", options => Configuration.Bind("AzureAd", options));

    services.Configure<OpenIdConnectOptions>("azuread", options =>
    {
        options.GetClaimsFromUserInfoEndpoint = true;
        options.SaveTokens = true;
        options.Scope.Add("openid");
        options.Scope.Add("profile");
        options.Scope.Add("email");
        options.Events = new OpenIdConnectEvents()
        {
            OnRedirectToIdentityProviderForSignOut = context =>
            {
                context.HandleResponse();
                context.Response.Redirect("/Account/Logout");
                return Task.FromResult(0);
            }
        };
    });

    var builder = services.AddIdentityServer(options =>
    {
        options.IssuerUri = "http://xxx:8888"; 
        options.PublicOrigin = "http://xxx:8888";

        options.Events.RaiseErrorEvents = true;
        options.Events.RaiseInformationEvents = true;
        options.Events.RaiseFailureEvents = true;
        options.Events.RaiseSuccessEvents = true;
        options.UserInteraction.LoginUrl = "/Account/Login";
        options.UserInteraction.LogoutUrl = "/Account/Logout";

        options.Authentication = new IdentityServer4.Configuration.AuthenticationOptions()
        {
            CookieLifetime = TimeSpan.FromHours(10), // ID server cookie timeout set to 10 hours
            CookieSlidingExpiration = true
        };
    })
    .AddConfigurationStore(options =>
    {
        options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
    })
    .AddOperationalStore(options =>
    {
        options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
        options.EnableTokenCleanup = true;
    })
    .AddAspNetIdentity<ApplicationUser>();
}

This is actually availiable to test as its on a VM with a public URL upon request.


Solution

  • My first observation is that you should place the various app.UseXXXX statements before the App.Map method. I also rearranged the middlewares in the code below.

    public void Configure(IApplicationBuilder app)
    {
        if (Environment.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        app.UseStaticFiles();
    
        app.UseRouting();
    
        app.UseIdentityServer();
        app.UseAuthorization();
    
        app.Map("/auth", app =>
        {
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
                endpoints.MapRazorPages();
            });
    });           
    

    }

    Also, UseIdentityServer includes a call to UseAuthentication, so it’s not necessary to have both.