Click here to Skip to main content
Click here to Skip to main content

Manage security and redirection for non authorized access in MVC

, 18 Jul 2012
Rate this:
Please Sign up or sign in to vote.
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 :

 /// <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.

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.

[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)
    {
        …
    }
}  
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)

About the Author

Yves Vaillancourt
Team Leader Larochelle Groupe Conseil
Canada Canada
No Biography provided
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 PinmemberAli Javani12-May-13 1:44 
GeneralRe: My vote of 5 PinmemberYves Vaillancourt14-May-13 5:12 
QuestionHandling Areas PinmemberMember 884661817-Jul-12 12:25 
AnswerRe: Handling Areas PinmemberYves Vaillancourt18-Jul-12 5:21 
QuestionRedirected to default page Pinmemberzbkkzdsp12-Apr-12 4:49 
Hello.
Great code. Thank you.
 
In my case I use forms authentication
<authentication mode="Forms">
  <forms loginUrl="~/Account/LogOn" timeout="2880" />
</authentication>
 
When I use
public class HttpUnauthorizedWithRedirectResult : HttpUnauthorizedResult
{
    public string ViewName { get; set; }
 

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");
        context.HttpContext.Response.StatusCode = StatusCode;
 
        var viewResult = new ViewResult
        {
            ViewName = ViewName
        };
        viewResult.ExecuteResult(context);
    }
}
I get first redirected to my prepared "Unauthorized" View and then autmatically to standard login page from web.config "~/Account/LogOn". How to make it statys on my view, not go to login url?
Thanks in advance for any help.
AnswerRe: Redirected to default page PinmemberYves Vaillancourt27-Apr-12 8:34 
SuggestionSuggested Change PinmemberMarcB200523-Mar-12 13:00 
GeneralRe: Suggested Change PinmemberYves Vaillancourt26-Mar-12 10:26 
Questionwishes Pinmemberprestlee15-Mar-12 4:19 
Questiona humble request from student Pinmemberprestlee13-Mar-12 23:48 
AnswerRe: a humble request from student PinmemberYves Vaillancourt14-Mar-12 0:27 
GeneralRe: a humble request from student Pinmemberprestlee15-Mar-12 4:17 
AnswerRe: a humble request from student PinmemberYves Vaillancourt14-Mar-12 1:19 
Questionn2012 comes, in order to thank everyone, characteristic, novel style, varieties, low price and good quality, and the low sale price. Thank everyone ==== ( http://www.fullmalls.com ) ===== ==== ( http://www.fullmalls.com ) ===== $33 True Religion Pingroupkjlfdgehe9-Mar-12 15:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 18 Jul 2012
Article Copyright 2012 by Yves Vaillancourt
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid