Defining authentication requirements in MVC3 by action method attribute - asp.net-mvc-3

I've got an MVC3 application with 4 levels of authentication, and 4 base controllers that tie to each one:
Unauthenticated - BaseController
User - BaseAuthController : BaseController
Advisor - BaseAdvisorController : BaseAuthController
Admin - BaseAdminController : BaseAuthController
Right now I have a series of overrides in place for special cases... e.g. a controller that is typically only for admins can have an action method or two that advisors can use... I have the overrides defined as strings in an array.
public class BaseAuthController : BaseController
{
/// <summary>
/// Enter action names in here to have them ignored during login detection
/// </summary>
public string[] NoAuthActions = new string[] { };
/// <summary>
/// Actions only usable by Users+
/// </summary>
public string[] UserOnlyActions = new string[] { };
/// <summary>
/// Actions only usable by Advisors+
/// </summary>
public string[] AdvisorOnlyActions = new string[] { };
/// <summary>
/// Actions only usable by Admins+
/// </summary>
public string[] AdminOnlyActions = new string[] { };
.......
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
//special code here to determine what to do with requested action...
//verifies that user is logged in and meets requirements for method...
//if not, redirects out to another page...
}
}
At the controller level I have them defined like this...
public class GrowerController : BaseAdminController
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
UserOnlyActions = new string[] { "GrowthStageSelection" };
AdvisorOnlyActions = new string[] { "Landing", "SeedSelection", "UpdateProjection",
"NitrogenApplications", "DeleteNitrogen", "MassUpload",
"VerifyHolding", "ConfirmHolding", "DeleteHoldingDir", "DeleteHoldingFile" };
base.OnActionExecuting(filterContext);
}
//......
[HttpPost]
public ActionResult GrowthStageSelection(int growerID, int reportGrowthStageID = 0)
{
//code...
}
}
This system has actually worked out pretty well for us, but the problem for me has been that it feels messy. You have to define the methods one place, and override their authentication level elsewhere if necessary. If you change the method name you have to remember to change it elsewhere.
What I'd LOVE to be able to do is decorate the methods themselves with authentication specific attributes and do away the string-based definitions (or at least make them transparent and use List<string> dynamically or something). Here's an example of what I'm looking for...
[HttpPost]
[AdvisorAuthentication]
public ActionResult GrowthStageSelection(int growerID, int reportGrowthStageID = 0)
{
//code...
}
Problem is that I can't find a good way to achieve this with attributes. I've tried creating subclasses of ActionFilterAttribute but they run after my BaseAuthController's override for OnActionExecuting. At that point it's too late in the game to add new methods to the string lists dynamically, and moreover I can't even seem to access the current controller instance from the attributes.
Maybe this whole idea is off base. Can anyone point me in the right direction? Thanks.
Final solution
First, I went ahead and deleted all of my special controllers except for BaseController - I had no use for them anymore. I moved the current special authentication code from BaseAuthController into BaseController. Next, I defined a series of attributes for each of my authentication states:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class BaseAuthAttribute : Attribute
{
public AuthLevels AuthLevel { get; protected set; }
public BaseAuthAttribute(AuthLevels level)
{
this.AuthLevel = level;
}
public override string ToString()
{
return string.Format("Auth Required: {0}", this.AuthLevel.ToString());
}
}
public class UnauthenticatedAccess : BaseAuthAttribute
{
public UnauthenticatedAccess()
: base(AuthLevels.Unauthenticated)
{
}
}
public class UserAccess : BaseAuthAttribute
{
public UserAccess()
: base(AuthLevels.User)
{
}
}
public class AdvisorAccess : BaseAuthAttribute
{
public AdvisorAccess()
: base(AuthLevels.Advisor)
{
}
}
public class AdminAccess : BaseAuthAttribute
{
public AdminAccess()
: base(AuthLevels.Admin)
{
}
}
Then in my BaseController I modified the OnActionExecuting to check the current auth level of the logged in user (if any) against the attribute. This is much cleaner than it was before! (Note: SessionUser and AuthLevels are custom objects for our project - you won't have those)
public partial class BaseController : Controller
{
/// <summary>
/// Override security at higher levels
/// </summary>
protected bool SecurityOverride = false;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
BaseAuthAttribute authAttribute = filterContext.ActionDescriptor.GetCustomAttributes(false).OfType<BaseAuthAttribute>().FirstOrDefault();
if (authAttribute == null) //Try to get attribute from controller
authAttribute = filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(false).OfType<BaseAuthAttribute>().FirstOrDefault();
if (authAttribute == null) //Fallback to default
authAttribute = new UnauthenticatedAccess(); //By default, no auth is required for base controller
if (!SessionUser.LoggedIn
&& authAttribute.AuthLevel == AuthLevels.Unauthenticated)
{
SecurityOverride = true;
}
else if (SessionUser.LoggedIn
&& SessionUser.LoggedInUser.AuthLevel >= (int)authAttribute.AuthLevel)
{
SecurityOverride = true;
}
if (!SessionUser.LoggedIn && !SecurityOverride)
{
//Send to auth page here...
return;
}
else if (!SecurityOverride)
{
//Send somewhere else - the user does not have access to this
return;
}
base.OnActionExecuting(filterContext);
}
// ... other code ...
}
That's it! Now just put it to use like so...
[AdminAccess]
public class GrowerController : BaseController
{
public ActionResult Index()
{
//This method will require admin access (as defined for controller)
return View();
}
[AdvisorAccess]
public ActionResult Landing()
{
//This method is overridden for advisor access or greater
return View();
}
}

