Open Closed

Hangfire | unable to Authorize Dashboard & Execute Recurring Jobs #7664


User avatar
0
themisoft created
  • ABP Framework version: v8.1.4
  • UI Type: Blazor Server
  • Database System: PostgreSQL
  • Tiered (Auth/Blazor/Host): yes
  • My Problem: I implemented a Recurring Job Scheduler into our Application. The Jobs are getting scheduled and executed. The Problem is that Hangfire is not able to execute Application Services because it has no Authorization (await _peopleAppService.GetAsync() etc.). How can i Authorize Hangfire?

The only issue i found similiar to this, was this Ticket: https://www.abp.io/support/questions/5167/Hangfire-Authorization-Problem

But the only issue we have in common is that we cannot Authorize the Dashboard too. It was explained that we should redirect from Auth Server to the /hangfire URL, i followed the Documentation and the ticket but found no solution, how do i redirect from Auth Server to /hangfire? To Authorize the Dashboard?

  • Steps to reproduce the issue:
  • I can share a simple test project to demonstrate the issue, tell me what email to send to.

10 Answer(s)
  • User Avatar
    1
    maliming created
    Support Team Fullstack Developer

    hi

    You can create a Domain Service, move your code into it, and then call it in the Application service and your job class.

  • User Avatar
    0
    themisoft created

    I did not think about that, since i was using a lot of DTOs. this solution helped, Thank you!

    But what about the Dashboard Authorization?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    But what about the Dashboard Authorization?

    Please share your module code that contains the UseAbpHangfireDashboard

  • User Avatar
    0
    themisoft created

    This is the same code as in the ABP-Hangfire Documentation, but it does not work. As it was mentioned in old ticket, i dont know how to redirect from Auth Server to "/hangfire".

    <br> HttpApiHostModule

    [DependsOn(
        typeof(HangfireAuthTestHttpApiModule),
        typeof(AbpAutofacModule),
        typeof(AbpCachingStackExchangeRedisModule),
        typeof(AbpDistributedLockingModule),
        typeof(AbpAspNetCoreMvcUiMultiTenancyModule),
        typeof(AbpIdentityAspNetCoreModule),
        typeof(HangfireAuthTestApplicationModule),
        typeof(HangfireAuthTestEntityFrameworkCoreModule),
        typeof(AbpSwashbuckleModule),
        typeof(AbpAspNetCoreSerilogModule)
        )]
    
    [DependsOn(
        typeof(AbpBackgroundJobsHangfireModule),
        typeof(AbpHangfireModule)
        )]
    
    ...
    
    public override void ConfigureServices(ServiceConfigurationContext context)
        {
            ...
            ConfigureHangfire(context, configuration);
            ...
        }
    
    
    private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
        {
            context.Services.AddHangfire(config =>
            {
                config.UsePostgreSqlStorage(options =>
                {
                    options.UseNpgsqlConnection(configuration.GetConnectionString("Default"));
                });
            });
    
            context.Services.AddHangfireServer(options =>
            {
                options.Queues = new[] { "jobs", "default" };
            });
        }
    
    
    public override void OnApplicationInitialization(ApplicationInitializationContext context)
        {
            ...
            app.UseAbpHangfireDashboard("/hangfire", options =>
            {
                options.AsyncAuthorization = new[]
                {
                    new AbpHangfireAuthorizationFilter(false, requiredPermissionName: HangfireAuthTestPermissions.Hangfire)
                };
            });
    
            app.UseConfiguredEndpoints();
        }
    
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    What are code about AddAuthencation?

  • User Avatar
    0
    themisoft created

    https://www.abp.io/support/questions/5167/Hangfire-Authorization-Problem

    I want to Authorize the Dashboard, so it can only be accessed by Login. EngincanV mentioned in this ticket we have to redirect from Auth Server to "/hangfire" Right now i get Error 401

    "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."

    But how would i do that?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Please share full code of your module.

    Thanks

  • User Avatar
    0
    themisoft created

    Test Project, but same issue:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using Medallion.Threading;
    using Medallion.Threading.Redis;
    using Microsoft.AspNetCore.Authentication.Google;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.AspNetCore.Authentication.MicrosoftAccount;
    using Microsoft.AspNetCore.Authentication.Twitter;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Cors;
    using Microsoft.AspNetCore.DataProtection;
    using Microsoft.AspNetCore.Hosting;
    using Volo.Abp.PermissionManagement;
    using Microsoft.Extensions.Caching.StackExchangeRedis;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using HangfireAuthTest.EntityFrameworkCore;
    using HangfireAuthTest.MultiTenancy;
    using StackExchange.Redis;
    using Microsoft.OpenApi.Models;
    using HangfireAuthTest.HealthChecks;
    using Volo.Abp.Caching.StackExchangeRedis;
    using Volo.Abp.DistributedLocking;
    using Volo.Abp;
    using Volo.Abp.Account;
    using Volo.Abp.AspNetCore.Mvc;
    using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy;
    using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared;
    using Volo.Abp.AspNetCore.Serilog;
    using Volo.Abp.Autofac;
    using Volo.Abp.Caching;
    using Volo.Abp.Identity.AspNetCore;
    using Volo.Abp.Modularity;
    using Volo.Abp.Security.Claims;
    using Volo.Abp.Swashbuckle;
    using Volo.Abp.UI.Navigation.Urls;
    using Volo.Abp.VirtualFileSystem;
    using Volo.Abp.BackgroundJobs.Hangfire;
    using Volo.Abp.Hangfire;
    using Hangfire;
    using Hangfire.PostgreSql;
    using HangfireAuthTest.Permissions;
    
    namespace HangfireAuthTest;
    
    [DependsOn(
        typeof(HangfireAuthTestHttpApiModule),
        typeof(AbpAutofacModule),
        typeof(AbpCachingStackExchangeRedisModule),
        typeof(AbpDistributedLockingModule),
        typeof(AbpAspNetCoreMvcUiMultiTenancyModule),
        typeof(AbpIdentityAspNetCoreModule),
        typeof(HangfireAuthTestApplicationModule),
        typeof(HangfireAuthTestEntityFrameworkCoreModule),
        typeof(AbpSwashbuckleModule),
        typeof(AbpAspNetCoreSerilogModule)
        )]
    
    [DependsOn(
        typeof(AbpBackgroundJobsHangfireModule),
        typeof(AbpHangfireModule)
        )]
    public class HangfireAuthTestHttpApiHostModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            var configuration = context.Services.GetConfiguration();
            var hostingEnvironment = context.Services.GetHostingEnvironment();
    
            if (!configuration.GetValue<bool>("App:DisablePII"))
            {
                Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
            }
    
            ConfigureHangfire(context, configuration);
    
            ConfigureUrls(configuration);
            ConfigureConventionalControllers();
            ConfigureAuthentication(context, configuration);
            ConfigureSwagger(context, configuration);
            ConfigureCache(configuration);
            ConfigureVirtualFileSystem(context);
            ConfigureDataProtection(context, configuration, hostingEnvironment);
            ConfigureDistributedLocking(context, configuration);
            ConfigureCors(context, configuration);
            ConfigureExternalProviders(context);
            ConfigureHealthChecks(context);
    
            Configure<PermissionManagementOptions>(options =>
            {
                options.IsDynamicPermissionStoreEnabled = true;
            });
        }
    
    
        private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
        {
            context.Services.AddHangfire(config =>
            {
                config.UsePostgreSqlStorage(options =>
                {
                    options.UseNpgsqlConnection(configuration.GetConnectionString("Default"));
                });
            });
    
            context.Services.AddHangfireServer(options =>
            {
                options.Queues = new[] { "jobs", "default" };
            });
        }
    
    
        private void ConfigureHealthChecks(ServiceConfigurationContext context)
        {
            context.Services.AddHangfireAuthTestHealthChecks();
        }
    
        private void ConfigureUrls(IConfiguration configuration)
        {
            Configure<AppUrlOptions>(options =>
            {
                options.Applications["Angular"].RootUrl = configuration["App:AngularUrl"];
                options.Applications["Angular"].Urls[AccountUrlNames.PasswordReset] = "account/reset-password";
                options.Applications["Angular"].Urls[AccountUrlNames.EmailConfirmation] = "account/email-confirmation";
            });
        }
    
        private void ConfigureCache(IConfiguration configuration)
        {
            Configure<AbpDistributedCacheOptions>(options =>
            {
                options.KeyPrefix = "HangfireAuthTest:";
            });
        }
    
        private void ConfigureVirtualFileSystem(ServiceConfigurationContext context)
        {
            var hostingEnvironment = context.Services.GetHostingEnvironment();
    
            if (hostingEnvironment.IsDevelopment())
            {
                Configure<AbpVirtualFileSystemOptions>(options =>
                {
                    options.FileSets.ReplaceEmbeddedByPhysical<HangfireAuthTestDomainSharedModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}HangfireAuthTest.Domain.Shared", Path.DirectorySeparatorChar)));
                    options.FileSets.ReplaceEmbeddedByPhysical<HangfireAuthTestDomainModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}HangfireAuthTest.Domain", Path.DirectorySeparatorChar)));
                    options.FileSets.ReplaceEmbeddedByPhysical<HangfireAuthTestApplicationContractsModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}HangfireAuthTest.Application.Contracts", Path.DirectorySeparatorChar)));
                    options.FileSets.ReplaceEmbeddedByPhysical<HangfireAuthTestApplicationModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}HangfireAuthTest.Application", Path.DirectorySeparatorChar)));
                    options.FileSets.ReplaceEmbeddedByPhysical<HangfireAuthTestHttpApiModule>(Path.Combine(hostingEnvironment.ContentRootPath, string.Format("..{0}..{0}src{0}HangfireAuthTest.HttpApi", Path.DirectorySeparatorChar)));
                });
            }
        }
    
        private void ConfigureConventionalControllers()
        {
            Configure<AbpAspNetCoreMvcOptions>(options =>
            {
                options.ConventionalControllers.Create(typeof(HangfireAuthTestApplicationModule).Assembly);
            });
        }
    
        private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
        {
            context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.Authority = configuration["AuthServer:Authority"];
                    options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata");
                    options.Audience = "HangfireAuthTest";
                });
    
            context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
            {
                options.IsDynamicClaimsEnabled = true;
            });
        }
    
        private static void ConfigureSwagger(ServiceConfigurationContext context, IConfiguration configuration)
        {
            context.Services.AddAbpSwaggerGenWithOidc(
                configuration["AuthServer:Authority"]!,
                ["HangfireAuthTest"],
                [AbpSwaggerOidcFlows.AuthorizationCode],
                configuration["AuthServer:MetaAddress"],
                options =>
                {
                    options.SwaggerDoc("v1", new OpenApiInfo { Title = "HangfireAuthTest API", Version = "v1" });
                    options.DocInclusionPredicate((docName, description) => true);
                    options.CustomSchemaIds(type => type.FullName);
                });
        }
    
        private void ConfigureDataProtection(
            ServiceConfigurationContext context,
            IConfiguration configuration,
            IWebHostEnvironment hostingEnvironment)
        {
            var dataProtectionBuilder = context.Services.AddDataProtection().SetApplicationName("HangfireAuthTest");
            if (!hostingEnvironment.IsDevelopment())
            {
                var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!);
                dataProtectionBuilder.PersistKeysToStackExchangeRedis(redis, "HangfireAuthTest-Protection-Keys");
            }
        }
    
        private void ConfigureDistributedLocking(
                ServiceConfigurationContext context,
                IConfiguration configuration)
        {
            context.Services.AddSingleton<IDistributedLockProvider>(sp =>
            {
                var connection = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!);
                return new RedisDistributedSynchronizationProvider(connection.GetDatabase());
            });
        }
    
        private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration)
        {
            context.Services.AddCors(options =>
            {
                options.AddDefaultPolicy(builder =>
                {
                    builder
                        .WithOrigins(
                            configuration["App:CorsOrigins"]?
                                .Split(",", StringSplitOptions.RemoveEmptyEntries)
                                .Select(o => o.Trim().RemovePostFix("/"))
                                .ToArray() ?? Array.Empty<string>()
                        )
                        .WithAbpExposedHeaders()
                        .SetIsOriginAllowedToAllowWildcardSubdomains()
                        .AllowAnyHeader()
                        .AllowAnyMethod()
                        .AllowCredentials();
                });
            });
        }
    
        private void ConfigureExternalProviders(ServiceConfigurationContext context)
        {
            context.Services
                .AddDynamicExternalLoginProviderOptions<GoogleOptions>(
                    GoogleDefaults.AuthenticationScheme,
                    options =>
                    {
                        options.WithProperty(x => x.ClientId);
                        options.WithProperty(x => x.ClientSecret, isSecret: true);
                    }
                )
                .AddDynamicExternalLoginProviderOptions<MicrosoftAccountOptions>(
                    MicrosoftAccountDefaults.AuthenticationScheme,
                    options =>
                    {
                        options.WithProperty(x => x.ClientId);
                        options.WithProperty(x => x.ClientSecret, isSecret: true);
                    }
                )
                .AddDynamicExternalLoginProviderOptions<TwitterOptions>(
                    TwitterDefaults.AuthenticationScheme,
                    options =>
                    {
                        options.WithProperty(x => x.ConsumerKey);
                        options.WithProperty(x => x.ConsumerSecret, isSecret: true);
                    }
                );
        }
    
        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.UseUnitOfWork();
            app.UseDynamicClaims();
            app.UseAuthorization();
    
            app.UseSwagger();
            app.UseAbpSwaggerUI(options =>
            {
                options.SwaggerEndpoint("/swagger/v1/swagger.json", "HangfireAuthTest API");
    
                var configuration = context.GetConfiguration();
                options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
            });
            app.UseAuditing();
            app.UseAbpSerilogEnrichers();
    
    
    
    
            app.UseAbpHangfireDashboard("/hangfire", options =>
            {
                options.AsyncAuthorization = new[]
                {
                    new AbpHangfireAuthorizationFilter(false, requiredPermissionName: HangfireAuthTestPermissions.Hangfire)
                };
            });
    
    
            app.UseConfiguredEndpoints();
        }
    }
    

    HTTP Error 401 when trying to access /hangfire

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Your project only has the AddJwtBearer

    Please refer the code from https://abp.io/support/questions/5167/Hangfire-Authorization-Problem#answer-3a0b888e-b47a-d13d-c030-80ce3f046997

    private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
    {
        context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.Authority = configuration["AuthServer:Authority"];
                options.RequireHttpsMetadata = configuration.GetValue("AuthServer:RequireHttpsMetadata");
                options.Audience = "HangfireAuthTest";
            });
    
        context.Services.Configure(options =>
        {
            options.IsDynamicClaimsEnabled = true;
        });
    }
    
  • User Avatar
    0
    themisoft created

    Hi, switching to a Domain Service and getting a bit more guidance on the Authorization, i managed to fix my issues.

    Thank you!

Made with ❤️ on ABP v9.0.0-preview Updated on September 19, 2024, 10:13