MVC Allow anonymous, still get current user when logged in - asp.net-mvc

I have a problem with my MVC 4.0/Razor site.
It's a (not yet launched) public site that I recently inherited.
90% of all pages should be available to everyone, the rest are for superusers and need authentication.
This is handled via an AllowAnonymous attribute on the public facing pages, implemented like this;
public class RequireAuthenticationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) ||
filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(
typeof(AllowAnonymousAttribute), true);
if (!skipAuthorization)
base.OnAuthorization(filterContext);
}
}
Now, the problem is that I want a few customizations of the public facing sites (for the sake of argument, let's assume a "Currently logged in: XYZ"-label somewhere). What I've tried is using the User.Identity, but on all pages with AllowAnonymous, User.Identity.Name == "", even though a super user did log in. (And if he changes the url to a page with authentication, he's logged in again, and User.Identity.Name is correct).
Is there any way to both use Allow Anonymous and keep track of who's logged in?

I think I have solved this.
The problem was our custom subdomain routing. I had to override the "domain" setting so it points to .example.com instead of example.com for the cookies (mainly aspxauth).
The reason this took some time to realize was a number of other custom made parts, potentially interfering in the application, especially:
Custom membership provider
Custom AllowAnonymous attribute (even when there's a standard attribute now)
And the fact that I thought this was the normal behavior when in fact it's not.
Summary:
If implementing a subdomain routing rule AND need authentication that follows along the subdomains, you must change the base domain of the cookies.

Related

Security Trimming MVC Sitemap Provider Nodes With AuthAttribute Based on Route Values

We have a fully working sitemap with many hundreds of nodes configured with sitemap attributes on the actions. These nodes are are security trimmed working perfectly based on claims. All working great and fast.
We now have a requirement that certain pages are inaccessible based on a route value. Basically hiding mvc menu links based on the value of personId in the route. Using the code below:
//Just proof of concept - people restricted will be from a service
public class PersonAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.Request.RequestContext.RouteData.Values.ContainsKey("personId"))
{
return base.AuthorizeCore(httpContext);
}
var personId = httpContext.Request.RequestContext.RouteData.Values["personId"].ToString();
int value;
int.TryParse(personId, out value);
if (value != 0 && value == 3708)
{
return false;
}
return base.AuthorizeCore(httpContext);
}
}
This works perfectly in terms of preventing access. However unlike the rest of our security trimming sitemap doesn't work with this. If I visit the person that is restricted first it hides the node for that person and everyone else. If I visit a non hidden person then the node is visible for everyone but I get a denied access request for the person if I try to visit their node.
I assume it is related to the fact that a node doesn't have a concept of trimming based on route values.
Any ideas how this could be implemented. We are trying to implement a more flexible security model.
There is a disconnect here because of the difference between the way MVC and MvcSiteMapProvider uses the AuthorizeAttribute.
In MVC, the AuthorizeAttribute is simply checked against the context of the current request. The current context contains everything that is needed to determine whether the user is authorized to view the current page.
MvcSiteMapProvider checks every node for each request. Therefore, we can't make the same assumptions that the current context is correct for determining that a node is accessible. There is a new temporary HttpContext created for each node (based on the node's generated URL) and the route values used during the check are derived from that context (not the current context).
This fake HttpContext is not perfect. Microsoft has created several redundant properties HttpContext, RequestContext, etc. that must be explicitly set or they will default to the current context, and not all of them have been set by the AuthorizeAttributeAclModule. This was done intentionally in this case because we want the current request (current user) to be checked when it comes to security.
As a result, your check is always using the route values from the current HttpContext, not the fake context that is created based on the node's URL. To use the fake context, you need to override OnAuthorization and use the RequestContext there.
public override void OnAuthorization(AuthorizationContext filterContext)
{
var personId = filterContext.RequestContext.RouteData.Values["personId"];
base.OnAuthorization(filterContext);
}
The real issue here is that (if you are using preserved route parameters) you are basing your security for the entire site on a route value which only applies to the currrent request. What is supposed to happen when your request changes to something that doesn't include the personId? Should the nodes all be invisible? Should they all be visible? What if the user changes the route value by changing the URL manually? There are a lot of cracks in this security model.
Important: Microsoft has said time and again the importance of not basing MVC security on the URL. The route values are just an abstraction of the URL, but don't really change the fact that they are based on the URL. It is virtually impossible to guarantee that an action method will only have a single route that can access it, which is why AuthorizeAttribute was created. AuthorizeAttribute secures the resource (action method) at its source so it can't be defeated by these alternate routes. Basing it on routes entirely defeats its purpose. In short, by including a route value in AuthorizeAttribute, you are opening up the possibility that your application can be hacked by an unintended alternate route to the action method. You are not simplifying security by basing AuthorizeAttribute on routes, you are making it more complex and nearly impossible to completely control over time.
It would be better to base your security on the IPrincipal and IIdentity interfaces that are part of every form of security that plugs into MVC. In this particular case, there is a user property that is already supported by the AuthorizeAttribute. There is no built-in way to add a not user, but that functionality could easily be added to your custom AuthorizeAttribute.

