Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / security

Manage security and redirection for non authorized access in MVC

4.50/5 (7 votes)
18 Jul 2012CPOL2 min read 57.7K   1.4K  
How to apply security and redirection to a view when a user cannot access a controller or a controller action in MVC

Introduction

Applying security and redirection to a view when a user cannot access a controller or a controller action in MVC. I chose the following solution because using integrated security with ASP.NET user / privileges is the easiest way to secure an application. Moreover, it fits very well with the third party tools as Telerik. For example, the Telerik menu automatically adjusts without a line of code by the simple fact that a user does not have the role to access a controller or action.

Using the code

The easiest and flexible way I found is to create me a new attribute inherited from the System.Web.Mvc.AuthorizeAttribute class. There, I add three properties, ActionOrViewName, Controller and Area.  You can set a viewname with or without an area il you have a shared view at the root or in a specific area.  Or, you can set an action and a controller, with or without an area to redirect to action.  I modify the behavior of the method HandleUnauthorizedRequest, as follow :

C#
 /// <summary>
///   Use this class instead of Authorize to redirect to a spcific view on unauthorized access.
///   If this attribute is used on a child action, it does the base result else, it redirect
///   to the specified view. The default view is UnauthorizedAccess, but can be overriden with 
///   the ActionOrViewName property.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class AuthorizeWithRedirectToViewAttribute : AuthorizeAttribute
{
    #region Private Fields

    private const string DefaultActionOrViewName = "UnauthorizedAccess";
    private string _actionOrViewName;
    #endregion

    #region Properties

    /// <summary>
    ///   The name of the view to render on authorization failure. Default is 
    ///   "UnauthorizedAccess".
    /// </summary>
    public string ActionOrViewName
    {
        get
        {
            return string.IsNullOrWhiteSpace(_actionOrViewName)
                       ? DefaultActionOrViewName
                       : _actionOrViewName;
        }
        set { _actionOrViewName = value; }
    }

    public string Controller { get; set; }
    public string Area { get; set; }

    #endregion

    #region Overrides

    /// <summary>
    ///   Processes HTTP requests that fail authorization.
    /// </summary>
    /// <param name="filterContext"> Encapsulates the information for using 
    ///   <see cref="T:System.Web.Mvc.AuthorizeAttribute" /> . The 
    ///   <paramref name="filterContext" /> object contains the controller, HTTP context, 
    ///   request context, action result, and route data. </param>
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.IsChildAction)
            base.HandleUnauthorizedRequest(filterContext);
        else
        {
            var factory = new HttpUnauthorizedWithRedirectToResultFactory();
            filterContext.Result = factory.GetInstance(
                                                       Area, 
                                                       Controller, 
                                                       ActionOrViewName);
        }
    }

    #endregion
}

This way, instead of get a blank page, I replace it by a customized Mvc ActionResult, witch inherits from System.Web.Mvc.HttpUnauthorizedResult class. The new result will get all necessary parameters to render the view.

C#
public abstract class HttpUnauthorizedWithRedirectToResultBase : HttpUnauthorizedResult
{
    protected ActionResult _result;

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        if (context.HttpContext.Request.IsAuthenticated)
        {
            context.HttpContext.Response.StatusCode = 200;
            InitializeResult(context);
            _result.ExecuteResult(context);
        }
        else
            base.ExecuteResult(context);
    }

    protected abstract void InitializeResult(ControllerContext context);
}

public class HttpUnauthorizedWithRedirectToViewResult 
             : HttpUnauthorizedWithRedirectToResultBase
{
    #region Ctors

    public HttpUnauthorizedWithRedirectToViewResult(string viewName, string area)
    {
        _viewName = string.IsNullOrWhiteSpace(viewName) ? viewName : viewName.Trim();
        _area = string.IsNullOrWhiteSpace(area) ? area : area.Trim();
    }

    #endregion

    #region Private Fields

    private readonly string _area;
    private readonly string _viewName;

    #endregion

    #region Overrides of HttpUnauthorizedWithRedirectToResultBase

    protected override void InitializeResult(ControllerContext context)
    {
        SetAreaRouteData(context);
        _result = new ViewResult
                      {
                          ViewName = _viewName,
                      };
    }

    #endregion

    #region Methods

    private void SetAreaRouteData(ControllerContext context)
    {
        if (context.RequestContext.RouteData.DataTokens.ContainsKey("area"))
        {
            if (!string.IsNullOrWhiteSpace(_area))
                context.RequestContext.RouteData.DataTokens["area"] = _area;
        }
        else
            context.RequestContext.RouteData.DataTokens.Add("area", _area);
    }

    #endregion
}

