How to restrict access of controller action in ASP.net MVC 5 - c#

I am learning ASP.Net MVC 5 and I came up to a case where I need to restrict access to controller action under some situations. Suppose I have 5 actions in my controller and I want to restrict two of them in certain scenarios.How to achieve this I know we have inbuilt attributes like [Authorize]. Can I create user-defined restrictions to the controller actions.
Something like:
[SomeRule]
public ActionResult Index()
{
return View();
}
And if I could create a function or class named "SomeRule" and then add some rules there.Can I add a function/method/class where I can add some logic and restrict the access and redirect to a genreal page if condition does not match. I am a beginner please guide me.

What you'd want to do is create a custom Action Filter, which would allow you to define custom logic within your action to determine if a given user could / could not access the decorated action:
public class SomeRuleAttribute : System.Web.Mvc.ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Define some condition to check here
if (condition)
{
// Redirect the user accordingly
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "Account" }, { "action", "LogOn" } });
}
}
}
You can also extend these even further and set properties on them as well if you need to apply some values to check against where the attribute is defined:
public class SomeRule: ActionFilterAttribute
{
// Any public properties here can be set within the declaration of the filter
public string YourProperty { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Define some condition to check here
if (condition && YourProperty == "some value")
{
// Redirect the user accordingly
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "Account" }, { "action", "LogOn" } });
}
}
}
This would look like the following when used:
[SomeRule(YourProperty = "some value")]
public ActionResult YourControllerAction()
{
// Code omitted for brevity
}

Related

ASP.NET MVC conditional routing

In my MVC Application I have my routes defined as follows:
routes.MapRoute(
"Category_default",
"{lang}/Category/{categoryid}/{controller}/{action}/{id}",
new { lang = "en", action = "Index", id = UrlParameter.Optional, categoryid = -1 }
).DataTokens.Add("area", "Category");
routes.MapRoute(
name: "Default",
url: "{lang}/{controller}/{action}/{id}",
defaults: new { controller = "home", action = "index", lang = "en", id = UrlParameter.Optional }
);
The application works fine. However, the system administrator just brought to my knowledge that those users who don't have access to the category (in other words, they are logically not associated with it) can also see the data just by switching the categoryid parameter, which is no surprise since I haven't put any check there.
What's the efficient way of checking if the user has privileges over this category or not. In the system I have a User object with User.AllowedCategories List which contains integer values of all the ids the user has access to.
The category area has about 20 controllers (therefore 20 views). Should I put a logic to check on every view? Or I can do it with minimum coding / or can I put this logic globally?
You can achieve that in 3 ways,
Method 1: Global Filters
In FilterConfig.cs:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ValidateUserFilter());
}
In ValidateUserFilter.cs
public class ValidateUserFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
//Controllers to avoid validation
if ((new[] { "<<CONTROLLER1>>", "<<CONTROLLER2>>" }).Any(x => x == filterContext.ActionDescriptor.ControllerDescriptor.ControllerName))
{
return;
}
if (!User.AllowedCategories.Any(x => x == FilterContext.RequestContext.RouteData.Values["id"]))
{
//Redirect user to unauthorized page.
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "Controller", "<<CONTROLLER_NAME>>" }, { "Action", "<<ACTION_NAME>>" } });
}
}
}
Method 2: FilterAttribute
public class ValidateControllerAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (!User.AllowedCategories.Any(x => x == filterContext.RequestContext.RouteData.Values["id"]))
{
//Redirect user to unauthorized page.
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "Controller", "<<CONTROLLER_NAME>>" }, { "Action", "<<ACTION_NAME>>" } });
//OR, You can redirect to 403 response
//throw new HttpException((int)System.Net.HttpStatusCode.Forbidden, "You do not have permission to view this page");
}
}
}
You have to add this attribute in every controller which you want to
validate
Ex:
[ValidateController]
public class MyControllerController : Controller
Method 3: ActionMethodSelectorAttribute
public class ValidateActionAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
if (!User.AllowedCategories.Any(x => x == controllerContext.RequestContext.RouteData.Values["id"]))
{
//Redirect user to unauthorized page.
controllerContext.HttpContext.Response.Clear();
controllerContext.HttpContext.Response.Redirect("~/<<CONTROLLER_NAME>>/<<ACTION_NAME>>");
return false;
//OR, You can redirect to 403 response
//throw new HttpException((int)System.Net.HttpStatusCode.Forbidden, "You do not have permission to view this page");
/*OR,
controllerContext.HttpContext.Response.StatusCode = 403;
return true;*/
}
}
}
You have to add this attribute in every action which you want to
validate
Ex:
[ValidateAction]
public ActionResult Index()
{
return View();
}
If I got it right, essentially the problem is that, unauthorized user is able to access parts of application, which it should not.
You can use Authorize attribute on your action methods, OR on the Controller class, depending on what areas you want to have authorization. If your design allows, you can also consider creating a base controller and apply Authorize attribute on that, if you do not want repetitions.
Now specify the valid users for these controllers, using Roles OR Users parameters
[Authorize(Roles = "Valid Roles", Users = "Valid Users")]
If the default Authorize attribute doesn't suffice your needs you can always create your own custom attribute for authorization.

