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

Manage security and redirection for non authorized access in MVC

By , 18 Jul 2012
 

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
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberAli Javani12 May '13 - 1:44 
Thanks I Use That
GeneralRe: My vote of 5memberYves Vaillancourt14 May '13 - 5:12 
Thanks for the vote! Big Grin | :-D
QuestionHandling AreasmemberMember 884661817 Jul '12 - 12:25 
Do you have a thought on how to modify the HttpUnauthorizedWithRedirectResult class to handle areas and controllers? Ideally, I'd like to have a single unauthorized view per site rather than it apparently redirecting to a specific area / controller.
AnswerRe: Handling AreasmemberYves Vaillancourt18 Jul '12 - 5:21 
I modified the post to help you. You can download the source code, it now compiles.
 
Hope that it will help you! Have a nice day.
QuestionRedirected to default pagememberzbkkzdsp12 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 pagememberYves Vaillancourt27 Apr '12 - 8:34 
Sorry I was on paternity leave for the last month... I didn't have time to test my answer, but if you try this code, I think that it will works.
 
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 = context.HttpContext.Request.IsAuthenticated
                                                      ? 200
                                                      : StatusCode;
 
        var viewResult = new ViewResult
                             {
                                 ViewName = ViewName
                             };
        viewResult.ExecuteResult(context);
    }
}

SuggestionSuggested ChangememberMarcB200523 Mar '12 - 13:00 
When using your approach with Windows Authentication, the application would prompt for credentials whenever trying to access an unauthorized resources (because the browser was being sent an http 401 code). This is confusing to users as they are allready authenticated.
 
I got around this by making a small change to the
 
class HttpUnauthorizedWithRedirectResult as follows:
 
if (context.HttpContext.Request.IsAuthenticated)
context.HttpContext.Response.StatusCode = 200;
else
context.HttpContext.Response.StatusCode = StatusCode;
 
Thank you for your useful code.
GeneralRe: Suggested ChangememberYves Vaillancourt26 Mar '12 - 10:26 
Thanks, I got the problem last week when I change project configuration to use IIS. I had thought about changing the status, but I had not thought to validate if the user was authenticated.
Questionwishesmemberprestlee15 Mar '12 - 4:19 
thanU very much sir...
Questiona humble request from studentmemberprestlee13 Mar '12 - 23:48 
hi sir,good evenig...
      i am very thankfull to you   to posted this aritcle.(Manage security and redirection for non authorized access in MVC),but this project .sln file is not excute in my vs2010..
could you please tell the procedure how to excute.   (i have vs2010 4.0 and sql server 2008)..awaiting for your valuable response..
thnaking you ...
AnswerRe: a humble request from studentmemberYves Vaillancourt14 Mar '12 - 0:27 
Hi,
Yes, I can help you. You have to create an empty database in SQL, modify the connection string "SecurityStore" in Web.Config. In command line, execute aspnet_regsql.exe[^] to create membership, profile and role tables. You can found here[^] a introduction to membership security.
 
Hope that I help you.
GeneralRe: a humble request from studentmemberprestlee15 Mar '12 - 4:17 
thnakU very much sir..
AnswerRe: a humble request from studentmemberYves Vaillancourt14 Mar '12 - 1:19 
I forgot something, you have to modify the membership, profile and roleManager section in Web.Config. Replace those section with :
 
    <membership>
      <providers>
        <clear />
        <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="SecurityStore" applicationName="MvcSecurityApplication" />
      </providers>
    </membership>
    <profile>
      <providers>
        <clear />
        <add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="SecurityStore" applicationName="MvcSecurityApplication" />
      </providers>
    </profile>
    <roleManager enabled="true">
      <providers>
        <clear />
        <add name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="SecurityStore" applicationName="MvcSecurityApplication" />
        <add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="MvcSecurityApplication" />
      </providers>
    </roleManager>
 
You can remove all classes in the project directory Tools/SqlCeMembership.
 
To manage your own security, select the web project in solution explorer, click menu Project -> ASP.NET Configuration option.
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 Religiongroupkjlfdgehe9 Mar '12 - 15:28 
n2012 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 jeans, Ed Hardy jeans,LV,Coogi jeans,Affliction jeans
 
$30 Air Jordan shoes,Shox shoes,Gucci,LV shoes
 
50%Discount winter fashion :Sandle,t-shirt,caps,jerseys,handbag and brand watches!!!
 
$15 Ed Hardy ,LV ,Gucci Bikini
 
$15 Polo, Ed Hardy, Gucci, LV, Lacoste T-shirts
 
$25 Coach,Gucci,LV,Prada,Juicy,Chanel handbag,
 
$10 Gucci,Ed Hardy sunglasses
 
$9 New Era caps.
 
give you the unexpected harvest
 
==== ( http://www.fullmalls.com ) =====
 
==== ( http://www.fullmalls.com ) =====
 
==== ( http://www.fullmalls.com ) =====
 
==== ( http://www.fullmalls.com ) =====
 
==== ( http://www.fullmalls.com ) =====
 
==== ( http://www.fullmalls.com ) =====

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 18 Jul 2012
Article Copyright 2012 by Yves Vaillancourt
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid