Why is Hangfire requiring authentication to view dashboard - asp.net-mvc

I am running HangFire within my MVC web app but whenever I try to navigate to http://MyApp/hangfire, it redirects me to my app's login page as though I am not logged in.
I have not explicitly configured any requirements for authorization...e.g. I had the below in the web.config, but then took it out in attempts to get this to work.
<location path="hangfire">
<system.web>
<authorization>
<allow roles="Administrator" />
<deny users="*" />
</authorization>
</system.web>
In theory, this is what I'd want, and when I log into my main web application, I will be logged in with an Administrator role so this rule should work.
But whether I have that configured in the web.config or not, whenever I try to navigate to http://MyApp/hangfire, it redirects me to my apps login page as configured in the web.config:
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="960" />
</authentication>
It does NOT do this on my local machine, just when I publish to my host. Does HangFire not recognize the authentication cookie that my main app provides when I login? I thought in general, the hangfire app doesn't require authentication, so what other configuration could be thinking that it does?
UPDATE 1:
I added the authorization filters per the hangfire docs, but the same thing happens. Here is my code in Startup.cs:
using Hangfire;
using Hangfire.Logging;
using Hangfire.Dashboard;
using Hangfire.SqlServer;
using Microsoft.Owin;
using OTIS.Web.AppCode;
using OTISScheduler.AppServ;
using Owin;
using System.Web.Security;
[assembly: OwinStartup(typeof(OTIS.Web.App_Start.Startup))]
namespace OTIS.Web.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app) {
app.UseHangfire(config => {
config.UseSqlServerStorage("DefaultConnection");
config.UseServer();
//Dashboard authorization
config.UseAuthorizationFilters(new AuthorizationFilter
{
Users = "USERA", // allow only specified users (comma delimited list)
Roles = "Account Administrator, Administrator" // allow only specified roles(comma delimited list)
});
});
LogProvider.SetCurrentLogProvider(new StubLogProviderForHangfire());
GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 0 });
var scheduleTasksInitializer = new ScheduleTasksInitializer();
scheduleTasksInitializer.ScheduleTasks();
}
}
}
UPDATE 2:
Per the more detailed instructions showing basic authentication, I also tried this...still no luck..redirects me to my app's login page.
config.UseAuthorizationFilters(
new BasicAuthAuthorizationFilter(
new BasicAuthAuthorizationFilterOptions
{
// Require secure connection for dashboard
RequireSsl = false,
SslRedirect = false,
// Case sensitive login checking
LoginCaseSensitive = true,
// Users
Users = new[]
{
new BasicAuthAuthorizationUser
{
Login = "MyLogin",
// Password as plain text
PasswordClear = "MyPwd"
}
}
}));

With the newer versions you should use IDashboardAuthorizationFilter. With the using statements, it will look like this:
using System.Web;
using Hangfire.Annotations;
using Hangfire.Dashboard;
namespace Scheduler.Hangfire
{
public class HangFireAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize([NotNull] DashboardContext context)
{
//can add some more logic here...
return HttpContext.Current.User.Identity.IsAuthenticated;
//Can use this for NetCore
return context.GetHttpContext().User.Identity.IsAuthenticated;
}
}
}
then in the configuration section:
app.UseHangfireDashboard("/jobs", new DashboardOptions()
{
Authorization = new [] {new HangFireAuthorizationFilter()}
});

Finally got it working. I created my own AuthorizationFilter class (see below).
Then I passed that to the MapHangfireDashboard method in the Startup.cs Configuration method (see below that)
public class HangFireAuthorizationFilter : IAuthorizationFilter
{
public bool Authorize(IDictionary<string, object> owinEnvironment)
{
bool boolAuthorizeCurrentUserToAccessHangFireDashboard = false;
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if(HttpContext.Current.User.IsInRole("Account Administrator"))
boolAuthorizeCurrentUserToAccessHangFireDashboard = true;
}
return boolAuthorizeCurrentUserToAccessHangFireDashboard;
}
}
To map hangfire to a custom url and specify the AuthorizationFilter to use:
public void Configuration(IAppBuilder app) {
//Get from web.config to determine to fire up hangfire scheduler or not
app.UseHangfire(config => {
config.UseSqlServerStorage("DefaultConnection");
config.UseServer();
});
//map hangfire to a url and specify the authorization filter to use to allow access
app.MapHangfireDashboard("/Admin/jobs", new[] { new HangFireAuthorizationFilter() });
}