MVC Limit / Redirect Role to Area

I'm trying to figure out how to do this and I can't seem to arrive at an idea to try.
I have already created a filter to redirect authenticated requests, so an authenticated user cannot revisit the login page for instance. That's all fine and dandy.
However there is one role in the system that only has access to a single area within the application, any requests to other controllers in the main area or other areas should redirect the user to this area.
I don't just want to show an unauthorised message to these users if they attempt to view anything outside of their area I would prefer to just redirect them back to their area.
what are the different ways to achieve this goal (please include pros and cons)?
EDIT
Just to be clear about this the reason I'm looking to do this is mop up potential edge cases where a user of a particular role enters a url from browser history or manually which coule take them to another area of the application they shouldn't be accessing.
For the default area of the application where the base role is User it is sufficient to just use the basic AuthorizeAttribute which just ensures requests are authorized (as most users will be granted a user role on registration.
For other areas there are specific roles which will correctly show a 401 unarthorized page to a user without the given role.
Where in this special case a certain type of user is created with a specific role and NOT provided with the basic User role but at the same time their requests will be deemed as Authorized.
Probably the easiest and most effective way is to create a custom AuthorizationAttribute that is specific to your needs:
public class ThatSpecificRoleAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// not sure what is EXACTLY needed so here is all the stuff I
// think someone might need
var rd = httpContext.Request.RequestContext.RouteData;
string currentAction = rd.GetRequiredString("action");
string currentController = rd.GetRequiredString("controller");
string currentArea = rd.Values["area"] as string;
if (httpContext.User != null
&& httpContext.User.Identity != null
&& httpContext.User.Identity.IsAuthenticated
&& httpContext.User.IsInRole("ThatSpecificRole")
&& currentController != "Home")
{
// alternatively you could also...
// httpContext.Controller.TempData["ThatSpecificRoleError401"] = true;
return false;
}
return true
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToAction("Index", Home);
}
}
Then register it globally (making sure it is the first auth attribute added):
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ThatSpecificRoleAttribute());
}
}
I know someone else already mentioned something like this, but this is very specific (and multiple authorization attributes are supported by MVC, fyi).
Pros:
You don't need to worry if someone added this to controllers or methods, it is global
It does only what you need, everyone else (anonymous and people without the role) use the system normally.
This will redirect them, and you could see if the TempData exists and give them a nice message letting them know they were redirected (option).
Cons:
Every request will check this authorization. It should be cached so the performance hit is very minimal but still exists.
I've had a similar problem, and I ended up using a little hack.
When you return a HttpUnauthorized result, the user is automatically redirected to the Login-page, defined in the Web.config (in the section
However, in stead of redirecting to the login-page, I made a redirect to an different action. In this action, I check if the user is actually logged in. If so, I check what area he can view, and redirect the user to that area. If no area is found, or if the user is not logged in at all, I manually redirect to the login-page.
Hope this helps!

Prevent Url Tampering to access another users data

I just wanted to gauge opinions on how I should approach this problem and ultimately looking for a quick win (wrong way to think about things nut time pressures mean I have to think and act quickly!
I've been given a website that has a bit of an issue.
I login using standard forms authentication as User1234 and my url is as follows:
www.mywebsite.co.uk/1234/Contact.
This will take me to User1234's details.
You can put two and two together and correctly assume that 1234 is a user id of some sort.
Once authenticated, I can access the views with [Authorize] attribute present, any anonymous/unathenticated users get redirected.
However, once logged in as User1234, I can then tinker with the url like so:
www.mywebsite.co.uk/1235/Contact.
So I am authenticated as User1234 but can see User1235's data. This is BAD for obvious reasons.
When I log in, I actively set the login ID in session so in theory, I could do a check whenever a user hits an ActionResult, I could cross check the ID present in the URL against the session login ID. However, it is a rather project with lots of action results and as such, I'm reluctant to spend my Saturday afternoon adding something to each and every ActionResult.
Is there an event in the global.asax I could use that is hit on each ActionResult request where I can compare Session login ID with url ID?
Alternatively, can anyone offer some suggestions about how I can achieve this or restrict URL tampering?
You can try and do a base controller
public class BaseController : Controller
{
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Do your stuff here
base.OnActionExecuted(filterContext);
}
}
I assume that you don't want to change your URL routes, as you could retrieve the user id also from the session. A quick solution would be to use an ActionFilter which you can place on the affected controllers or action methods:
public class VerifyUserIdAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var sessionUserId = filterContext.HttpContext.Session["UserId"];
var routeUserId = filterContext.RouteData.Values["UserId"];
if (routeUserId != null && sessionUserId == routeUserId)
filterContext.Result = new RedirectResult("<<url to redirect to>>");
}
}
I don't understand why the URL contains a data entry point. This appears to be a design flaw. I would remove all code that uses a URL parameter and instead make sure the controller looks up what the ID is based on the logged in user.

HttpContext.Current.Request.LogonUserIdentity.Groups returns different results

I'm trying to use MVC Authorize attribute on a particular controller in my MVC intranet application using Windows authentication. IIS 7.5 is set to use Windows Authentication only, and anonymous access is off in web.config. I am authenticated to the domain, However, I still get prompted for credentials when any action on the controller is executed. I checked my browser settings ( IE9 ) and it is set to automatically log in with my current Windows credentials.
I tried to create a custom Authorize Attribute class to see what was going on. Inside AuthorizeCore, I checked my user name and group membership using httpContext. I found that there was a group missing from System.Web.HttpContext.Current.Request.LogonUserIdentity.Groups that I belong to. Once authorizecore returns false and the prompt for credentials appears, I supply the same credentials that I am currently logged in with and AuthorizeCore runs again. This time, all the appropriate groups are found and the base AuthorizeCore of course authorizes the user and everything works fine. Here is the custom Authorization class I created so
public class MyAuthorize : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool chk = httpContext.User.IsInRole("mydomain\\heavyequipadmin");
// this is just to see what AD groups are provided for the current user
ArrayList groups = new ArrayList();
foreach (System.Security.Principal.IdentityReference group in System.Web.HttpContext.Current.Request.LogonUserIdentity.Groups)
{
groups.Add(group.Translate(typeof(System.Security.Principal.NTAccount)).ToString());
}
// just run the base method to Authorize
return base.AuthorizeCore(httpContext);
}
}
The attribute on my entire controller is:
[MyAuthorize(Roles = "mydomain\\HeavyEquipAdmin")]
One other thing, the role manager in web.config is:
<roleManager defaultProvider="AspNetWindowsTokenRoleProvider" />
Is there something that I am not understanding with regard to how this is supposed to work? I simply want my authentication / authorization to be handled by AD. I am fairly new at MVC and honestly configuring any authentication/authorization scheme. I did download the source for MVC 3 to look at the code for AuthorizeAttribute class to see if there is something that would make sense to me . Any advise here would be appreciated!
ANONYMOUSE USER PLAYS A ROLE HERE:
If on IIS anonymouse user us allowed, then System.Web.HttpContext.Current.Request.LogonUserIdentity.Name takes the user configured as anonymous in IIS.
otherwise it takes currently logged in user.

What is Webform's "UrlAuthorizationModule.CheckUrlAccessForPrincipal" equivalent for MVC?

I got a problem as i am writing a custom SSO solution for my company. To mkae it simple, i've made a custom authentication httpmodule that intercepts all requests so as to check user authentication state. If not authenticated, user is redirected to my custom sso login page.
The thing is, when user is not authenticated, i'd like to check if he can access the requested page/resource... With Webforms, no problem, i add an authorization block in web.config, and i use UrlAuthorizationModule.CheckUrlAccessForPrincipal with an anonymous user. Everything works fine...
But when i apply my module to an MVC (3) web site, this does not work anymore (for obvious reasons, like the possibility to access the same controller and/or action from differents urls when using routing, and because authorizations are made through controller attributes).
How can I achieve this ?? I've been searching all day long, didn't find anything about that :/
ASP.NET MVC 3 Internet Application template includes a basic AccountController which implements the following actions (along with the associated models and views):
LogOn
Register
ChangePassword / ChangePasswordSuccess
You simply need the [Authorize] attribute on the Actions or classes you wish to secure. But if you need something really custom you can do something like I've done.
I created a custom class to override security in my application.
public class AuthorizeActivityAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
HttpContext currentContext = HttpContext.Current;
//Do your custom authentication stuff here and return true or false depending on results
Return true;
}
}
And now in my Controller I have the following:
[AuthorizeActivity]
public ActionResult Index()
{
ViewBag.Message = "Welcome";
return View();
}
I had the same problem.
See solution here: MVC equivalent of Webforms "UrlAuthorizationModule.CheckUrlAccessForPrincipal"
You would have to read the information from the other controller. This
can be done by instantiating its context and the Descriptor, then
instantiating the AuthorizationContext for that controller and read
the filter info.

Resources