Valter
Valter

Reputation: 2899

Set up JWT Bearer Token Authorization/Authentication in Hangfire

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

Answers (2)

Zanyar Jalal
Zanyar Jalal

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

ShanieMoonlight
ShanieMoonlight

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

Related Questions