Authorization with Session variables in asp net mvc 5

So my project requirements changed and now I think I need to build my own action filter.
So, this is my current login controller:
public class LoginController : Controller
{
// GET: Login
public ActionResult Index()
{
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel model)
{
string userName = AuthenticateUser(model.UserName, model.Password);
if (!(String.IsNullOrEmpty(userName)))
{
Session["UserName"] = userName;
return View("~/Views/Home/Default.cshtml");
}
else
{
ModelState.AddModelError("", "Invalid Login");
return View("~/Views/Home/Login.cshtml");
}
}
public string AuthenticateUser(string username, string password)
{
if(password.Equals("123")
return "Super"
else
return null;
}
public ActionResult LogOff()
{
Session["UserName"] = null;
//AuthenticationManager.SignOut();
return View("~/Views/Home/Login.cshtml");
}
}
And this is my action filter attempt:
public class AuthorizationFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (HttpContext.Current.Session["UserName"] != null)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary{{ "controller", "MainPage" },
{ "action", "Default" }
});
}
base.OnActionExecuting(filterContext);
}
}
I have already added it to FilterConfig, but when I login it does not load Default.cshtml it just keeps looping the action filter. The action result for it looks like this:
//this is located in the MainPage controller
[AuthorizationFilter]
public ActionResult Default()
{
return View("~/Views/Home/Default.cshtml");
}
So, what would I need to add in order to give authorization so only authenticated users can view the applicationĀ“s pages? Should I use Session variables or is there another/better way of doing this using? I am pretty much stuck with AuthenticateUser(), since what happens there now is just a simple comparison like the one we have there now.
Thank you for your time.
Create an AuthorizeAttribute with your logic in there:
public class AuthorizationFilter : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true))
{
// Don't check for authorization as AllowAnonymous filter is applied to the action or controller
return;
}
// Check for authorization
if (HttpContext.Current.Session["UserName"] == null)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
As long as you have the Login URL Configured in your Startup.Auth.cs file, it will handle the redirection to the login page for you. If you create a new MVC project it configures this for you:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(
new CookieAuthenticationOptions {
// YOUR LOGIN PATH
LoginPath = new PathString("/Account/Login")
}
);
}
}
Using this you can decorate your controllers with [AuthorizationFilter] and also [AllowAnonymous] attributes if you want to prevent the authorization from being checked for certain Controllers or Actions.
You might want to check this in different scenarios to ensure it provides tight enough security. ASP.NET MVC provides mechanisms that you can use out of the box for protecting your applications, I'd recommend using those if possible in any situation. I remember someone saying to me, if you're trying to do authentication/security for yourself, you're probably doing it wrong.
Since your attribute is added to the FilterConfig, it will apply to ALL actions. So when you navigate to your MainPage/Default action it will be applying the filter and redirecting you to your MainPage/Default action (and so on...).
You will either need to:
remove it from the FilterConfig and apply it to the appropriate actions / controllers
or add an extra check in the filter so that it doesn't redirect on certain routes

How to use the Authorize attribute both at the controller and action level?

