ASP.NET MVC - Allow internal anonymous users, require windows authentication on external - asp.net-mvc

Is it possible to setup Authorization based on the zone of the request? Basically it is an intranet type application, with only little sensitive information.
If the request is performed from within the organization, it is fine to allow anonymous users.
However if it is an external request, they should get the 401 Authorization challenge.
External requests are coming from a single firewall, so an IP/IP range should be fine to specify if it is an external or internal request.
Currently it is configured for Windows authentication in the web.config file.
<authentication mode="Windows" />
<authorization>
<deny users="?" />
</authorization>

It would be easier to handle this rule directly at your firewall.
As an alternative you could configure IP Security at your IIS level and filter by client IP.
But if you have no control over the firewall you could write a custom Authorize attribute that will check the incoming IP address and allow/deny the request:
public class IpBasedAuthorizeAttribute: AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var ip = httpContext.Request.UserHostAddress;
return IsAllowed(ip);
}
private bool IsAllowed(string ip)
{
// TODO: do your checks here and return true or false
// depending on whether the IP address is allowed to
// access the application or not
throw new NotImplementedException();
}
}
and then you could either decorate individual controllers/actions with this attribute or register it as a global authorization attribute if you want it to apply to all requests:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new IpBasedAuthorizeAttribute());
}

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.

ApiController with CORS does not like Anonymous requests

I'm hosting an ASP.NET MVC project in Azure web apps.
In this project I'm using an ApiController to serve data to a client program.
This Api controller has a method defined as such:
[AllowAnonymous]
[RoutePrefix("api/v1/search")]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class CompanyDataController : APIController
{
[Route("companies")]
public string CompanySearch(string request)
{
return "well hello there beautiful";
}
}
When I try to make requests to this controller after its been published to Azure I get this error:
"The HTTP request was forbidden with client authentication scheme 'Anonymous'."
I can access the rest of the website without issue.
I have tried to allow anonymous access with a few variations of this in <system.web>:
<authentication mode="None" />
<authorization>
<allow users="?"/>
</authorization>
But that has made no difference.. any bright ideas?
From the link I posted in comments above... (wouldn't let me mark this as duplicate since the answer below wasn't marked as an answer!)
Since there is already an existing custom authorization filter on the class/controller level, therefore, to override a specific action handler (the method) and have it work without any authorization filters, we need to override the filter at the controller/class level. So adding the OverrideAuthorization filter did the trick. Now AllowAnonymous will be to do its magic.
[Route("api/accounts/{accountNumber}/GetX")]
[AllowAnonymous]
[OverrideAuthorization]
[HttpGet]
public HttpResponseMessage GetX(string accountNumber)
{
// Process
// ..
// ..
}

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.

How to intercept a non SSL connection in onAuthorization method?

I am using MVC4, ASP.NET 4.5, C#
I want to add code to my onAuthorization method in global.asa to identify whether the connection is SSL or not, if not then to issue a permanent redirect to a SSL domain. I am using another domain that is SSLed.
In my Login controller I have code along the lines of :
[HttpGet]
public virtual ActionResult LogOn(string Error="")
{
if (Request.IsSecureConnection)
{
return View(viewModel);
}
else
{
return RedirectPermanent("https://www.mysslapp.com/logon");
}
}
I want to add this same functionality to the onAuthorization method so that when actions, covered by the [authorize] filter are called then they must also be accessed by a SSL connection. So I believe my global.asa code needs changing. However it will not accept "Request.IsSecureConnection", as the context is different.
My "pseudo" Global.asa onAuthorization routine is:
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (Request.IsSecureConnection)
{
base.OnAuthorization(filterContext);
}
else
{
RedirectPermanent("https://www.mysslapp.com/logon");
}
}
The above will not work, but it describes what I am trying to achieve. I would appreciate help on how I need to change the above code to make it work, such that any "adventurous" use of urls, on a non SSL connection will automatically redirect to the SSL site logon page.
Thanks in advance.
EDIT1
Think I have the first bit:
filterContext.HttpContext.Request.IsSecureConnection
EDIT2
if (filterContext.HttpContext.Request.IsSecureConnection)
{
base.OnAuthorization(filterContext);
}
else
{
filterContext.Result = new RedirectResult("https://www.mysslapp.com");
}
I think your main issue is that you have 2 separate concerns and you are trying to achieve both in one go. Your 2 concerns are:
Making every URL of domain A 301 redirect to the same URL on domain B
Making domain B redirect all requests to HTTPS
The first one is really easy. Create a new IIS site for domain A, install the IIS rewrite module, add this web.config to the site, and then adjust your DNS (if necessary) to make the site live.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<httpRedirect
enabled="true"
destination="https://www.mysslapp.com$V$Q"
exactDestination="true"
httpResponseStatus="Permanent" />
<httpProtocol>
<redirectHeaders>
<!-- This is to ensure that clients don't cache the 301 itself -
this is dangerous because the 301 can't change when put in place
once it is cached -->
<add name="Cache-Control" value="no-cache"/>
</redirectHeaders>
</httpProtocol>
</system.webServer>
</configuration>
NOTE: The above configuration is for IIS 7.5. I am not sure if it will work on other versions of IIS.
Now none of the users of domain B will incur the performance hit of the redirect rule, so all is good.
For redirecting your users of domain B to HTTPS, you should not use 301. Why? Because not all browsers respond to 301.
You should also not only allow HTTPS on the domain, but allow both HTTP and HTTPS. Why? Because your users that type myssldomain.com will get an ugly error message instead of a fast redirect to you HTTPS protected site.
So the simple solution to making your whole site redirect to HTTPS is to use the RequireHttps attribute and register it as a global filter. The RequireHttpsAttribute uses a 302 redirect when a request comes in that is not secure.
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new RequireHttpsAttribute());
filters.Add(new HandleErrorAttribute());
}
}
As for the AuthorizeAttribute, you should leave that out of the equation altogether unless you need some customization that deals with authorization.

Why is Hangfire requiring authentication to view dashboard

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.

Resources