Hangfire is one of the best background processing libraries for .NET. I recently needed to use it in a .NET 5.0 Web API application to schedule a recurring job and it was pretty straightforward and userful. The only challenge I faced, was to find a way to access Hangfire dashboard on the production environment.
Hangfire dashboard is accessible only on localhost by default and you need to implement sort of authorization on other environments. In my scenario my API authorization was implemented using AzureAD; the client app does the authentication operation and sends the JWT token in all requests to my REST API; in that case, I needed to find a way to show the Hangfire dashboard in the client app using the same authentication method.
Thankfully, Hangfire supports custom authorization filters; all steps I needed to follow were
- Implementing a custom
HangfireDashboardJwtAuthorizationFilter
inherited fromIDashboardAuthorizationFilter
- Extracting the JWT token passed to my Hangfire dashboard route defined in my Startup.cs (/hangfire in my case)
- Checking if the JWT token includes the required claims/roles in order to grant/deny access the dashboard.
- Setting the JWT in a cookie to be accessed by cunsecutive requests made by Hangfire dashboard.
On the client side, regardless of whatever you're using (pure HTML/JS, Angular, Vue, React, etc.), you just need to have a page with an IFRAME pointing to the /hangfire URL passing the obtained JWT as a query string.
And you're done! Let me show you the code!
HangfireDashboardJwtAuthorizationFilter.cs
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using Hangfire.Dashboard;
using Microsoft.AspNetCore.Http;
namespace Company.Api.Authorization
{
public class HangfireDashboardJwtAuthorizationFilter : IDashboardAuthorizationFilter
{
public HangfireDashboardJwtAuthorizationFilter() {}
private void SetCookie(string jwtToken)
{
httpContext.Response.Cookies.Append("_hangfireCookie",
jwtToken,
new CookieOptions()
{
Expires = DateTime.Now.AddMinutes(30)
});
}
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
var jwtToken = String.Empty;
if (httpContext.Request.Query.ContainsKey("jwt_token"))
{
jwtToken = httpContext.Request.Query["jwt_token"].FirstOrDefault();
SetCookie(jwt_token);
}
else
{
jwtToken = httpContext.Request.Cookies["_hangfireCookie"];
}
if (String.IsNullOrEmpty(jwtToken))
{
return false;
}
var handler = new JwtSecurityTokenHandler();
var jwtSecurityToken = handler.ReadJwtToken(jwtToken);
try
{
// Only authenticated users who have the required claim (AzureAD group in this demo) can access the dashboard.
return jwtSecurityToken.Claims.Any(t => t.Type == "groups" && t.Value == "SOME_VALUE");
}
catch (Exception exception)
{
throw exception;
}
}
}
}
Protecting Hangfire dashboard in Startup.cs
app.UseHangfireDashboard("/hangfire",
new DashboardOptions { Authorization = new [] { new HangfireDashboardJwtAuthorizationFilter() } });
Showing the /hangfire page in your client app using an IFRAME
<iframe :src="hangfireUrl" frameborder="0" width="100%" height="100%"></iframe>
The above is an IFRAME in a Vue.js view, the value hangfireUrl passed to the IFRAME src is the full URL of the Hangfire dashboard route in your API + "?jwt_token=XXXXX
" where XXXXX is the token your client app has already obtained from your authorization service (AzureAD in my demo).
Now whenever you open your client page in the browser, the inline IFRAME tries to open the actual Hangfire dashboard page in your Web API application and the passed value of jwt_token will help the custom authorization filter to authorize users with the required claims and show the dashboard or return a 401 http status.
Top comments (1)
For others who find their way here, this implementation of the HangfireAuthorizationFilter is missing token validation, so you've got to add that yourself to reject forged/manipulated tokens.