I have implemented my own custom Authorize attribute.
The attribute is applied both at the controller level and at the action level.
Here is an example of what I need to do:
[ClaimsAuthorize(Roles = "AdvancedUsers")]
public class SecurityController : Controller
{
[ClaimsAuthorize(Roles = "Administrators")]
public ActionResult AdministrativeTask()
{
return View();
}
public ActionResult SomeOtherAction()
{
return View();
}
}
Currently if a user has the Administrator Role but not the AdvancedUsers role, he cannot execute "Administrative Task".
How can I change this behavior to perform a security check at the action level even if the user is not authorized at the controller level?
For the moment, the only solution I can think about is to implement 2 attributes: one for securing controllers, another for securing actions. Then I would play with the Order property to execute the one at the action level first.
However, I would prefer a solution with a single attribute if possible.
Use built-in [OverrideAuthorization]:
[ClaimsAuthorize(Roles = "AdvancedUsers")]
public class SecurityController : Controller
{
[OverrideAuthorization]
[ClaimsAuthorize(Roles = "Administrators")]
public ActionResult AdministrativeTask()
{
return View();
}
public ActionResult SomeOtherAction()
{
return View();
}
}
OverrideAuthorization Attribute is available for MVC 5 (at least) and up. Once you decorate the Action with it, also decorate with the new Role and that will take effect over the Controller level Role.
This should not be possible. Imagine the logic which MVC uses with the authorization filters.
When the controller is determined - check if there is an authorization filter that applies to that controller and execute it.
When the action is known - do the same for the action.
In all cases a fail in authorization would short-circuit the pipeline.
To make specific actions restricted you simply use the Authorize-attribute on the methods that handle these actions.
When you mark an action method with the Authorize attribute, access to that action method is restricted to users who are both authenticated and authorized.
//[ClaimsAuthorize(Roles = "AdvancedUsers")]
public class SecurityController : Controller
{
{
[ClaimsAuthorize(Roles ="Administrators", "Role2","Role3")]
public ActionResult AdministrativeTask()
{
return View();
}
}
OR you can override your authorization at controller level ,
Create a new OverrideAuthorizeAttribute attribute.
public class OverrideAuthorizeAttribute : AuthorizeAttribute {
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}
}
and you can use this attribute to override your controller level autorization.
[ClaimsAuthorize(Roles = "AdvancedUsers")]
public class SecurityController : Controller
{
[ClaimsAuthorize(Roles = "Administrators")]
public ActionResult AdministrativeTask()
{
return View();
}
[OverrideAuthorizeAttribute(Roles ="xxxx")] // This role will override controller
//level authorization
public ActionResult SomeOtherAction()
{
return View();
}
}
You need two authorization attributes - a base one with all authorization logic, and a second one, derived from the base attribute, that is only used to override the base attribute.
Example authorization attributes:
public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
protected bool _canOverride = true;
//...custom authorization code goes here.....
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
//Don't authorize if the override attribute exists
if (_canOverride && actionContext.ActionDescriptor.GetCustomAttributes<OverrideClaimsAuthorizeAttribute>().Any())
{
return;
}
base.OnAuthorization(actionContext);
}
}
public class OverrideClaimsAuthorizeAttribute : ClaimsAuthorizeAttribute
{
public OverrideClaimsAuthorizeAttribute ()
: base()
{
_canOverride = false;
}
}
In the base authorization attribute we are saying to go ahead and authorize as normal, as long as the OverrideClaimsAuthorizeAttribute doesn't exist. If the OverrideClaimsAuthorizeAttribute does exist, then only run the authorization on classes where _canOverride is false (ie the OverrideClaimsAuthorizeAttribute class itself).
Example usage:
[ClaimsAuthorize(Roles = "AdvancedUsers")]
public class SecurityController : Controller
{
//Ignores the controller authorization and authorizes with Roles=Administrators
[OverrideClaimsAuthorize(Roles = "Administrators")]
public ActionResult AdministrativeTask()
{
return View();
}
//Runs both the controller and action authorization, so authorizes with Roles=Administrators AND Roles=AdvancedUsers
[ClaimsAuthorize(Roles = "Administrators")]
public ActionResult AdvancedAdministrativeTask()
{
return View();
}
//authorizes with controller authorization: Roles=AdvancedUsers
public ActionResult SomeOtherAction()
{
return View();
}
}
Check this previous question. (check #AndyBrown answer, case 2)
For a simple way you might also try adding (
[AllowAnonymous]) to override the controller
[Authorize]
then add a new custom filter to check for your logic for this particular action. Or you can add the code that checks for the role just inside it.

Intercepting ASP.NET MVC routes

I need to transform some url parameters while creating link on server side.
Example:
#html.ActionLink("text","index","Home",null,new { id=Model.Id });
Now i have to transform id parameter so i can simply convert it and pass it into object objectRoute parameter or i can simply override ActionLink.But problem is that i have to make refactor on whole project.
So i am looking a way to intercepting mechanism or handler mechanism.
Is there any solution for this ?
You could try using an ActionFilterAttribute:
public class ConversionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var idValue = filterContext.RouteData.Values["id"];
var convertedIdValue = ConvertId(idValue);
var newRouteValues = new RouteValueDictionary(filterContext.RouteData.Values);
newRouteValues["id"] = convertedIdValue;
filterContext.Result = new RedirectToRouteResult(newRouteValues);
}
}
Then you'll need to apply the attribute to the action where you want this to happen:
[Conversion]
public ActionResult Index(int id)
{
// Your logic
return View();
}

