I have a custom auth set up so that a user is stored as a session variable. Once they go through the Account/LogIn process I store the details returned from a 3rd party API as a user in the session like this:
Session["User"] = new UserViewModel(result);
I want to check the user is present before every controller action so I have made a BaseController with the following check in it:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Session["User"] != null)
base.OnActionExecuting(filterContext);
else
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { action = "LogIn", controller = "Account" }));
Each of the controllers then inherits from the BaseController so that it redirects to the Log In page if there is no user. I don't inherit from the BaseController for the AccountController so that it doesn't get in an infinite loop of checking and redirecting, but I also want to have specific pages not check for log in. Is there any way to do this, i.e. write an exception rule in the same way that you might have [AllowAnonymous]?
You could use a filter on those methods as:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class ActionCheckAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower().Trim();
string actionName = filterContext.ActionDescriptor.ActionName.ToLower().Trim();
// this is just a sample.. you can implement any logic you want
if (!actionName.StartsWith("your method name") && !controllerName.StartsWith("your controller name"))
{
var session1 = HttpContext.Current.User.Identity.Name;
HttpContext ctx = HttpContext.Current;
//Redirects user to login screen if session has timed out
if (session1 == null)
{
base.OnActionExecuting(filterContext);
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "Account",
action = "LogOff"
}));
}
}
}
}
then on the controllers put the attribute as:
[ActionCheck]
public class MyController : Controller
{
public ActionResult Index()
{
return View();
}
}
or on specific action methods as:
[ActionCheck]
public Actionresult SomeMethod()
{
return View();
}
I have certain code with in the same controller class that looks almost the same, such as setting viewbags to populate all the drop down lists, the same code applies in my Post and get Create and Edit action methods.
So I have created a private method at the end of my controller class as follow:-
private void populateViewBags()
{
string controllerName = RouteData.Values["controller"].ToString();
ViewBag.PossibleDataCenters = repository.AllDataCenter().OrderBy(a => a.Name).ToList();
ViewBag.PossibleZones = repository.AllZone().OrderBy(a => a.Name).ToList();
List<string> s = new List<string>();
s.Add(controllerName.ToLower());
ViewBag.Products = repository.GetProducts(s).OrderBy(a => a.COMPONENTNAME).ToList();
ViewBag.Sites = repository.GetSDOrg().OrderBy(a => a.NAME).ToList();
ViewBag.Customers = repository.FindAccountDefinition(null).ToList();
}
And I am calling this method inside my action method. So is it the right way to re-use the code?
Thanks
There are two standard ways you can do this.
1st approach - override OnActionExecuting and/or OnActionExecuted methods of your controller class:
public class HomeController: Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
string controllerName = RouteData.Values["controller"].ToString();
ViewBag.ControllerName = controllerName;
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
string controllerName = RouteData.Values["controller"].ToString();
ViewBag.ControllerName = controllerName;
}
}
You can also make abstract base controller which implements those methods and then inherit concrete controllers from abstract one, so that you don't duplicate code code in each controller.
2nd approach - make custom ActionFilter attribute and decorate each controller that needs to perform additional actions.
public class MyActionFilterAttribute: ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string controllerName = filterContext.RouteData.Values["controller"].ToString();
filterContext.Controller.ViewBag.ControllerName = controllerName;
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
string controllerName = filterContext.RouteData.Values["controller"].ToString();
filterContext.Controller.ViewBag.ControllerName = controllerName;
}
}
Then just decorate controllers, like:
[MyActionFilter]
public class HomeController: Controller
{
// ....
}
UPDATE: Additional flexibility of filter approach, if you need filter on per-action basis, instead of all actions in the controller, it's also possible:
public class HomeController: Controller
{
[MyActionFilter]
public ActionResult MyAction()
{
//...
}
}
If I define an Action filter that implements IActionFilter like so:
public class FooAttribute : FilterAttribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{ }
public void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = new ContentResult
{
Content = "Test",
ContentEncoding = Encoding.UTF8,
ContentType = "text"
};
}
}
And apply it to the following action method:
[Foo]
public ActionResult Index()
{
return View();
}
When I do this the Index action code does not run and neither does the OnResultExecuted method. Is this because I set the Result property in the OnActionExecuting method? Are there any other things that will cause the normal flow to be interrupted?
I think its just the Result property that has this effect..
See here : http://books.google.be/books?id=gzfFQrs_qQAC&lpg=PA442&ots=EXCefpt5-O&dq=iactionfilter%20onactionexecuting&pg=PA442#v=onepage&q=iactionfilter%20onactionexecuting&f=false
User can cancel the action by setting Result to something non-null
I want to create an action filter that will be used by ONLY two controllers in my app... This action filter is supposed to be checked for every action inside the TWO controllers.
Heres my action filter code
public class AllowedToEditEHRFilter : IActionFilter
{
IUnitOfWork unitOfWork;
IRepository<EHR> ehrRepository;
public AllowedToEditEHRFilter(IUnitOfWork dependency)
{
unitOfWork = dependency;
ehrRepository = unitOfWork.EHRs;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
int ehrId;
if (int.TryParse(filterContext.HttpContext.Request.QueryString["ehrId"], out ehrId))
{
EHR ehr = ehrRepository.FindById(ehrId);
if (ehr.UserName != Membership.GetUser().UserName)
filterContext.Result = new ViewResult { ViewName = "InvalidOwner" };
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
Now Im just not sure how can I configure MVC framework so that the action filter gets triggered at the appropriate times.
Im using this as reference but that is applying a GLOBAL actionfilter and it doesnt specify how to limit to just some of your controllers.
Please help.
btw Im using NinjectMVC3
This depends on what the appropriate time is.
See my original blog post
http://www.planetgeek.ch/2010/11/13/official-ninject-mvc-extension-gets-support-for-mvc3/
Or read the other pages of the doc:
https://github.com/ninject/ninject.web.mvc/wiki/Conditional-bindings-for-filters
https://github.com/ninject/ninject.web.mvc/wiki/Filter-configurations
(Probably I should link them)
Basically you need to configure a binding for the filter and define some condition:
kernel.BindFilter<AllowedToEditEHRFilter>(FilterScope.Action, 0).When...
e.g.
.WhenActionHas<AllowedToEditEHRAttribute>()
Update:
In your case simply
kernel.BindFilter<AllowedToEditEHRFilter>(FilterScope.Controller, 0).When(
(controllerContext, actionDescriptor) =>
controllerContext.Controller is PhysicalTestsController)
To apply the action filter to only some of your controllers, simply add the attribute to the controllers in question.
[AllowedToEditEHR]
public class YourController : Controller
{
...
For this to work, you should rename your filter to AllowedToEditEHRAttribute, i.e. replace "Filter" by "Attribute". This is a standard naming convention for attributes.
Update: To inject dependencies in the filter, just decorate it's constructor with the [Inject] attribute.
public class AllowedToEditEHRFilter : IActionFilter
{
IUnitOfWork unitOfWork;
IRepository<EHR> ehrRepository;
public AllowedToEditEHRFilter(IUnitOfWork dependency)
{
unitOfWork = dependency;
ehrRepository = unitOfWork.EHRs;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
int ehrId;
if (int.TryParse(filterContext.ActionParameters["ehrId"].ToString(), out ehrId))
{
EHR ehr = ehrRepository.FindById(ehrId);
if (ehr.UserName != Membership.GetUser().UserName)
filterContext.Result = new ViewResult { ViewName = "InvalidOwner" };
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
And the CustomFilterProvider
public class ConfiguredFilterProvider : IFilterProvider
{
private readonly IKernel _kernel;
public ConfiguredFilterProvider(IKernel kernel)
{
_kernel = kernel;
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
List<Filter> list = new List<Filter>();
if (controllerContext.Controller is PhysicalTestsController)
{
list.Add(new Filter(
_kernel.Get(typeof(AllowedToEditEHRFilter)),
FilterScope.Global, order: null
));
}
return list;
}
}
And in Ninject
kernel.Bind<IFilterProvider>()
.To<ConfiguredFilterProvider>();
It might not be the cleanest solution but its working.
You just have to decorate that two controllers with the action filter like this
[AllowedToEditEHRFilter]
public class YourController : Controller
{
...
}
However, I am not sure if it is allowed to have a complex object passed in a constructor of that filter.
Instead of implementing IActionFilter, extend ActionFilterAttribute and then assign the attribute to the two controllers you want to affect:
public class AllowedToEditEHRFilter : ActionFilterAttribute
{
// ...
}
and:
[AllowedToEditEHRFilter]
public class MyController : Controller
{
// ...
}
Right now I decorate a method like this to allow "members" to access my controller action
[Authorize(Roles="members")]
How do I allow more than one role? For example the following does not work but it shows what I am trying to do (allow "members" and "admin" access):
[Authorize(Roles="members", "admin")]
Another option is to use a single authorize filter as you posted but remove the inner quotations.
[Authorize(Roles="members,admin")]
If you want use custom roles, you can do this:
CustomRoles class:
public static class CustomRoles
{
public const string Administrator = "Administrador";
public const string User = "Usuario";
}
Usage
[Authorize(Roles = CustomRoles.Administrator +","+ CustomRoles.User)]
If you have few roles, maybe you can combine them (for clarity) like this:
public static class CustomRoles
{
public const string Administrator = "Administrador";
public const string User = "Usuario";
public const string AdministratorOrUser = Administrator + "," + User;
}
Usage
[Authorize(Roles = CustomRoles.AdministratorOrUser)]
One possible simplification would be to subclass AuthorizeAttribute:
public class RolesAttribute : AuthorizeAttribute
{
public RolesAttribute(params string[] roles)
{
Roles = String.Join(",", roles);
}
}
Usage:
[Roles("members", "admin")]
Semantically it is the same as Jim Schmehil's answer.
For MVC4, using a Enum (UserRoles) with my roles, I use a custom AuthorizeAttribute.
On my controlled action, I do:
[CustomAuthorize(UserRoles.Admin, UserRoles.User)]
public ActionResult ChangePassword()
{
return View();
}
And I use a custom AuthorizeAttribute like that:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class CustomAuthorize : AuthorizeAttribute
{
private string[] UserProfilesRequired { get; set; }
public CustomAuthorize(params object[] userProfilesRequired)
{
if (userProfilesRequired.Any(p => p.GetType().BaseType != typeof(Enum)))
throw new ArgumentException("userProfilesRequired");
this.UserProfilesRequired = userProfilesRequired.Select(p => Enum.GetName(p.GetType(), p)).ToArray();
}
public override void OnAuthorization(AuthorizationContext context)
{
bool authorized = false;
foreach (var role in this.UserProfilesRequired)
if (HttpContext.Current.User.IsInRole(role))
{
authorized = true;
break;
}
if (!authorized)
{
var url = new UrlHelper(context.RequestContext);
var logonUrl = url.Action("Http", "Error", new { Id = 401, Area = "" });
context.Result = new RedirectResult(logonUrl);
return;
}
}
}
This is part of modifed FNHMVC by Fabricio MartÃnez Tamayo https://github.com/fabriciomrtnz/FNHMVC/
You can use Authorization Policy
in Startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("admin", policy => policy.RequireRole("SuperAdmin","Admin"));
options.AddPolicy("teacher", policy => policy.RequireRole("SuperAdmin", "Admin", "Teacher"));
});
And in Controller Files:
[Authorize(Policy = "teacher")]
[HttpGet("stats/{id}")]
public async Task<IActionResult> getStudentStats(int id)
{ ... }
"teacher" policy accept 3 roles.
Using AspNetCore 2.x, you have to go a little different way:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class AuthorizeRoleAttribute : AuthorizeAttribute
{
public AuthorizeRoleAttribute(params YourEnum[] roles)
{
Policy = string.Join(",", roles.Select(r => r.GetDescription()));
}
}
just use it like this:
[Authorize(YourEnum.Role1, YourEnum.Role2)]
I mixed answers and proposed this method.
Firstly, We create an enum for role accesses.
public enum ERoleAccess
{
[Description("Admin User")]
Admin = 1,
[Description("General User")]
User = 2,
[Description("Editor User")]
Editor = 3,
}
Secondly, we need an attribute filter for customer MVC authorize.
public class RolesAttribute:AuthorizeAttribute
{
public RolesAttribute(params ERoleAccess[] roles)
{
Roles = string.Join(",", roles);
}
}
Finally, we can use "RolesAttribute" on the controllers or actions.
[Roles(ERoleAccess.Admin, ERoleAccess.Editor, ERoleAccess.User)]
In this approach, we use numbers of alternative string values.
(1= Admin, 2=User,...)
It's good for decreasing token size and comparing performance.
Another clear solution, you can use constants to keep convention and add multiple [Authorize] attributes. Check this out:
public static class RolesConvention
{
public const string Administrator = "Administrator";
public const string Guest = "Guest";
}
Then in the controller:
[Authorize(Roles = RolesConvention.Administrator )]
[Authorize(Roles = RolesConvention.Guest)]
[Produces("application/json")]
[Route("api/[controller]")]
public class MyController : Controller
Better code with adding a subclass AuthorizeRole.cs
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
class AuthorizeRoleAttribute : AuthorizeAttribute
{
public AuthorizeRoleAttribute(params Rolenames[] roles)
{
this.Roles = string.Join(",", roles.Select(r => Enum.GetName(r.GetType(), r)));
}
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {
{ "action", "Unauthorized" },
{ "controller", "Home" },
{ "area", "" }
}
);
//base.HandleUnauthorizedRequest(filterContext);
}
else
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {
{ "action", "Login" },
{ "controller", "Account" },
{ "area", "" },
{ "returnUrl", HttpContext.Current.Request.Url }
}
);
}
}
}
How to use this
[AuthorizeRole(Rolenames.Admin,Rolenames.Member)]
public ActionResult Index()
{
return View();
}
If you find yourself applying those 2 roles often you can wrap them in their own Authorize. This is really an extension of the accepted answer.
using System.Web.Mvc;
public class AuthorizeAdminOrMember : AuthorizeAttribute
{
public AuthorizeAdminOrMember()
{
Roles = "members, admin";
}
}
And then apply your new authorize to the Action. I think this looks cleaner and reads easily.
public class MyController : Controller
{
[AuthorizeAdminOrMember]
public ActionResult MyAction()
{
return null;
}
}
[Authorize(Roles="admin")]
[Authorize(Roles="members")]
will work when the AND is needed (as asked by question) whereas this answer shows the OR version.
See more at https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-6.0#adding-role-checks
Intent promptInstall = new Intent(android.content.Intent.ACTION_VIEW);
promptInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
promptInstall.setDataAndType(Uri.parse("http://10.0.2.2:8081/MyAPPStore/apk/Teflouki.apk"), "application/vnd.android.package-archive" );
startActivity(promptInstall);