MVC Limit / Redirect Role to Area - asp.net-mvc

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!

Related

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.

MVC Allow anonymous, still get current user when logged in

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.

Caching content only for non-authenticated users

For a web-site, I want to cache the pages ONLY for users who are not authenticated - authenticated users do not get cached content (since they will be updating and need to see results right away).
I know how to vary the cache for each user using VaryByCustom:
Link1
Link2
...But I can't figure out how to turn off caching entirely for authenticated users.
What to do?
Edit
The code below has a problem if there is already a cached version of the page from an unauthenticated user. Basically the authenticated user will be served the unauthenticated view of things.
However, this link here has solution that works: Link
Use this as a global action filter.
public class NoCacheForAuthenticatedUsersAttribute: ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if(filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
}
}
}
Use HttpCachePolicy.AddValidationCallback.
See: http://msdn.microsoft.com/en-us/library/system.web.httpcachepolicy.addvalidationcallback.aspx
Taking inspiration from the Link1 you posted, as easy-to-code idea is that you could alter the output of the GetVaryByCustomString override as follows:
public override string GetVaryByCustomString(HttpContext context, string arg)
{
if (arg == "IsLoggedIn")
{
if (context.Request.Cookies["anon"] != null)
{
if (context.Request.Cookies["anon"].Value == "false")
{
return "auth";
}
}
return Guid.New().ToString();
}
else
{
return base.GetVaryByCustomString(context, arg);
}
}
This is not really an answer as technically the authenticated user's output will still be cached, but it does satisfy the requirement of having authenticated users see whatever the results are right away. The downside is that you'll need to keep the cache duration/TTL small enough so your cache doesn't get flooded but large enough that anonymous users gain some benefit from it.
Another alternative is to write your own Action Filter to do the caching, and add support in there for anonymous-only caching. This is far more in 'roll your own' territory though. See Klopfenstein's old post or Steve Sanderson's on this for a starting point. They lack many of the other features of OutputCache (it keys on the whole route data for instance), but you can make it work to your own specifications.
You may want to create two controllers, one for authenticated users (where you don't cache), one for non authenticated users (where you cache). Then you can refractor the logic in the controllers to a common "business layer object" to keeping your code DRY and unit testable.

How does the Authorize tag work? - ASP.NET MVC

How does the Authorize Tag determine if the user is authorized or not?
Like say, if a user logs in and they try to go to a view that has an Authorize tag. How does it determine if a user is authorized or not? Does it do a query to database and check?
How about if they go to a view with a role authorization? Does it query the membership role table?
I am just wondering since I have what the ASP.NET membership tables considers duplicate userNames. I use a serious of fields to determine which user is what, allowing users to have the same duplicate userName, but still be unique in my database.
This caused me to have to write custom methods for lots of .NET membership stuff since it all used "userName" to do searching instead of using the UserId.
So I am now wondering if this could be the case with the Authorize tag. Since I have no clue how it works and like if I was not using .NET membership I would not have a clue how it would determine it.
The Authorize tag uses all the built in membership checks from ASP.NET. It's VERY easy to roll your own tag. For example:
public class MyAuthorize : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null) throw new ArgumentNullException("httpContext");
// Make sure the user is authenticated.
if (httpContext.User.Identity.IsAuthenticated == false) return false;
// Do you own custom stuff here
bool allow = CheckIfAllowedToAccessStuff();
return allow;
}
}
You then can use the [MyAuthorize] tag which will use your custom checks.
ControllerActionInvoker parses the attribute and calls OnAuthorization() on it when it's time to check the credentials.
The AuthorizationAttribute.OnAuthorization() method basically checks to see if User.Identity.IsAuthenticated is true or not. This just draws on the functionality of FormsAuthentication or whatever other authentication scheme you may be using.

asp.net MVC, how to redirect registered users that don't have a profile to page?

We are building a site using asp.net mvc. We want to allow the user to easily register and create an account. There is though a very special piece of information, that will be registered in his profile, that we want to show to him *after registration is finished, and he is logging in for the first time.
The logic is whatever the URL that was hit, if the user is authenticated and does not have a valid profile, redirect him to the "Create Profile" page.
The whole ui will depend on those profile choices. What approach should we use within the MVC framework, to force this workflow on the visitor? The ideas I can come up with require tons of code duplication in controllers etc, so its clearly a bad idea.
We are using Membership for users, but profile is our own implementation (no profile provider) that will connect profile data to a userId.
I think the easiest way to do this is either create a custom AuthorizeAttribute, extending the existing one or create a separate FilterAttribute. Either one of these would get applied to all of your controllers and ensure that an authenticated user has a profile. In the case where no profile exists, the filter would redirect the user to the page where the profile is created. This would be done by setting the result property on the context to a RedirectResult to the profile creation action. Only if the profile exists and is complete would the filter allow the user to proceed to the desired action.
Alternatively, you could create a base controller that overrides OnActionExecuting and performs the same task. I prefer the attribute mechanism as it is more flexible, i.e., you could have some public actions that are available without the profile (including the profile setting action).
Answering my own question: In the end I created a custom actionFilter. In the beginning, I took the path of subclassing [authorize] into [AuthorizeCheckProfile]. But then I realized that the use case was wrong: I did not want the logged-in only parts of my site to redirect to the create-profile page, if no user profile existed. I wanted any page of my site to redirect to that page, if its a logged-in user with no profile. The only place I don't want to check that is in the actual profile-create. Here's the code:
public class AssertProfileAttribute : ActionFilterAttribute {
public AssertProfileAttribute() {
}
public override void OnActionExecuting(ActionExecutingContext filterContext) {
if (filterContext.HttpContext.Request.IsAuthenticated == false)
return;
//we have a user, but does he have a profile?
if (filterContext.HttpContext.Session["UserProfile"] == null) { //not in the session
IUnityContainer container = filterContext.HttpContext.Application["container"] as IUnityContainer;
Profile hasProfile = container.Resolve<IProfileRepository>().GetUserProfile(Membership.GetUser());
if (hasProfile == null) {
//we have to redirect to the create profile
string returnURL = filterContext.HttpContext.Request.AppRelativeCurrentExecutionFilePath;
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Profile", action = "Create", returnTo = returnURL }));
} else {
//he has a profile but we haven't put it in session yet
filterContext.HttpContext.Session["UserProfile"] = hasProfile;
}
}
}
}
It has the side-effect that it will store the profile in a Session key. This way it can be easily be fetched so that further role checks can happen in every request with other custom filters. The implementation uses Unity and repositories for db access.
A big thanks to the community.
Try consider ActionFilter and FilterAttribute if not most pages need to do so, or else, you may actually put this redirection logic to global.asax (in those Begin Request or similar events)

Resources