As designed I believe.
See the docs for the dashboard.
By default Hangfire allows access to Dashboard pages only for local requests.
Strangely enough I was dealing with this the other day and one thing to be aware of is that if you are using Autofac dependency injection then you need to make sure you configure items in the correct order. Specifically Hangfire after other authentication but also, in my case, MembershipReboot before the other OAuth stuff.
Took quite a bit of trial and error.

Related

Hangfire package in MVC on local IIS server responds with "The website declined to show this webpage" [duplicate]

I am running HangFire within my MVC web app but whenever I try to navigate to http://MyApp/hangfire, it redirects me to my app's login page as though I am not logged in.
I have not explicitly configured any requirements for authorization...e.g. I had the below in the web.config, but then took it out in attempts to get this to work.
<location path="hangfire">
<system.web>
<authorization>
<allow roles="Administrator" />
<deny users="*" />
</authorization>
</system.web>
In theory, this is what I'd want, and when I log into my main web application, I will be logged in with an Administrator role so this rule should work.
But whether I have that configured in the web.config or not, whenever I try to navigate to http://MyApp/hangfire, it redirects me to my apps login page as configured in the web.config:
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="960" />
</authentication>
It does NOT do this on my local machine, just when I publish to my host. Does HangFire not recognize the authentication cookie that my main app provides when I login? I thought in general, the hangfire app doesn't require authentication, so what other configuration could be thinking that it does?
UPDATE 1:
I added the authorization filters per the hangfire docs, but the same thing happens. Here is my code in Startup.cs:
using Hangfire;
using Hangfire.Logging;
using Hangfire.Dashboard;
using Hangfire.SqlServer;
using Microsoft.Owin;
using OTIS.Web.AppCode;
using OTISScheduler.AppServ;
using Owin;
using System.Web.Security;
[assembly: OwinStartup(typeof(OTIS.Web.App_Start.Startup))]
namespace OTIS.Web.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app) {
app.UseHangfire(config => {
config.UseSqlServerStorage("DefaultConnection");
config.UseServer();
//Dashboard authorization
config.UseAuthorizationFilters(new AuthorizationFilter
{
Users = "USERA", // allow only specified users (comma delimited list)
Roles = "Account Administrator, Administrator" // allow only specified roles(comma delimited list)
});
});
LogProvider.SetCurrentLogProvider(new StubLogProviderForHangfire());
GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 0 });
var scheduleTasksInitializer = new ScheduleTasksInitializer();
scheduleTasksInitializer.ScheduleTasks();
}
}
}
UPDATE 2:
Per the more detailed instructions showing basic authentication, I also tried this...still no luck..redirects me to my app's login page.
config.UseAuthorizationFilters(
new BasicAuthAuthorizationFilter(
new BasicAuthAuthorizationFilterOptions
{
// Require secure connection for dashboard
RequireSsl = false,
SslRedirect = false,
// Case sensitive login checking
LoginCaseSensitive = true,
// Users
Users = new[]
{
new BasicAuthAuthorizationUser
{
Login = "MyLogin",
// Password as plain text
PasswordClear = "MyPwd"
}
}
}));
With the newer versions you should use IDashboardAuthorizationFilter. With the using statements, it will look like this:
using System.Web;
using Hangfire.Annotations;
using Hangfire.Dashboard;
namespace Scheduler.Hangfire
{
public class HangFireAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize([NotNull] DashboardContext context)
{
//can add some more logic here...
return HttpContext.Current.User.Identity.IsAuthenticated;
//Can use this for NetCore
return context.GetHttpContext().User.Identity.IsAuthenticated;
}
}
}
then in the configuration section:
app.UseHangfireDashboard("/jobs", new DashboardOptions()
{
Authorization = new [] {new HangFireAuthorizationFilter()}
});
Finally got it working. I created my own AuthorizationFilter class (see below).
Then I passed that to the MapHangfireDashboard method in the Startup.cs Configuration method (see below that)
public class HangFireAuthorizationFilter : IAuthorizationFilter
{
public bool Authorize(IDictionary<string, object> owinEnvironment)
{
bool boolAuthorizeCurrentUserToAccessHangFireDashboard = false;
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if(HttpContext.Current.User.IsInRole("Account Administrator"))
boolAuthorizeCurrentUserToAccessHangFireDashboard = true;
}
return boolAuthorizeCurrentUserToAccessHangFireDashboard;
}
}
To map hangfire to a custom url and specify the AuthorizationFilter to use:
public void Configuration(IAppBuilder app) {
//Get from web.config to determine to fire up hangfire scheduler or not
app.UseHangfire(config => {
config.UseSqlServerStorage("DefaultConnection");
config.UseServer();
});
//map hangfire to a url and specify the authorization filter to use to allow access
app.MapHangfireDashboard("/Admin/jobs", new[] { new HangFireAuthorizationFilter() });
}
As designed I believe.
See the docs for the dashboard.
By default Hangfire allows access to Dashboard pages only for local requests.
Strangely enough I was dealing with this the other day and one thing to be aware of is that if you are using Autofac dependency injection then you need to make sure you configure items in the correct order. Specifically Hangfire after other authentication but also, in my case, MembershipReboot before the other OAuth stuff.
Took quite a bit of trial and error.