How to pass parameters to a custom ActionFilter in ASP.NET MVC 2?

I'm trying to create a custom ActionFilter which operates on a set of parameters that would be passed to it from the controller.
So far, my customer ActionFilter looks like this:
public class CheckLoggedIn : ActionFilterAttribute
{
public IGenesisRepository gr { get; set; }
public Guid memberGuid { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Member thisMember = gr.GetActiveMember(memberGuid);
Member bottomMember = gr.GetMemberOnBottom();
if (thisMember.Role.Tier <= bottomMember.Role.Tier)
{
filterContext
.HttpContext
.Response
.RedirectToRoute(new { controller = "Member", action = "Login" });
}
base.OnActionExecuting(filterContext);
}
}
I know I still need to check for nulls, etc. but I can't figure out why gr and memberGuid aren't successfully being passed. I'm calling this Filter like this:
[CheckLoggedIn(gr = genesisRepository, memberGuid = md.memberGUID)]
public ActionResult Home(MemberData md)
{
return View(md);
}
genesisRepository and md are being set in the controller's constructor.
I'm not able to get this to compile. The error I get is:
Error 1 'gr' is not a valid named attribute argument because it is not a valid attribute parameter type
Error 2 'memberGuid' is not a valid named attribute argument because it is not a valid attribute parameter type
I double checked that gr and memberGuid were the same types as genesisRepority and md.memberGUID, What is causing these errors?
Solution
Thanks to jfar for offering a solution.
Here's the Filter I ended up using:
public class CheckLoggedIn : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var thisController = ((MemberController)filterContext.Controller);
IGenesisRepository gr = thisController.GenesisRepository;
Guid memberGuid = ((MemberData)filterContext.HttpContext.Session[thisController.MemberKey]).MemberGUID;
Member thisMember = gr.GetActiveMember(memberGuid);
Member bottomMember = gr.GetMemberOnBottom();
if (thisMember.Role.Tier >= bottomMember.Role.Tier)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new {
controller = "Member",
action = "Login"
}));
}
base.OnActionExecuting(filterContext);
}
}
This is a way to make this work. You have access to the ControllerContext and therefore Controller from the ActionFilter object. All you need to do is cast your controller to the type and you can access any public members.
Given this controller:
public GenesisController : Controller
{
[CheckLoggedIn()]
public ActionResult Home(MemberData md)
{
return View(md);
}
}
ActionFilter looks something like
public class CheckLoggedIn : ActionFilterAttribute
{
public IGenesisRepository gr { get; set; }
public Guid memberGuid { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
/* how to get the controller*/
var controllerUsingThisAttribute = ((GenesisController)filterContext.Controller);
/* now you can use the public properties from the controller */
gr = controllerUsingThisAttribute .genesisRepository;
memberGuid = (controllerUsingThisAttribute .memberGuid;
Member thisMember = gr.GetActiveMember(memberGuid);
Member bottomMember = gr.GetMemberOnBottom();
if (thisMember.Role.Tier <= bottomMember.Role.Tier)
{
filterContext
.HttpContext
.Response
.RedirectToRoute(new { controller = "Member", action = "Login" });
}
base.OnActionExecuting(filterContext);
}
}
Of course this is assuming the ActionFilter isn't used across multiple controllers and you're ok with the coupling. Another Option is to make a ICheckedLoggedInController interface with the shared properties and simply cast to that instead.
You can only use constant values for attribute properties; see a this page for a full explanation.
Attributes are essentially metadata added to a type. They can only use const values, instead of instance variables. In your case you are tying to pass in your instance variables of genisisRepository, etc. This will fail to compile as they are not compile time constants.
You should look into Dependency Injection for Action Filters to achieve this, typically using an IoC container.
Also, if your ActionFilter is performing a post ActionResult action, such as OnActionExecuted, you could probably get away with storing something in the route data:
public ActionResult Index()
{
ControllerContext.RouteData.DataTokens.Add("name", "value");
return View();
}

Categories