Abp Version 1.3.1
Hangfire version 1.6.12
Using the following code does not allow me to see the dashboard page. The abp auth token is being sent with the request but I always get a 401. The user I am logged in with does have the correct permissions.
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new AbpHangfireAuthorizationFilter(AppPermissions.Pages_Administration_HangfireDashboard) }
});
Are you using cookie auth or token based auth?
Token based auth.
@evannielsen How do you send token to hangfire ?
@emaish I suppose that is the part that I am missing. I just log in to the app and then navigate to /hangfire. Should I be doing something else?
/hangfire is different mvc controller and when you navigate to it you don't pass token authentication...
You should login with Form Authentication, or Create custom OAuthBearerAuthenticationProvider to support cookie as described bellow
Step1: Change Authenticate method in Account Web APi Controller to create a cookie
```C#
[HttpPost]
public async Task
{
CheckModelState();
var loginResult = await GetLoginResultAsync(
loginModel.UsernameOrEmailAddress,
loginModel.Password,
loginModel.TenancyName
);
var ticket = new AuthenticationTicket(loginResult.Identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = System.DateTime.MaxValue.ToUniversalTime();
string bearerToken = OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
AjaxResponse ajaxResponse = new AjaxResponse
{
Success = true,
Result = bearerToken
};
var response = ActionContext.Request.CreateResponse(System.Net.HttpStatusCode.OK, ajaxResponse);
// Create cookie and add it to the response header
var cookie = new CookieHeaderValue("YourBearerTokenCookieName", bearerToken);
cookie.Expires = System.DateTime.MaxValue.ToUniversalTime();
cookie.Path = "/";
response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
return response;
}
**Step 2:** Create custom OAuthBearerAuthenticationProvider to support cookie
```C#
public class ApplicationOAuthBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
if (context == null) throw new ArgumentNullException("context");
// try to find bearer token in a cookie
// (by default OAuthBearerAuthenticationHandler only checks Authorization header)
var tokenCookie = context.OwinContext.Request.Cookies["YourBearerTokenCookieName"];
if (!string.IsNullOrEmpty(tokenCookie))
context.Token = tokenCookie;
return Task.FromResult<object>(null);
}
}
Step 2: use your custom OAuthBearerAuthenticationProvider in the Account Web APi Controller
```C#
static AccountController()
{
//Use default OAuthBearerAuthenticationProvider which only check header for the token
//OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
//user out custom OAuthBearerAuthenticationProvider which check header or cookie named YourBearerTokenCookieName
OAuthBearerOptions = new OAuthBearerAuthenticationOptions
{
Provider = new ApplicationOAuthBearerAuthenticationProvider(),
};
}
```
Remember to remove the cookie when user log out
I dont have the AccountController but I do have the TokenAuthController. How would I make it work with that?
You may want to ask this question to Hangfire's Github issues: How to authorize dashboard using token based authentication.
I know this post is closed - but i would be interested in knowing what is the recommended approach here. Having a project that is based off the Ng2 Core template I have an ng2 app and a Web.Host (api). The api has hangfire enabled. But by default there is only teh TokenAuthController available in the api for issuing tokens (only to the ng2 app at the moment). Is it best to introduce Mvc controllers to allow login to the api to allow a user to authenticate so they can then access the hangfire dashboard?
Any guidance would be appreciated.
I personally haven't worked on that so don't know the best approach. Did you asked that to Hangfire? Maybe they have a solution.
@evannielsen @oakuraape
You can do it with cookie authentication middleware.
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "YourCookieInstanceName",
AutomaticAuthenticate = true
});
app.UseHangfireServer();
app.UseHangfireDashboard("", new DashboardOptions()
{
Authorization = new[] { new HangfireDashboardAuthorizationFilter(PermissionNames.Administration) },
AppPath = ""
});
public class HangfireDashboardAuthorizationFilter : AbpHangfireAuthorizationFilter
{
public HangfireDashboardAuthorizationFilter(string requiredPermissionName = null)
: base(requiredPermissionName)
{
}
public bool Authorize([NotNull] DashboardContext context)
{
if (base.Authorize(context))
{
context.GetHttpContext().Authentication.SignInAsync(
"YourCookieInstanceName",
context.GetHttpContext().User,
new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(30)
});
return true;
}
return false;
}
}
You can do it with cookie authentication middleware.
ApplicationBuilder
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "YourCookieInstanceName",
AutomaticAuthenticate = true
});
This code is deprecated and does not work. Any other ideas for adding the dashboard to an angular solution?
@bobingham
Startup.Configure
app.UseAuthentication();
app.UseAuthenticationMiddleware("AuthenticationScheme");
Startup.ConfigureServices
services.AddAuthentication("AuthenticationScheme")
.AddCookie("YourCookieInstanceName");
HangfireDashboardAuthorizationFilter
public class HangfireDashboardAuthorizationFilter : AbpHangfireAuthorizationFilter, IDashboardAuthorizationFilter
{
public HangfireDashboardAuthorizationFilter(string requiredPermissionName = null)
: base(requiredPermissionName)
{
}
public bool Authorize([NotNull]DashboardContext context)
{
if (base.Authorize(context))
{
AsyncHelper.RunSync(() => context.GetHttpContext().SignInAsync(
"YourCookieInstanceName",
context.GetHttpContext().User,
new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(30)
}));
return true;
}
return false;
}
}
@Caskia , thanks for this. Sorry to be a pain in the ass....
> Startup.Configure
>
> app.UseAuthentication();
> app.UseAuthenticationMiddleware("AuthenticationScheme");
IApplicationBuilder does not contain a definition for UseAuthenticationMiddleware ...
And where in Zero do you place HangfireDashboardAuthorizationFilter?
I've tried a number of different scenarios here and still no luck. I have stumbled my way through a couple things though:
1) I am not using cookie authentication, I am using the WebAPI so shoving cookie authentication also into the already implemented JWT seemed like a hack.
2) I could not, for the life of me, get the AuthorizationFilter to work when I was inheriting from AbpHangfireAuthorizationFilter. However, if I inherit from IDashboardAuthorizationFilter it seem to call the filter perfect.
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new HangfireAuthorizationFilter() }
});
This gets me real close. However, the current problem I am facing is that within the Authorize method, none of the suggested methods get me access to proper permissions. I have also tried grabbing the AbpSession information manually, and still no love.
public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter //AbpHangfireAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
using (var session = IocManager.Instance.ResolveAsDisposable<IAbpSession>())
{
using (var permissionChecker = IocManager.Instance.ResolveAsDisposable<IPermissionChecker>())
{
var auth = permissionChecker.Object.IsGranted(PermissionNames.HangfireDashboard);
return auth;
}
}
}
}
I'm betting these two problems are related: permissionChecker fails, session info is empty, because I am not inheriting from the documented AbpHangfireAuthorizationFilter class. But I feel like I am close. All I know is that if I follow the directions on the Abp Hangfire documentation page to the letter, it doesn't quite get us where we need to be. If you just shove the permission names within the Hangfire initializer, then you need to have cookies turned on, which for the web api doesn't make any sense, so the filter is the only reasonable way to go. However, as I mentioned, it doesn't seem to work as documented.
Is there a simple Hangfire sample using the Abp WebAPI and JWT out there somewhere?
hi @jasenf
1.I am not using cookie authentication, I am using the WebAPI so adding shoving cookie authentication in above the already implemented JWT seemed like a hack.
Identity will automatically add the authorization of the cookie.
https://github.com/aspnet/Identity/blob/8ef14785a4a1e416189ca1137eb13f43c2f4349d/src/Identity/IdentityServiceCollectionExtensions.cs#L38
This is my practice, for reference only.
Use razor pages to write a simple login function page.
Then use cookie authorization.
Now you can access the hangfire page that requires authorization.
```c#
public async Task
{
using (var uow = _unitOfWorkManager.Begin())
{
var loginResult = await _logInManager.LoginAsync("admin", "qq1234", "Default");
if (loginResult.Result == AbpLoginResultType.Success)
{
await _signInManager.SignInAsync(loginResult.User, true);
}
uow.Complete();
return Page();
}
}
```
Hi @maliming,
Thanks for the reply. So you are suggesting that even if your server is a pure web api, add a "backdoor" login page, implement cookies, and then just use the standard dashboard filter?
Have you done this successfully?
I am doing this now and succeeding, if there is no login information under the filter, you can try:
c#
var cookieAuthenticateResult = AsyncHelper.RunSync(() =>
聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽 context.GetHttpContext().AuthenticateAsync(IdentityConstants.ApplicationScheme));
Because hangfire dashboard is also an html page. The browser does not support setting the Token of the request header.
I don't think this is a backdoor, because the login has logical verification, you can add a verification code, etc.
Jeepers creepers, I think I'll leave this to the guys at Volosoft! @jasenf - thanks for the effort, it seems you are quite close. @maliming - it seems you have it working but with my limited coding ability your instructions point the way to what is probably going to be a lot of pain for very little gain! Thanks both for your input.
More hours dedicated to this and absolutely no love.
Cookie authentication added to project. Razor login page built. Authentication working great, a new Aspnet authentication cookie is returned.
Still within the AuthenticationFilter no Identity, no Claims are available or found.
Could some PLEASE post an Abp DotNet core project where the hangfire dashboard is available only when authenticated. This is incredibly frustrating.
@jasenf - did you take a look at this... (I'm not sure it will help but then again....).
hangfire-dashboard-with-jwt-token-authentication
@bobingham -- this worked perfect! exactly what was needed. thanks!
tips: remove all attempts to use cookies. don't bother with AbpHangfireAuthorizationFilter, use
just create a role, in Configure use:
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new HangfireAuthorizationFilter() }
});
and create your HangfireAuthorizationFilter something like
public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter //AbpHangfireAuthorizationFilter doesn't work
{
public bool Authorize(DashboardContext context)
{
// if we have a cookies and we are in release mode
var cookies = context.GetHttpContext().Request.Cookies;
if (cookies["Abp.AuthToken"] != null)
{
var jwtCookie = cookies["Abp.AuthToken"];
string jwtToken = jwtCookie;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
JwtSecurityToken securityToken = handler.ReadToken(jwtToken) as JwtSecurityToken;
var roles = securityToken.Claims.Where(claim => claim.Type.EndsWith("role")).ToList();
return roles.First(claim => claim.Value.ToLower() == "super") != null;
}
return false;
}
}
@jasenf - glad to be of help. It's something I've been trying to do but just gave up because I'm not the best coder and I have other priorities. Two questions (because I'm lazy); where do you create the HangfireAuthorizationFilter and did you manage to add this to the angular router and/or did you use a href inside a menu item? Could you do me a favour and share some code (again)....
What I shared above is the entire file. Just put it in your web.core project and it will be accessible during your startup config.
I'm not using Angular so that is literally all I had to do to make this work (other than create the super role of course).
@bobingham
UseAuthenticationMiddleware
public static class AuthenticationMiddleware
{
public static IApplicationBuilder UseAuthenticationMiddleware(this IApplicationBuilder app, string authenticationScheme)
{
return app.Use(async (ctx, next) =>
{
if (ctx.User.Identity?.IsAuthenticated != true)
{
var result = await ctx.AuthenticateAsync(authenticationScheme);
if (result.Succeeded && result.Principal != null)
{
ctx.User = result.Principal;
}
}
await next();
});
}
}
You need to place HangfireDashboardAuthorizationFilter in application configuration.
app.UseHangfireDashboard("", new DashboardOptions()
{
Authorization = new[] { new HangfireDashboardAuthorizationFilter(PermissionNames.Administration) },
AppPath = ""
});
A more flexible soulition
public class HangfireTokenAuthorizationFilter : IDashboardAuthorizationFilter
{
private readonly string _requiredPermissionName;
public HangfireTokenAuthorizationFilter(string requiredPermissionName = null)
{
_requiredPermissionName = requiredPermissionName;
}
public bool Authorize([NotNull]DashboardContext context)
{
// if we have a cookies and we are in release mode
var cookies = context.GetHttpContext().Request.Cookies;
if (cookies["Abp.AuthToken"] != null)
{
var jwtCookie = cookies["Abp.AuthToken"];
string jwtToken = jwtCookie;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
JwtSecurityToken securityToken = handler.ReadToken(jwtToken) as JwtSecurityToken;
var id = securityToken.Claims.FirstOrDefault(claim => claim.Type.EndsWith("sub"));
var tenanIdClaim = securityToken.Claims.FirstOrDefault(claim => claim.Type.EndsWith("tenantId"));
int? tenanId = null;
if (tenanIdClaim != null)
{
tenanId = int.Parse(tenanIdClaim.Value);
}
return IsPermissionGranted(context, _requiredPermissionName, new UserIdentifier(tenanId, long.Parse(id.Value)));
}
return false;
}
private static bool IsPermissionGranted(DashboardContext context, string requiredPermissionName, UserIdentifier userIdentifier)
{
var permissionChecker = context.GetHttpContext().RequestServices.GetRequiredService<IPermissionChecker>();
return permissionChecker.IsGranted(userIdentifier, requiredPermissionName);
}
}
and:
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new HangfireTokenAuthorizationFilter(AppPermissions.Pages_Administration_HangfireDashboard) }
});
Though using cookie can get token information which can sole the issue but this solution is unsafe, is anyone have solution that can get user information from DashboardContext?
@XiaotongZhao best option we can find is using cookie based auth.
@bobingham -- this worked perfect! exactly what was needed. thanks!
tips: remove all attempts to use cookies. don't bother with AbpHangfireAuthorizationFilter, use
just create a role, in Configure use:app.UseHangfireDashboard("/hangfire", new DashboardOptions { Authorization = new[] { new HangfireAuthorizationFilter() } });and create your HangfireAuthorizationFilter something like
public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter //AbpHangfireAuthorizationFilter doesn't work { public bool Authorize(DashboardContext context) { // if we have a cookies and we are in release mode var cookies = context.GetHttpContext().Request.Cookies; if (cookies["Abp.AuthToken"] != null) { var jwtCookie = cookies["Abp.AuthToken"]; string jwtToken = jwtCookie; JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler(); JwtSecurityToken securityToken = handler.ReadToken(jwtToken) as JwtSecurityToken; var roles = securityToken.Claims.Where(claim => claim.Type.EndsWith("role")).ToList(); return roles.First(claim => claim.Value.ToLower() == "super") != null; } return false; } }
I am using the same method but my 'Abp.AuthToken' is empty while calling /hangfire from API .
Could anyone advice why is the cookie not set or not passing on hangfire request. I assume we are setting this after login to api on abp.swagger.js .
Please refer the following if it helps.
@retribe if you are also using ASP.NET Core & Angular, you can follow suggestions on https://support.aspnetzero.com/QA/Questions/7817
@retribe if you are also using ASP.NET Core & Angular, you can follow suggestions on https://support.aspnetzero.com/QA/Questions/7817
I am trying to access /hangfire from api url and there is no angular project used for this. Leveraging the login feature on swagger to authenticate and set token as cookie which will be used on custom authorization of /hangfire. Here the token is not passed with the hangfire request.
Most helpful comment
@bobingham -- this worked perfect! exactly what was needed. thanks!
tips: remove all attempts to use cookies. don't bother with AbpHangfireAuthorizationFilter, use
just create a role, in Configure use:
and create your HangfireAuthorizationFilter something like