If I understood your question properly, you can implement your own custom attributes (not authorisation attributes) and in the overriden OnActionExecuting of the base controller, you can retrieve the custom attributes of the executing method and based on wich ones are defined you can take appropriate actions. So if a method has the [AdvisorAuthentication] you know that you need to check for those credentials before proceeding.
EDIT:
I don't have an example to point you to as this is something I have implemented in one of my projects. I have no access to that code now but here is an outline:
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
IEnumerable<MyCustomAttribute> attributes = filterContext.ActionDescriptor.GetCustomAttributes(false).OfType<MyCustomAttribute>();
foreach (MyCustomAttributeobj in attributes)
{
switch(MyCustomAttribute.AttribType){
case MyCustomeAttribute.AdvisorAuthentication:
break;
case MyCustomeAttribute.AdminAuthentication:
break;
}
}
}
You can implement just one custom attribute MyCustomAttribute and have it accept a parameter to indicate which authorization type you want. Like that the use of the attribute becomes [MyCustomAttribute("MyCustomeAttribute.AdminAuthentication")]

You can create different Authorize attributes extending IAuthorizationFilter and FilterAttribute something like this
public sealed class AuthenticateAdvisorAttribute : IAuthorizationFilter, FilterAttribute
{
public void OnAuthorization(AuthorizationContext filterContext)
{
//advisor specific logic goes here
}
}
public sealed class AuthenticateAdminAttribute : IAuthorizationFilter, FilterAttribute
{
public void OnAuthorization(AuthorizationContext filterContext)
{
//admin specific logic goes here
}
}
And then you can apply those attributes wherever you require to controller classes/actions
as
[AuthenticateAdmin]
public class AdminController : Controller
{
}
[AuthenticateAdvisor]
public class AdvisorController : Controller
{
}

Related

Redirect to Login Page if Session expired in ASP MVC net core

I am working in ASP net core MVC Application. My aim is to redirect to the login page if the session expired.
In my case, I am using several ajax call to load a page. Sometimes it is redirecting properly, but sometimes it is not doing so.
public class CheckSessionIsAvailable : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if (filterContext.HttpContext == null || filterContext.HttpContext.Session.GetString("UserID") == null)
{
//return RedirectToAction("Index", "Login");
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "Login",
action = "Index"
}));
}
}
}
Created an action filter CheckSessionIsAvailable and associated it with all the controllers.
[CheckSessionIsAvailable]
public class TestController : Controller
{
public IActionResult Index()
{
return View();
}
}
Am I doing anything wrong while creating Action Filter ?
You can achieve this by using the OnActionExecuting method of IActionFilter by redefining it in our custom class.
First add it to the services:
services.AddControllersWithViews(options =>
{
options.Filters.Add(typeof(RequestAuthenticationFilter));
});
Then create a custom class RequestAuthenticationFilter and define the OnActionExecuting method.
public class RequestAuthenticationFilter : IActionFilter
{
private readonly IHttpContextAccessor _httpContextAccessor;
public RequestAuthenticationFilter(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// OnActionExecuting
/// </summary>
/// <param name="context"></param>
public void OnActionExecuting(ActionExecutingContext context)
{
var session = _httpContextAccessor.HttpContext.Session.Get("UserId");
if (session == null)
{
context.Result = new RedirectToRouteResult(new RouteValueDictionary(new { action = "Login", controller = "Account", area = "Identity" }));
}
}
/// <summary>
/// OnActionExecuted
/// </summary>
/// <param name="context"></param>
public void OnActionExecuted(ActionExecutedContext context)
{
}
}

Redirect to Login on session expiry in MVC

How I can redirect to Login.cshtml from session_end of Global.asax on session expiry.
Response.redirect() not working there.
Please comment on it.
try this may be you will able to check the session is in process or expire
Check this
Answer is given here
check also
Here is the Class which overrides ActionFilterAttribute.
public class SessionExpireAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
// check sessions here
if( HttpContext.Current.Session["username"] == null )
{
filterContext.Result = new RedirectResult("~/Login/Index");
return;
}
base.OnActionExecuting(filterContext);
}
}
Then in action just add this attribute as shown :
[SessionExpire]
public ActionResult Index()
{
return Index();
}
Or Just add attribute only one time as :
[SessionExpire]
public class LoginController : Controller
{
public ActionResult Index()
{
return Index();
}
}

How to develop action filter controller or action method specific

i was trying to develop a custom action filter which will check cookie is enable or not. if cookie is not enable then redirect use to a specific error page.here is my code.
public class CheckCookieAttribute : FilterAttribute, IActionFilter
{
public string prmAction{get;set;}
public string prmController{get;set;}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if(filterContext.HttpContext.Request.Cookie["YourCookie"]==null)
{
filterContext.Result = controller.RedirectToAction(prmAction,prmController)
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
//The action filter logic - after
}
}
now i am using like
[CheckCookie(prmAction="MyAction",prmController="MyController")]
due to lack of good knowledge i am not being able to develop attribute driven check for cookie enable or disable.
i want to develop a code in such a way as a result i should not pass any controller name or action name. i like to use code like
[HttpPost]
[CheckCookieAttribute]
public ActionResult Save(Person oPerson)
{
return View();
}
[CheckCookieAttribute]
public class HomeController : Controller
{
public ActionResult Index()
{return View();}
public ActionResult About()
{return View();}
}
}
where i will not provide any name of controller or action name. just guide me what i need to change in my code. thanks
It seems that what you are trying to accomplish is already built into ASP.NET MVC.
I would use the [Authorize] attribute (http://msdn.microsoft.com/en-us/library/system.web.mvc.authorizeattribute(v=vs.108).aspx) where you want to check if the user has a cookie.
If you want to redirect the user to a specific controller/action when the user is not authorized, you can use the following attribute instead:
public class AuthorizeUserAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new
{
controller = "Error",
action = "Unauthorized"
})
);
}
}
See ASP.NET MVC 4 Custom Authorize Attribute with Permission Codes (without roles)
Then you would use it by using:
[HttpPost]
[AuthorizeUser]
public ActionResult Save(Person oPerson)
{
return View();
}
Or if you want exactly what you asked for you can do it this way:
public class CheckCookieAttribute : ActionFilterAttribute, IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.Cookies["YourCookie"] == null)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { {"controller", "MyController"}, {"action", "MyAction"}});
}
else
{
base.OnActionExecuting(filterContext);
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
//The action filter logic - after
}
}

Multiple attributes of same type fail when using Ninject's BindFilter<T>

