We are leaving Russia. We are against aggression and war with Ukraine. It is a tragedy for our nations, it is a nightmare

Getting a 403 when accessing on a server

Hi,

I setup an oidc implementation ages ago for a hangfire dashboard I have, but never really used it. Anyway I’ve picked up again, upgraded the project to .net 6 and reworked a few bits and bobs, but I’m having a weird issue where by it works with oidc locally, but when I deploy it to my server and access remotely, it flows through the auth fine and redirects back to the signedIn controller, but then when it redirects with a fully authenticated user, it returns a 403.

This is my DashboardAuthorizationFilter and the auth startup config

    internal interface IAuthenticationProvider
    {
        IDashboardAuthorizationFilter GetAuthorizationFilter();
    }

    internal class AuthenticationProvider : IAuthenticationProvider
    {
        private readonly IConfigurationFactory _configurationFactory;

        public AuthenticationProvider(IConfigurationFactory configurationFactory)
        {
            _configurationFactory = configurationFactory;
        }

        public IDashboardAuthorizationFilter GetAuthorizationFilter()
        {
            return _configurationFactory.GetConfig().EnableSso
                ? new IdentityServerAuthorizationFilter() as IDashboardAuthorizationFilter
                : new LocalRequestsOnlyAuthorizationFilter();
        }
    }

    internal static class AuthenticationServiceProvider
    {
        public static IServiceCollection Register([NotNull] this IServiceCollection services, HostConfiguration configuration)
        {
            if (configuration.EnableSso)
            {
                services
                    .AddAuthentication(opt =>
                    {
                        opt.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                        opt.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
                    })
                    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, opt =>
                    {
                        opt.ExpireTimeSpan = TimeSpan.FromMinutes(60);
                        opt.Cookie.Name = "scheduler";
                    })
                    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, opt =>
                    {
                        opt.Authority = configuration.AuthServiceUrl;
                        opt.RequireHttpsMetadata = false;
                        opt.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;

                        opt.ClientId = "scheduler_client";
                        opt.ClientSecret = "secret";

                        opt.ResponseType = "code id_token";
                        opt.CallbackPath = "/SignedIn";

                        opt.Scope.Clear();
                        opt.Scope.Add("openid");
                        opt.Scope.Add("profile");
                        opt.Scope.Add("offline_access");
                        opt.Scope.Add("scheduler");

                        opt.ClaimActions.MapAllExcept("iss", "nbf", "exp", "aud", "nonce", "iat", "c_hash");

                        opt.GetClaimsFromUserInfoEndpoint = true;
                        opt.SaveTokens = true;

                        opt.Events.OnRedirectToIdentityProvider = async context =>
                        {
                            if (context.Properties.Items.ContainsKey("idpName"))
                                context.ProtocolMessage.SetParameter("idpName", context.Properties.Items["idpName"]);

                            await Task.CompletedTask;
                        };
                    });
            }
            return services;
        }
    }

Here is an excert from my startup

        public void ConfigureServices(IServiceCollection services)
        {
            ...

            services.AddTransient<IAuthenticationProvider, AuthenticationProvider>();
            services.AddMvc();
            AuthenticationServiceProvider.Register(services, HostConfiguration);

            services.AddHangfire((sp, config) =>
            {
                config.UseSqlServerStorage(() => sp.GetService<IDbConnectionFactory>().Create() as DbConnection);
                config.UseAtoms();
                config.UseSerilogLogProvider();
            });

            if (HostConfiguration.Mode == OperationMode.AllInOne || HostConfiguration.Mode == OperationMode.Server)
            {
                services.AddHangfireServer(options =>
                {
                    options.ServerCheckInterval = TimeSpan.FromSeconds(10);
                    options.HeartbeatInterval = TimeSpan.FromSeconds(10);
                    options.ServerTimeout = TimeSpan.FromSeconds(15);
                    options.WorkerCount = Environment.ProcessorCount * HostConfiguration.WorkerCountPerCore;
                    options.ServerName = "Scheduler";
                    options.Queues = new[] { "default" };
                });
            }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
            IRecurringJobManager recurringJobManager,
            IConfigurationFactory configurationFactory)
        {
            var config = configurationFactory.GetConfig();
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

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

                if(context.Response.StatusCode == StatusCodes.Status401Unauthorized && config.EnableSso)
                {
                    context.Response.Redirect(config.WebAppUrl.AppendToRelativePath("scheduler"));
                }
            });

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapHangfireDashboard();
            });

            if (config.Mode == OperationMode.AllInOne || config.Mode == OperationMode.Dashboard)
            {
                app.UseHangfireDashboard("/hangfire", new DashboardOptions
                {
                    AppPath = config.WebAppUrl.GetRelativePath(),
                    Authorization = new List<IDashboardAuthorizationFilter>
                    {
                        app.ApplicationServices.GetRequiredService<IAuthenticationProvider>().GetAuthorizationFilter()
                    },
                    StatsPollingInterval = 1000
                });
            }
        }

It feels like something is overriding the IDashboardAuthorizationFilter or something to do with the routing, but I haven’t managed to put my finger on it.

I have confirmed that the HttpContext.Identity.User.IsAuthenticated and does have the role “Administrator” as specified in my auth filter.

Figured it out…

The new MapHangfireDashboard was overwriting my configuration in UseHangfireDashboard.

Removing UseHangfireDashboard and adding the configuration within MapHangfireDashboard fixed the issue.