wrong user/password didn't checked at AD

I have created an MVC 5 Application with Windows Authentication,
<authentication mode="Windows" />
<authorization>
<deny users="?" />
</authorization>
I have below code to get user's Display name along with I also want to do validation,
protected void Session_Start(object sender, EventArgs e)
{
if (Context.User != null)
{
MapUserADDetails(Context.User);
}
}
private void MapUserADDetails(IPrincipal user)
{
using (HostingEnvironment.Impersonate())
using (var domain = new PrincipalContext(ContextType.Domain, "test.com"))
using (var usr = UserPrincipal.FindByIdentity(domain, user.Identity.Name))
{
if (usr == null)
{
return;
}
Session.Add("UserDisplayName", usr.DisplayName);
}
}
Now I am hosted this app to IIS with only windows authentication enabled. When I am browsing it, it's prompt for userName and Password,
Question,
Even I am entering wrong username/password or even doesn't fill anything, it's able to fetch Display Name.
How to restrict this? User/Pass must be validate against the AD. Please suggest. Thanks!
It sounds as IIS configuration issue and not the code.
To troubleshoot:
check if IE behaves differently
make sure that IIS has only Windows authentication enabled and not e.g. anonymous (see Receiving login prompt using integrated windows authentication)
make sure that the page has no other resources (e.g. images) used from other location that requires authentication (maybe that prompt is not for the page but for resources embedded into it)
check browser settings (e.g. in IE that site might need to be added into Intranet Zone, or "Automatically logon with current username and password" is not enabled)
You're not actually validating any username/password combination. UserPrincipal.FindByIdentity only checks if the user is found in AD.
To validate user credentials, you would need to check:
using (var domain = new PrincipalContext(ContextType.Domain, "test.com"))
{
bool authenticated = domain.ValidateCredentials(user.Identity.Name, password);
if (!authenticated)
{
// Do stuff
}
}
You can check MSDN for more info.

ASP MVC Single Application Multi-Domain Authentication

I have the following strict scenario specifically required by a client: A single website using Asp.NET MVC4 which is accessible via various domains with Single-Sign On mechanism.
I have managed to make form authentication work with subdomains by specifying in the webconfig the second-level domain
<authentication mode="Forms">
<forms name="SingleSignOn" loginUrl="/Login/LoginRedirect" timeout="10" slidingExpiration="false" domain="domain.ml" cookieless="UseCookies" enableCrossAppRedirects="true">
<credentials passwordFormat="SHA1" />
</forms>
</authentication>
Also when calling the FormsAuthentication.SetAuthCookie in the login logic, I am specifying the second level domain as well:
System.Web.HttpCookie MyCookie = System.Web.Security.FormsAuthentication.GetAuthCookie(lName, false);
MyCookie.Domain = lSecondLevelDomain;
FormsAuthentication.SetAuthCookie(lName, false);
Across different domains, this does not work, since the actual domain will not match with the domain specified in the web.config and neither with the cookies.
The aim is:
User accesses domain1.com
User redirected to logindomain.com and authenticated cookie created
User redirected back to domain1.com
The user is always redirected to a "login domain", the cookie is created using that domain, and always authenticate using the same cookie across domains.
Is it possible to override the logic of the Authorize attribute in order to allow authorization using the cookie of the login domain instead of the domain the user originally used?
Before diving into programming, take a look at How does SO's new auto-login feature work? to understand how to implement such this scenarios.
Then take a look at Forms Authentication Across Applications and Single Sign On (SSO) for cross-domain ASP.NET applications. Now you can meet your purpose as you want :)
You can also use the following code if you strongly consider the validity of the resultant absolute returned URL:
public class Startup {
public void Configuration(IAppBuilder app) {
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationMode = AuthenticationMode.Active,
LoginPath = new PathString("/account/login"),
LogoutPath = new PathString("/account/logout"),
Provider = new CookieAuthenticationProvider { OnApplyRedirect = ApplyRedirect },
});
}
private static void ApplyRedirect(CookieApplyRedirectContext context) {
Uri absoluteUri;
if (Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out absoluteUri)) {
var path = PathString.FromUriComponent(absoluteUri);
if (path == context.OwinContext.Request.PathBase + context.Options.LoginPath)
context.RedirectUri = "http://accounts.domain.com/login" +
new QueryString(
context.Options.ReturnUrlParameter,
context.Request.Uri.AbsoluteUri);
// or use context.Request.PathBase + context.Request.Path + context.Request.QueryString
}
context.Response.Redirect(context.RedirectUri);
}
}

Why isn't my authentication cookie being set in MVC 4?

I've got an MVC4 project that I'm working on. When a user's login credentials are valid, I call FormsAuthentication.SetAuthCookie() to indicate that the user is logged in. (I have it wrapped in a class so I can mock the Interface for my unit tests.)
namespace FlashMercy.Shared.Security
{
using System;
using System.Web.Security;
public class Auth : IAuth
{
public void SetAuthCookie(string userId, bool remember)
{
FormsAuthentication.SetAuthCookie(userId, remember);
}
public void Signout()
{
FormsAuthentication.SignOut();
}
}
}
In the debugger, I can confirm that the .SetAuthCookie(userId, remember) line is executing, and userId is populated.
Then, I have a custom authorize attribute to check that the user is logged in:
namespace FlashMercy.Shared.Security
{
using System.Web.Mvc;
public class FlashMercyAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new RedirectResult("/");
}
}
}
}
When I debug the application, the filterContext.HttpContext.User.Identity.IsAuthenticated is false even after I've supposedly set the auth cookie. Also, filterContext.HttpContext.User.Identity.Name is empty. I'm not sure what I'm missing here.
Update
If you care to look at the whole source, it's available on GitHub: https://github.com/quakkels/flashmercy.
Problem with your code is that you are using FormsAuthentication, but you didn't add it to web.config. Your web.config should have such section:
<system.web>
<authentication mode="Forms"></authentication>
...
</system.web>
Based on this Mode Asp.Net understand what authentication mode it should use, e.g. Forms, Windows, etc. And without settings it to Forms value - FormsAuthenticationModule just ignores .ASPXAUTH cookie from the request.
PS. I've downloaded your code, and with correct authentication section in web.config it works fine and updates HttpContext.User.Identity.IsAuthenticated to true.
The problem is that you only set the authentication cookie but do not have anything that load it.
It's forms authentication that uses that cookie. So you either have to activate forms authentication or you'll have to load it yourself.
filterContext.HttpContext.User.Identity.IsAuthenticated is false even after I've supposedly set the auth cookie.
This will always be the case if you do not redirect after SetAuthCookie(). The ASP.Net pipeline is in charge of authorizing the user (most of the time before we write code) in the AuthenticateRequest. Setting a Cookie does not update the current User.Identity, this requires code that has already been executed. Just make sure anytime you SetAuthCookie() you immediately redirect (server side is fine) to another URL (probably should anyway, its a good way to seperate logging in a user, and what they should do next SRP).

asp.net mvc - limit access to web pages

Greetings,
in my asp.net mvc application what i would like to do is to enable access to some pages only after user was successfully authorized. I have already created custom membership provider and that works fine. How can I, in web config create such rule - for instance for all pages in ~Admin/ folder? I don't want to create on every controller's action the validation code.
For now i have in my web.config the following statement:
<location path="~/Admin">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
but it doesn't work.
Doing authorization logic in config files has one big disadvantage: it cannot be easily unit tested, and something so important as authentication should be unit tested. I would recommend you for this matter to write a custom authorization filter which could be used to decorate a base controller for all admin actions that requires authentication:
[AttributeUsage(
AttributeTargets.Method | AttributeTargets.Class,
Inherited = true
)]
public class RequiresAuthenticationAttribute
: FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new RedirectResult(
string.Format("{0}?ReturnUrl={1}",
FormsAuthentication.LoginUrl,
filterContext.HttpContext.Request.Url.AbsoluteUri
)
);
}
}
}
And your admin controller:
[RequiresAuthentication]
public class AdminController : Controller
{
// .. some actions that require authorized access
}

Resources