Hangfire dashboard access with JWT token authentication
While attempting to access the Hangfire dashboard of a ASP.NET Web API project recently, we realised we had been denied access.
Earlier this year we changed our development stack and began using Angular and ASP.NET Web API. Before this change, we were using ASP.NET MVC systems utilizing Razor views. We also use Hangfire for background processing, which allows us to offload certain processing to background threads. Once we had deployed our first project we realized that some of our Hangfire tasks weren’t completing and in attempting to debug the issue we tried to view the Hangfire dashboard. When we were greeted with a white page we realized that we did not have access to the dashboard. Our custom HangfireAuthorizationFilter was denying us access because it was still validating users against the HttpContext object.
Our API was using JWT bearer tokens for authorization, so we needed to take another look at our HangfireAuthorizationFilter logic.
We have no dynamic or static pages within our API project and therefore no traditional login page. We needed a way to get our bearer token decoded and verify the claims within the token. Our firsts thoughts were to pass the token through URL parameters. We realized that once this was implemented, it would only work for the initial page load; navigating through the dashboard would still deny us access whenever we clicked a link. It was at this point we decided to use a cookie which would be present for all requests. This cookie would be added to the browser using dev tools and would contain the JWT bearer token
The code within our custom HangfireAuthorizationFilter was modified as below
public bool Authorize([NotNull] DashboardContext context)
{
#if DEBUG
// If we are in debug, always allow Hangfire access.
return true;
#else
// if we have a cookies and we are in release mode
HttpCookieCollection cookies = System.Web.HttpContext.Current.Request.Cookies;
if (cookies["custom_cookie_name"] != null)
{
HttpCookie jwtCookie = cookies["custom_cookie_name"];
string jwtToken = jwtCookie.Value;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
JwtSecurityToken securityToken = handler.ReadToken(jwtToken) as JwtSecurityToken;
// return true or false based on the presence of a specific claim e.g role claim
// string role = securityToken.Claims.First(claim => claim.Type == "role").Value;
// return role == "THE_ROLE_WE_ARE_LOOKING_FOR";
}
return false;
#endif
}
On line 3 if we have built in Debug we always return true. Otherwise on line 6 we look for the presence of a specific cookie [cci]custom_cookie_name[/cci]. If this cookie is present we use its value (the bearer token) and look for the presence of a specific claim. Our authorization logic verifies that there is specific role claim present. If this role is present we return true, granting access, otherwise false which denies access.
It is within this code block where you would apply logic relevant to your code base.
In order to make this process work, we use Postman to create an API request to our token URL (e.g. [cci]http://systemurl/token[/cci]) and use the return bearer token as the value that our cookie contains.
Related
Originally published at dev.aerion.com.au on October 17, 2017.