Open Closed

Hangfire Authorization Problem #5167


User avatar
0
cangunaydin created
  • ABP Framework version: v7.1.0
  • UI type: Angular
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): yes

Hello, I am trying to implement hangfire to my project. I managed to do that, the only problem that i have is with hangfire dashboard. If i do not use authentication for my hangfire dashboard it works fine. And i can access it. But when i have enabled the filter, i can not access the page. In HttpContext i can not see the user claims, so it return unauthenticated inside AbpHangfireAuthorizationFilter.

I logged in with admin rights and when i call a method from swagger endpoint i can see that claims are there for user and CurrentUser.IsAuthenticated property returns true. I couldn't understand why current user is unauthenticated (or claims are not in the context) when AbpHangfireAuthorizationFilter is triggered. any tips about it?

here is the onApplicationInitialization

  public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var app = context.GetApplicationBuilder();
        var env = context.GetEnvironment();

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

        app.UseAbpRequestLocalization();
        app.UseStaticFiles();
        app.UseAbpSecurityHeaders();
        app.UseRouting();
        app.UseCors();
        app.UseAuthentication();

        if (MultiTenancyConsts.IsEnabled)
        {
            app.UseMultiTenancy();
        }

        app.UseAuthorization();
        app.UseSwagger();
        app.UseAbpSwaggerUI(options =>
        {
            options.SwaggerEndpoint("/swagger/v1/swagger.json", "Doohlink API");

            var configuration = context.GetConfiguration();
            options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
        });
        app.UseAuditing();
        app.UseAbpSerilogEnrichers();
        app.UseUnitOfWork();
        app.UseHangfireDashboard("/hangfire", new DashboardOptions
        {
            AsyncAuthorization = new[] { new DoohlinkAuthorizationFilter() }
        });
        app.UseConfiguredEndpoints();

    }

ps: to see the httpcontext I have changed AbpHangfireAuthorizationFilter to custom class inside my project here is DoohlinkAuthorizationFilter

public class DoohlinkAuthorizationFilter : IDashboardAsyncAuthorizationFilter
{
    private readonly bool _enableTenant;
    private readonly string _requiredPermissionName;

    public DoohlinkAuthorizationFilter(bool enableTenant = false, string requiredPermissionName = null)
    {
        _enableTenant = requiredPermissionName.IsNullOrWhiteSpace() ? enableTenant : true;
        _requiredPermissionName = requiredPermissionName;
    }

    public async Task<bool> AuthorizeAsync(DashboardContext context)
    {
        if (!IsLoggedIn(context, _enableTenant))
        {
            return false;
        }

        if (_requiredPermissionName.IsNullOrEmpty())
        {
            return true;
        }

        return await IsPermissionGrantedAsync(context, _requiredPermissionName);
    }

    private static bool IsLoggedIn(DashboardContext context, bool enableTenant)
    {
        var currentUser = context.GetHttpContext().RequestServices.GetRequiredService<ICurrentUser>();

        if (!enableTenant)
        {
            return currentUser.IsAuthenticated && !currentUser.TenantId.HasValue;
        }

        return currentUser.IsAuthenticated;
    }

    private static async Task<bool> IsPermissionGrantedAsync(DashboardContext context, string requiredPermissionName)
    {
        var permissionChecker = context.GetHttpContext().RequestServices.GetRequiredService<IPermissionChecker>();
        return await permissionChecker.IsGrantedAsync(requiredPermissionName);
    }
}

and this is httpcontext quickwatch