public class HttpUnauthorizedWithRedirectToRouteResult 
             : HttpUnauthorizedWithRedirectToResultBase
{
    #region Ctors

    public HttpUnauthorizedWithRedirectToRouteResult(string action, string controller, string area)
    {
        _action = string.IsNullOrWhiteSpace(action) ? action : action.Trim();
        _controller = string.IsNullOrWhiteSpace(controller) ? controller : controller.Trim();
        _area = string.IsNullOrWhiteSpace(area) ? area : area.Trim();
    }

    #endregion

    #region Private Fields

    private readonly string _action;
    private readonly string _area;
    private readonly string _controller;

    #endregion

    #region Overrides of HttpUnauthorizedWithRedirectToResultBase

    protected override void InitializeResult(ControllerContext context)
    {
        _result = new RedirectToRouteResult(new RouteValueDictionary
                                                {
                                                    {"area", _area},
                                                    {"controller", _controller},
                                                    {"action", _action}
                                                });
    }

    #endregion
}

public class HttpUnauthorizedWithRedirectToResultFactory
{
    public HttpUnauthorizedWithRedirectToResultBase GetInstance(string area, 
           string controller, string actionOrViewName)
    {
        if (string.IsNullOrWhiteSpace(actionOrViewName))
            throw new ArgumentException("You must set an actionOrViewName");

        if(string.IsNullOrWhiteSpace(controller) )
            return new HttpUnauthorizedWithRedirectToViewResult(actionOrViewName, area);
        return new HttpUnauthorizedWithRedirectToRouteResult(actionOrViewName, controller, area);
    }
} 

I modify the ExecuteResult method behavior to get same reaction as a ViewResult or an RedirectToRouteResult. In this case, the view of redirection does't receive any value, it shows only a generic message, but it can be more complex. To do this, you simply have to add a model or use the view bag. 

Why inherit from HttpUnauthorizedResult class? Why not simply return a ViewResult in HandleUnauthorizedRequest method? Because I'm using Telerik third party, Telerik verify if the obtained result is an HttpUnauthorizedResult to show or hide menu items. 

It's important to verify the filterContext.IsChildAction to preserve the expected result on child action. This way, when the user doesn't have permission, MVC will simply hide child section.

In application, the attribute is placed either on the declaration of the class or action method.

C#
[AuthorizeWithRedirectToView(Roles = "ReaderRole,WriterRole,SpecialRole")]
public class MyController : Controller
{

    [AuthorizeWithRedirectToView(Roles = " ReaderRole , WriterRole , SpecialRole "), 
           Controller = "Security", Area = "MyArea")]
    public ViewResult Details(Guid id)
    {
        …
    }

    [ChildActionOnly]
    [AuthorizeWithRedirectToView(Roles = " SpecialRole ")]
    public PartialViewResult ChildDetails(Guid id)
    {
        …
    }

    // To define a view other than the default "UnauthorizedAccess".
    [AuthorizeWithRedirectToView(Roles = " WriterRole ", ActionOrViewName = "OtherUnauthorizedAccess")]
    public ViewResult Details(ViewModel viewModel)
    {
        …
    }
}  
ASP.NET
UnauthorizedAccess.cshtml
@{
    ViewBag.Title = "Unauthorized Access";
}

<h2>Unauthorized access.</h2>

<p>
Sorry, you lack privileges to access this page. <a href="javascript:history.go(-1)">Click here</a> to go back.
</p> 

In conclusion...

I hope this article has helped you or help you in the future. I adjust my solution to support Asp.Net Role Based security on a Sql Compact database and run in stand-alone mode. Feel free if you have any suggestions or comments! You can download the full version of the project here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)