Reputation: 2899
How can you configure Bearer Token Authorization/Authentication in Hangfire?
I have a custom authentication filter that read the Authentication Token on the initial request but all other requests (Hangfire calls) it return 401.
How can I attach Auth Token to the header of every request that Hangfire does?
How can I refresh the token when it is expired?
Upvotes: 16
Views: 4936
Reputation: 1884
To set up JWT Bearer Token Authorization/Authentication in Hangfire using a custom filter, you can create a class that implements the IDashboardAuthorizationFilter
interface.
public class HangFireAuthorizationFilter : IDashboardAuthorizationFilter
{
private readonly JwtSettings settings;
public HangFireAuthorizationFilter(JwtSettings jwtSettings)
{
settings = jwtSettings;
}
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
var cookies = httpContext.Request.Cookies;
var token = httpContext.Request.Query["access_token"].ToString();
// Check if the token is present in the query or cookies
if (string.IsNullOrEmpty(token))
{
if (cookies.ContainsKey(WebsiteConstants.HangfireAuthCookieName))
token = cookies[WebsiteConstants.HangfireAuthCookieName];
}
else
{
// If token is found in the query, set it in cookies for future use
httpContext.Response.Cookies.Append(
WebsiteConstants.HangfireAuthCookieName,
token,
new CookieOptions { Expires = DateTime.Now.AddMinutes(10) });
}
// Validate the token if it's available
return !string.IsNullOrEmpty(token) && IsAuthenticated(token);
}
private bool IsAuthenticated(string token)
{
try
{
// Define the token validation parameters
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.Zero,
ValidIssuer = settings.Issuer,
ValidAudience = settings.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Base64UrlEncoder.DecodeBytes(settings.Key))
};
var tokenHandler = new JwtSecurityTokenHandler();
var principal = tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken);
// Check if the token was validated
return validatedToken != null && principal.Identity is { IsAuthenticated: true };
}
catch (Exception)
{
return false;
}
}
}
Setup action filter in Program.cs
app.UseHangfireDashboard(WebsiteConstants.HangfireUrl,
new DashboardOptions()
{
DashboardTitle = "CRM.JihanyKeshmir Hangfire Dashboard",
IsReadOnlyFunc = _ => hangfireSettings.IsReadOnly,
AppPath = hangfireSettings.AppPath,
DarkModeEnabled = hangfireSettings.DarkModeEnabled,
Authorization = new [] { new HangFireAuthorizationFilter(/* pass settings in here */) }
});
Upvotes: 0
Reputation: 1861
Maybe a bit late but here's a possible solution. The idea comes from this post: https://discuss.hangfire.io/t/using-bearer-auth-token/2166
The basic idea is to add your jwt as a query param then collect it in JwtBearerOptions.Events and set your MessageReceivedContext.Token equal to it. This will work for the first request but the requests that follow from it won't have the query param attached so we need to add the jwt to a cookie when we get it. So now we check for the jwt in the query param. If we find it then add it to a cookie. If not check for it in the cookies. In ConfigureServices:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer((Action<JwtBearerOptions>)(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters
{
LifetimeValidator = (before, expires, token, param) =>
{
return expires > DateTime.UtcNow;
},
IssuerSigningKey = JwtSettings.SecurityKey,
ValidIssuer = JwtSettings.TOKEN_ISSUER,
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = false,
NameClaimType = GGClaimTypes.NAME
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = mrCtx =>
{
// Look for HangFire stuff
var path = mrCtx.Request.Path.HasValue ? mrCtx.Request.Path.Value : "";
var pathBase = mrCtx.Request.PathBase.HasValue ? mrCtx.Request.PathBase.Value : path;
var isFromHangFire = path.StartsWith(WebsiteConstants.HANG_FIRE_URL) || pathBase.StartsWith(WebsiteConstants.HANG_FIRE_URL);
//If it's HangFire look for token.
if (isFromHangFire)
{
if (mrCtx.Request.Query.ContainsKey("tkn"))
{
//If we find token add it to the response cookies
mrCtx.Token = mrCtx.Request.Query["tkn"];
mrCtx.HttpContext.Response.Cookies
.Append("HangFireCookie",
mrCtx.Token,
new CookieOptions()
{
Expires = DateTime.Now.AddMinutes(10)
});
}
else
{
//Check if we have a cookie from the previous request.
var cookies = mrCtx.Request.Cookies;
if (cookies.ContainsKey("HangFireCookie"))
mrCtx.Token = cookies["HangFireCookie"];
}//Else
}//If
return Task.CompletedTask;
}
};
}));
HangFire Auth Filter:
public class HangFireAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
var httpCtx = context.GetHttpContext();
// Allow all authenticated users to see the Dashboard.
return httpCtx.User.Identity.IsAuthenticated;
}//Authorize
}//Cls
Upvotes: 2