6 Answer(s)
  • User Avatar
    0
    EngincanV created
    Support Team .NET Developer

    Hi, this is the expected behavior. Because, when you authorize through Swagger, in every request that you made through Swagger, an authorization header will be passed with the bearer token (ABP intercepts the call and pass the token, in the js side), and that makes you authorized.

    However, if you navigate to the /hangfire page, you will see 401 status code, because there is not any authorization header in the request headers. You should set the authorization header with the bearer token in your request by yourself.

  • User Avatar
    0
    cangunaydin created

    Hello again, Thanks for the explanation, can i ask how can i do the same for hangfire dashboard? If i write a mvc controller action, how can i redirect the user to auth server log in page and then redirect to hangfire dashboard?

  • User Avatar
    0
    EngincanV created
    Support Team .NET Developer

    Hello again, Thanks for the explanation, can i ask how can i do the same for hangfire dashboard? If i write a mvc controller action, how can i redirect the user to auth server log in page and then redirect to hangfire dashboard?

    Hi, you can do this easily by redirecting to auth server and specifying the ReturnUrl as /hangfire (https://localhost:5000/Account/Login?ReturnUrl=https://localhost:5001/hangfire), however, even if you do that, you should pass the authorization header from auth server to your host application somehow, so a simple redirection would not be enough.

    Ideally, you need to authenticate via your client application, retrieve the token, and use it in the authorization header in the subsequent calls that you'll make to the host application.

  • User Avatar
    0
    cangunaydin created

    Hello, I didn't understand how i can pass the authorization header while i am redirecting the user from angular. Is there any sample code how to do that? I would be happy if you can share it. I fixed the problem by implementing another authentication schema, so in that way i can challenge the user to authenticate and when it is done i can sign in the user and redirect the user to the page. sth like this.

     context.Services.AddAuthentication(options =>
            {
                options.DefaultScheme = "DoohlinkCustomPolicy";
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
            .AddPolicyScheme("DoohlinkCustomPolicy", "DoohlinkCustomPolicy", options =>
            {
                options.ForwardDefaultSelector = context =>
                {
                    string authorization = context.Request.Headers[HeaderNames.Authorization];
                    if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
                        return JwtBearerDefaults.AuthenticationScheme;
                    else
                        return CookieAuthenticationDefaults.AuthenticationScheme;
                };
            })
            .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
            {
                options.Authority = configuration["AuthServer:Authority"];
                options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
                options.Audience = "Doohlink";
            })
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
             {
                 options.ExpireTimeSpan = TimeSpan.FromDays(365);
             })
            .AddAbpOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
            {
                options.Authority = configuration["AuthServer:Authority"];
                options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
                options.ResponseType = OpenIdConnectResponseType.Code;
    
                options.ClientId = configuration["AuthServer:WebClientId"];
                options.ClientSecret = configuration["AuthServer:WebClientSecret"];
    
                options.UsePkce = true;
                options.SaveTokens = true;
                options.GetClaimsFromUserInfoEndpoint = true;
    
                options.Scope.Add("roles");
                options.Scope.Add("email");
                options.Scope.Add("phone");
                options.Scope.Add("Doohlink");
                options.Events.OnTicketReceived = async (TicketReceivedContext e) =>
                {
                    await e.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
                        e.Principal,
                        new AuthenticationProperties
                        {
                            IsPersistent = true,
                            AllowRefresh = true,
                            ExpiresUtc = DateTime.UtcNow.AddDays(1)
                        });
                };
            });
    

    and challenging the user to login in a controller.

    public ActionResult Login()
        {
            return Challenge(new AuthenticationProperties { RedirectUri = "/hangfire" }, OpenIdConnectDefaults.AuthenticationScheme);
        }
    

    this works for now. But I would happy to know how to do it from angular. Because as i know when you do redirect from client side like. window.open() you can not pass authorization header. Or maybe I am wrong?

  • User Avatar
    0
    EngincanV created
    Support Team .NET Developer

    I fixed the problem by implementing another authentication schema, so in that way i can challenge the user to authenticate and when it is done i can sign in the user and redirect the user to the page.

    Great to hear that you fixed the problem.

    I didn't understand how i can pass the authorization header while i am redirecting the user from angular. Is there any sample code how to do that? I would be happy if you can share it.

    Actually, I was just clarifying the flow in a normal situation. For your requirement, creating a new authentication scheme and challenging to login with redirect uri as /hangfire is a good approach I think. And i don't have any alternative to that approach for now.

  • User Avatar
    0
    cangunaydin created

    Thank you for the help.

Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11