On my MVC4 internet application I am using the AccountController that comes by default along with roles etc.
So I have this controller in which I have defined roles to access the actions, example below.
public class SomeController : Controller
{
private SomeDbContext db = new LookbookDbContext();
//
// GET: /Default1/
[Authorize(Roles = "Administrator")]
public ActionResult Index()
{
return View(db.SomeTable.ToList());
}
...
}
What I wanted now is that, when a user/anonymous tries to access this Index action, get's a custom error view I have made instead of showing the Login form.
I have added this but it just does nothing. I keep getting the login form page. I changed it, for testing porpuses, to give me the default 401 error page but it doesn't work either.
public class CustomAuthorize : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
You should just be able to redirect to your custom error view from your attribute.
Example
public class UnAuthorizedRedirectAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.RequestContext.HttpContext.Response.Redirect("~/error/no-bacon");
}
}
Obviously, the first thing you need to do is make your custom view.
Now, I would reccomend making an action filter to handle this:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
}
else
{
filterContext.RequestContext.HttpContext.Response.Redirect("~/shared/error");
}
}
}
NOTE: This answer was added to the question. I'm moving it here to conform to site guidelines.
What I was missing was the [CustomAuthorize] attribute on my Actions. Once I have added that to the desired action it worked.
Related
I need to be able to log all actions that are called from my asp.net mvc application. How and what would be the best way to achieve this? Where I log it to whether it be the console or log file doesn't matter.
You could create your own class which inherits from ActionFilterAttribute and then override the OnActionExecuting method.
Example
public class LogActionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.RequestContext.RouteData.Values["Controller"];
var action = filterContext.RequestContext.RouteData.Values["Action"];
//
// Perform logging here
//
base.OnActionExecuting(filterContext);
}
}
public class HomeController : Controller
{
[LogAction]
public ActionResult Index()
{
return View();
}
}
Hope this helps!
Credit HeyMega for their answer. Here's an example of an expanded implementation I arrived at in MVC5.
public class LogActionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.RequestContext.RouteData.Values.ContainsKey("Controller") ? filterContext.RequestContext.RouteData.Values["Controller"].ToString() : null;
var action = filterContext.RequestContext.RouteData.Values.ContainsKey("Action") ? filterContext.RequestContext.RouteData.Values["Action"].ToString() : null;
var area = filterContext.RequestContext.RouteData.DataTokens.ContainsKey("Area") ? filterContext.RequestContext.RouteData.DataTokens["Area"].ToString() : null;
var user = filterContext.RequestContext.HttpContext.User.Identity.GetUserId();
Task.Run(() => Generic().AreaActionLog(user, area, controller, action));
base.OnActionExecuting(filterContext);
}
}
I chose to separate the method doing the actual logging into a separate process, if anything goes wrong with the Database interaction, or the DB interaction takes several seconds, the UI is uninterrupted.
You can then decorate the entire controller with [LogAction] attribute like so.
[LogAction]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Contact()
{
return View();
}
}
Or selectively apply the attribute by decorating individual methods:
public class HomeController : Controller
{
[LogAction]
public ActionResult Index_Logs_Things()
{
return View();
}
}
Hope this helps someone.
You could try Audit.NET library with its Audit.MVC and the different data providers to store the logs on files, eventlog, sql, redis, mongo, and much more.
With the MVC extension you just need to decorate your controllers or actions with an attribute:
[Audit]
public class HomeController : Controller
{ ... }
Execute a static configuration to set the output of your logs:
Audit.Core.Configuration.Setup()
.UseFileLogProvider(_ => _
.Directory(#"C:\Logs"));
And it will provide the infrastructure to log the interactions with your MVC application.
I commonly need to authorize a particular parameter to be evaluated in a service call within an action in MVC5. For instance, let's say that my action is public ActionResult Edit(string partnerName).
Today, I handle this by always evaluating if (!User.CanAccessPartnerModule(THIS_MODULE_ID, partnerName)) throw new UnauthorizedException();
However, I would like to be able to do something like this:
[Authorize(Roles = THIS_MODULE_ID)]
public ActionResult Edit([AuthorizePartnerModule(THIS_MODULE_ID)] string partnerName)
{
...
}
To be clear, 1) I don't think the AuthorizeAttribute would be necessary if this were implemented as I envision, and 2) the thing that doesn't exist is the AuthorizePartnerModuleAttribute.
Is there a ready-made attribute or tutorial that explains how this may be accomplished? And if not, is this not advisable to do?
You could extend authorization with a custom authorization filter by creating a subclass of AuthorizeAttribute
using System.Web;
using System.Web.Mvc;
namespace Filters
{
public class AuthorizePartnerModule : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// getting the parameter from the request
string partnerName = httpContext.Request.Params["groupId"].ToString();
// custom validation
return User.CanAccessPartnerModule(THIS_MODULE_ID, partnerName);
}
}
}
And then, you could validate your action method with:
[AuthorizePartnerModule(Roles = THIS_MODULE_ID)]
public ActionResult Edit(string partnerName)
{
...
}
Another option would be to create a custom ActionFilter (an implementation of IActionFilter). An ActionFilter implements two methods:
OnActionExecuting is executed right before the action method
OnActionExecuted is executed right after the action method execution.
So, you could make the necessary validation with something like:
using System.Web.Mvc;
namespace Filters {
public class AuthorizePartnerModule : FilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
// getting the parameter from the request
string partnerName = filterContext.ActionParameters["partnerName"].ToString();
// custom validation
if (!User.CanAccessPartnerModule(THIS_MODULE_ID, partnerName))
{
filterContext.Result = new HttpNotFoundResult();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// do nothing
}
}
}
In this case, however ,you would have to validate like that:
[Authorize(Roles = THIS_MODULE_ID)]
[AuthorizePartnerModule]
public ActionResult Edit(string partnerName)
{
...
}
I have a scenario i need to check security for each menu item if user 'A' is allowed to access this menu or not and for that reason i created a class which is inherited with ActionFilterAttribute
public class SecurityFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Log("OnActionExecuting", filterContext.RouteData);
}
}
and using this class on my controller
[SecurityFilter]
public class XYZController : Controller
{
public ActionResult Index() {
return View();
}
}
Now the problem is in my View i have #Html.Action() calls e.g
#Html.Action("COM")
which results in calling onActionExecuting Method again, i Just want it to call it one time when menu link is clicked and for that method only where menu is redirecting not the other Action method which is render inside view
When called using #Html.Action the IsChildAction property of the filterContext will be true. You can rely on it to determine whether you actually want to do something or not:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.IsChildAction)
return;
Log("OnActionExecuting", filterContext.RouteData);
}
See MSDN
I would like to protect my public method from being called by a user.
Because I'm calling the action from an ajax script I can't use any access modifiers, (private, protected etc).
Also, [HttpPost] doesn't stop the user from doing a fake request.
Anyone got a solution?
Thanks
Create an action filter that allows action methods to be called by AJAX only
namespace MyFilters
{
[AttributeUsage(AttributeTargets.Method)]
public class AjaxOnlyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 404;
filterContext.Result = new HttpNotFoundResult();
}
else
{
base.OnActionExecuting(filterContext);
}
}
}
}
Then apply this to the action method
[AjaxOnly]
public JsonResult DoSomething()
{
....
Put the [Authorize(Roles="admin")] on a view, it works as the user is redirected, however, they are always redirected to the login view despite the fact they are already logged in.
How can I change this so it goes to an error page or something similar?
you can create a custom authorize attribute like this
public class CustomAuthorize : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
base.HandleUnauthorizedRequest(filterContext);
}
else
{
filterContext.Result = new RedirectToRouteResult(new
RouteValueDictionary(new { controller = "Home", action = "UnAuthorized" }));
}
}
}
and use it like this
[CustomAuthorize(Roles="admin")]
Hope this helps
Instead of the duplicate offered I used code from the question: Prevent FormsAuthenticationModule of intercepting ASP.NET Web API responses and modified accordingly:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeCustom : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
filterContext.Result = new RedirectResult("/Error/Unauthorized");
}
else
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
}
base.HandleUnauthorizedRequest(filterContext);
}
}
}
Then just created a view for the "/Error/Unauthorized" route and changed the attribute [Authorize] to [AuthorizeCustom]. Now unauthorized people will be redirected to login as expected and people who aren't in roles are redirected to a custom view.