As part of a identity-enabled authorization system, I'd like to use IAuhtorizationFilter and Attributes to restrict access to action methods in my controllers. I've got things working very well, partly due to help from the following resources:
Ninject Binding Attribute to Filter with Constructor Arguments
https://github.com/ninject/ninject.web.mvc/wiki/Filter-configurations
Custom Authorization MVC 3 and Ninject IoC
https://github.com/ninject/ninject.web.mvc/wiki/Dependency-injection-for-filters
However, when I try to decorate an action method with more than one of my attributes, I get an exception as follows (sorry for the formatting):
[InvalidOperationException: Sequence contains more than one element]
System.Linq.Enumerable.Single(IEnumerable`1 source) +2691369
Ninject.Web.Mvc.FilterBindingSyntax.c__DisplayClass15`1.b__14(IContext ctx, ControllerContext controllerContext, ActionDescriptor actionDescriptor) in c:\Projects\Ninject\Maintenance2.2\ninject.web.mvc\mvc3\src\Ninject.Web.Mvc\FilterBindingSyntax\FilterFilterBindingBuilder.cs:379
Ninject.Web.Mvc.FilterBindingSyntax.c__DisplayClass12.b__11(IContext ctx) in c:\Projects\Ninject\Maintenance2.2\ninject.web.mvc\mvc3\src\Ninject.Web.Mvc\FilterBindingSyntax\FilterFilterBindingBuilder.cs:358
Ninject.Parameters.c__DisplayClass6.b__4(IContext ctx, ITarget target) in c:\Projects\Ninject\Maintenance2.2\ninject\src\Ninject\Parameters\Parameter.cs:60
Ninject.Parameters.Parameter.GetValue(IContext context, ITarget target) in c:\Projects\Ninject\Maintenance2.2\ninject\src\Ninject\Parameters\Parameter.cs:88
Ninject.Activation.Providers.StandardProvider.GetValue(IContext context, ITarget target) in c:\Projects\Ninject\Maintenance2.2\ninject\src\Ninject\Activation\Providers\StandardProvider.cs:97
Ninject.Activation.Providers.c__DisplayClass2.b__1(ITarget target) in c:\Projects\Ninject\Maintenance2.2\ninject\src\Ninject\Activation\Providers\StandardProvider.cs:81
...
Here is a very simplified version of my code that demonstrates the problem in an MVC3 app:
Attribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class SampleAttribute : Attribute
{
private Guid typeId;
public bool IsAllowed { get; set; }
public SampleAttribute(bool IsAllowed)
{
this.IsAllowed = IsAllowed;
this.typeId = new Guid();
}
public override object TypeId
{
get
{
return (object)typeId;
}
}
}
Filter:
public class SampleFilter : IAuthorizationFilter, IMvcFilter
{
private bool isAllowed;
public bool AllowMultiple
{
get { return true; }
}
public int Order
{
get { return 0; }
}
public SampleFilter(bool isAllowed)
{
this.isAllowed = isAllowed;
}
public void OnAuthorization(AuthorizationContext filterContext)
{
if (!isAllowed)
throw new Exception("unauthorized");
}
}
Controller:
public class HomeController : Controller
{
[Sample(true)]
[Sample(false)]
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
}
The controller method above works as expected if one or the other of the Sample attributes on the Index() method is removed. Having both in place, generates the exception. I realize that in this simplified example, there isn't a situation that would call for both attributes, but it's simply for illustration.
What am I missing?
This is a known issue of Ninject 2.2. Please use 3.0 instead.
https://github.com/ninject/ninject.web.mvc/blob/master/mvc3/ReleaseNotes.txt

session becoming null in MVC AuthorizeAttribute

I am using an AuthorizeAttribute to check that users have an over 18 age cookie set to access pages.
This works fine, but I am extending in slightly now.
As all Views use this Attribute, I am using it to allow me to launch my site early.
If uses add ?VIEWSITE=true to any URL, it will set a Session variable, and allow them access to the site. Otherwise, they get directed to a holding page.
This works fine first time the page runs. But, I am using output caching on the page, and the next time the page loads, my httpcontext.session is null?
I've added an "Order" varible to my attributes to ensure they execute in the correct order:
[OfAge(Order = 1)]
[OutputCache(Order = 2, Duration = 2000, VaryByParam = "categoryName")]
Snipit from my Attribute:
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
HttpRequestBase req = httpContext.Request;
HttpResponseBase res = httpContext.Response;
DateTime Live_Date = new DateTime(2011, 07, 01, 07, 0, 0);
if (DateTime.Now > Live_Date || req.QueryString["VIEWSITE"] != null || httpContext.Session["VIEWSITE"] != null)
{
httpContext.Session["VIEWSITE"] = true;
Is there something I am missing here for me to be able to read/set session variables once a page is loaded from cache?
To be clear, it's httpContext.Session that is null, and not specifically httpContext.Session["VIEWSITE"]
3 years down the line and I ran into a similar issue. Now I'm no expert but I believe each controller context call is unique in it's own space, thus httpContext.Session would be null on a new call.
My issue came in the form of a logged in AD user I wanted to store (with his custom application permissions) in a session variable. I'm extending on the AuthorizationAttribute too, but when this filter is applied to a controller action, httpContext is null even though the user was saved.
For people battling with the same issue, the way around this is to create a base controller where this user and it's session state is kept throughout other controllers (inheriting the base controller).
ex.
My Model:
public class LoggedInUser
{
public somenamespace.userclass UserProfile { get; set; }
public List<somenamespace.user_permission_class> UserPermissions { get; set; }
}
My Base Controller:
public class ControllerBase : Controller
{
private LoggedInUser _LoginUser;
public LoggedInUser LoginUser
{
get
{
if (_LoginUser != null)
return _LoginUser;
if (Session["_LoginUser"] == null)
return null;
return Session["_LoginUser"] as LoggedInUser;
}
set
{
_LoginUser = value;
Session["_LoginUser"] = _LoginUser;
}
}
public void PerformUserSetup(string sUsername) // sUsername for testing another user, otherwise User.Identity will be used.
{
sUsername = string.IsNullOrEmpty(sUsername) ? User.Identity.Name : sUsername;
sUsername = (sUsername.IndexOf("\\") > 0) ? sUsername.Split('\\').ToArray()[1] : sUsername;
// Todo - SQL conversion to stored procedure
List<userclass> tmpUser = Root.Query<userclass>(/*sql to select user*/).ToList();
List<user_permission_class> tmpUserpermissions = Root.Query<user_permission_class>(/*sql to select user permissions*/).ToList();
LoggedInUser _LoginUser = new LoggedInUser();
_LoginUser.UserProfile = tmpUser.First();
_LoginUser.UserPermissions = tmpUserpermissions;
LoginUser = _LoginUser;
}
}
My HomeController (standard with any MVC example) :
public class HomeController : ControllerBase
{
[Authorize] // Standard AuthorizeAttribute (AD test)
public ActionResult Index()
{
if (Session["_LoginUser"] == null)
PerformUserSetup("");
return View();
}
}
My Custom permission checking filter which I'll use on any other controller action:
public class PermissionAuthorize : AuthorizeAttribute
{
private readonly string[] permissions;
public PermissionAuthorize(params string[] perms)
{
this.permissions = perms;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool auth = false;
if (httpContext.Session["_LoginUser"] == null)
{
// Do nothing as auth is false.
}
else
{
// Check permissions and set auth = true if permission is valid.
auth = true;
}
return auth;
}
/* not using
public override void OnAuthorization(AuthorizationContext filterContext)
{
var tmp = filterContext.HttpContext.Session;
}
*/
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// Todo - direct to "unauth page"
base.HandleUnauthorizedRequest(filterContext);
}
}
Usage:
public class Some_OtherController : /*PossibleNamespace?.*/ControllerBase
{
[PermissionAuthorize("somepermission")] // This was a CRUD application thus 1 permission per actionresult
public ActionResult ViewWhatever()
{
....
}
}

Resources