I am writing an MVC 3 app where users will be able to log in and manage their data. I want to prevent users from viewing or tampering with other user's data. My first instinct was to just verify access to the relevant object in each action method like this:
public ActionResult ShowDetails(int objectId)
{
DetailObject detail = _repo.GetById(objectId);
if (detail.User.UserID != (Guid)Membership.GetUser().ProviderUserKey)
{
return RedirectToAction("LogOff", "Account");
}
}
This works fine, but I thought it might be better to put the object authorization code into a custom Authorize attribute derived from AuthorizeAttribute, which I could then apply to the controller. Unfortunately, I have not been able to find a way to access the action method parameters from within my custom Authorize attribute. Instead, the only way I have found to access the incoming objectId is by examining httpContext.Request or filterContext.RequestContext.RouteData.Values:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
private int _objectId = 0;
private IUnitOfWork _unitOfWork;
public MyAuthorizeAttribute(IUnitOfWork uow)
{
_unitOfWork = uow;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
int.TryParse((string) filterContext.RequestContext.RouteData.Values["id"], out _objectId);
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
int objectId = 0;
if (httpContext.Request.Params.AllKeys.Contains("id", StringComparer.InvariantCultureIgnoreCase))
{
int.TryParse(httpContext.Request[idKey], out objectId);
}
if (objectId != 0)
{
if (!IsAuthorized(objectId, httpContext.User.Identity.Name))
{
return false;
}
}
if (_objectId != 0)
{
if (!IsAuthorized(objectId, httpContext.User.Identity.Name))
{
return false;
}
}
return base.AuthorizeCore(httpContext);
}
private bool IsAuthorized(int objectId, string userName)
{
DetailObject detail;
detail = _unitOfWork.ObjectRepository.GetById(objectId);
if (detail == null)
{
return false;
}
if (userName != detail.User.UserName)
{
return false;
}
return true;
}
}
I find this approach to be very clunky. I really don't want to have to poke around in the RouteData or Request objects; it would be much cleaner to be able to access the action method parameters since model binding would have already pulled out the relevant data from the RouteData and Request.
I know I can access action method parameters from a custom Action Filter (as detailed here), but shouldn't data authorization code be placed in an Authorize Filter? The more examples I see of Authorize filters, the more I get the impression that they are intended only to handle roles.
My main question is: How do I access action method parameters from my custom Authorize Attribute?