[ACCEPTED]-Redirecting unauthorized controller in ASP.NET MVC-authorization

Accepted answer
Score: 71

Create a custom authorization attribute 8 based on AuthorizeAttribute and override 7 OnAuthorization to perform the check how 6 you want it done. Normally, AuthorizeAttribute 5 will set the filter result to HttpUnauthorizedResult 4 if the authorization check fails. You could 3 have it set it to a ViewResult (of your 2 Error view) instead.

EDIT: I have a couple of 1 blog posts that go into more detail:

Example:

    [AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false )]
    public class MasterEventAuthorizationAttribute : AuthorizeAttribute
    {
        /// <summary>
        /// The name of the master page or view to use when rendering the view on authorization failure.  Default
        /// is null, indicating to use the master page of the specified view.
        /// </summary>
        public virtual string MasterName { get; set; }

        /// <summary>
        /// The name of the view to render on authorization failure.  Default is "Error".
        /// </summary>
        public virtual string ViewName { get; set; }

        public MasterEventAuthorizationAttribute()
            : base()
        {
            this.ViewName = "Error";
        }

        protected void CacheValidateHandler( HttpContext context, object data, ref HttpValidationStatus validationStatus )
        {
            validationStatus = OnCacheAuthorization( new HttpContextWrapper( context ) );
        }

        public override void OnAuthorization( AuthorizationContext filterContext )
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException( "filterContext" );
            }

            if (AuthorizeCore( filterContext.HttpContext ))
            {
                SetCachePolicy( filterContext );
            }
            else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                // auth failed, redirect to login page
                filterContext.Result = new HttpUnauthorizedResult();
            }
            else if (filterContext.HttpContext.User.IsInRole( "SuperUser" ))
            {
                // is authenticated and is in the SuperUser role
                SetCachePolicy( filterContext );
            }
            else
            {
                ViewDataDictionary viewData = new ViewDataDictionary();
                viewData.Add( "Message", "You do not have sufficient privileges for this operation." );
                filterContext.Result = new ViewResult { MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData };
            }

        }

        protected void SetCachePolicy( AuthorizationContext filterContext )
        {
            // ** IMPORTANT **
            // Since we're performing authorization at the action level, the authorization code runs
            // after the output caching module. In the worst case this could allow an authorized user
            // to cause the page to be cached, then an unauthorized user would later be served the
            // cached page. We work around this by telling proxies not to cache the sensitive page,
            // then we hook our custom authorization code into the caching mechanism so that we have
            // the final say on whether a page should be served from the cache.
            HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
            cachePolicy.SetProxyMaxAge( new TimeSpan( 0 ) );
            cachePolicy.AddValidationCallback( CacheValidateHandler, null /* data */);
        }


    }
Score: 28

You can work with the overridable HandleUnauthorizedRequest inside 3 your custom AuthorizeAttribute

Like this:

protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    // Returns HTTP 401 by default - see HttpUnauthorizedResult.cs.
    filterContext.Result = new RedirectToRouteResult(
    new RouteValueDictionary 
    {
        { "action", "YourActionName" },
        { "controller", "YourControllerName" },
        { "parameterName", "YourParameterValue" }
    });
}

You can also do something 2 like this:

private class RedirectController : Controller
{
    public ActionResult RedirectToSomewhere()
    {
        return RedirectToAction("Action", "Controller");
    }
}

Now you can use it in your HandleUnauthorizedRequest method 1 this way:

filterContext.Result = (new RedirectController()).RedirectToSomewhere();
Score: 10

The code by "tvanfosson" was giving me 6 "Error executing Child Request".. I have 5 changed the OnAuthorization like this:

public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);

        if (!_isAuthorized)
        {
            filterContext.Result = new HttpUnauthorizedResult();
        }
        else if (filterContext.HttpContext.User.IsInRole("Administrator") || filterContext.HttpContext.User.IsInRole("User") ||  filterContext.HttpContext.User.IsInRole("Manager"))
        {
            // is authenticated and is in one of the roles 
            SetCachePolicy(filterContext);
        }
        else
        {
            filterContext.Controller.TempData.Add("RedirectReason", "You are not authorized to access this page.");
            filterContext.Result = new RedirectResult("~/Error");
        }
    }

This 4 works well and I show the TempData on error 3 page. Thanks to "tvanfosson" for the code 2 snippet. I am using windows authentication 1 and _isAuthorized is nothing but HttpContext.User.Identity.IsAuthenticated...

Score: 5

I had the same issue. Rather than figure 2 out the MVC code, I opted for a cheap hack 1 that seems to work. In my Global.asax class:

member x.Application_EndRequest() =
  if x.Response.StatusCode = 401 then 
      let redir = "?redirectUrl=" + Uri.EscapeDataString x.Request.Url.PathAndQuery
      if x.Request.Url.LocalPath.ToLowerInvariant().Contains("admin") then
          x.Response.Redirect("/Login/Admin/" + redir)
      else
          x.Response.Redirect("/Login/Login/" + redir)
Score: 2

This problem has hounded me for some days 33 now, so on finding the answer that affirmatively 32 works with tvanfosson's answer above, I 31 thought it would be worthwhile to emphasize 30 the core part of the answer, and address 29 some related catch ya's.

The core answer 28 is this, sweet and simple:

filterContext.Result = new HttpUnauthorizedResult();

In my case I inherit 27 from a base controller, so in each controller 26 that inherits from it I override OnAuthorize:

protected override void OnAuthorization(AuthorizationContext filterContext)
{
    base.OnAuthorization(filterContext);
    YourAuth(filterContext); // do your own authorization logic here
}

The 25 problem was that in 'YourAuth', I tried 24 two things that I thought would not only 23 work, but would also immediately terminate 22 the request. Well, that is not how it works. So 21 first, the two things that DO NOT work, unexpectedly:

filterContext.RequestContext.HttpContext.Response.Redirect("/Login"); // doesn't work!
FormsAuthentication.RedirectToLoginPage(); // doesn't work!

Not 20 only do those not work, they don't end the 19 request either. Which means the following:

if (!success) {
    filterContext.Result = new HttpUnauthorizedResult();
}
DoMoreStuffNowThatYouThinkYourAuthorized();

Well, even 18 with the correct answer above, the flow 17 of logic still continues! You will still 16 hit DoMoreStuff... within OnAuthorize. So 15 keep that in mind (DoMore... should be in 14 an else therefore).

But with the correct 13 answer, while OnAuthorize flow of logic 12 continues till the end still, after that 11 you really do get what you expect: a redirect 10 to your login page (if you have one set 9 in Forms auth in your webconfig).

But unexpectedly, 1) Response.Redirect("/Login") does 8 not work: the Action method still gets called, and 2) FormsAuthentication.RedirectToLoginPage(); does 7 the same thing: the Action method still 6 gets called!

Which seems totally wrong to 5 me, particularly with the latter: who would 4 have thought that FormsAuthentication.RedirectToLoginPage 3 does not end the request, or do the equivalant 2 above of what filterContext.Result = new 1 HttpUnauthorizedResult() does?

Score: 1

You should build your own Authorize-filter 1 attribute.

Here's mine to study ;)

Public Class RequiresRoleAttribute : Inherits ActionFilterAttribute
    Private _role As String

    Public Property Role() As String
        Get
            Return Me._role
        End Get
        Set(ByVal value As String)
            Me._role = value
        End Set
    End Property

    Public Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
        If Not String.IsNullOrEmpty(Me.Role) Then
            If Not filterContext.HttpContext.User.Identity.IsAuthenticated Then
                Dim redirectOnSuccess As String = filterContext.HttpContext.Request.Url.AbsolutePath
                Dim redirectUrl As String = String.Format("?ReturnUrl={0}", redirectOnSuccess)
                Dim loginUrl As String = FormsAuthentication.LoginUrl + redirectUrl

                filterContext.HttpContext.Response.Redirect(loginUrl, True)
            Else
                Dim hasAccess As Boolean = filterContext.HttpContext.User.IsInRole(Me.Role)
                If Not hasAccess Then
                    Throw New UnauthorizedAccessException("You don't have access to this page. Only " & Me.Role & " can view this page.")
                End If
            End If
        Else
            Throw New InvalidOperationException("No Role Specified")
        End If

    End Sub
End Class
Score: 1

Would have left this as a comment but I 7 need more rep, anyways I just wanted to 6 mention to Nicholas Peterson that perhaps 5 passing the second argument to the Redirect 4 call to tell it to end the response would 3 have worked. Not the most graceful way 2 to handle this but it does in fact work.

So

filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true);

instead 1 of

filterContext.RequestContext.HttpContext.Response.Redirect("/Login);

So you'd have this in your controller:

 protected override void OnAuthorization(AuthorizationContext filterContext)
 {
      if(!User.IsInRole("Admin")
      {
          base.OnAuthorization(filterContext);
          filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true);
      }
 }
Score: 1

Perhaps you get a blank page when you run 7 from Visual Studio under development server 6 using Windows authentication (previous topic).

If you deploy 5 to IIS you can configure custom error pages 4 for specific status codes, in this case 3 401. Add httpErrors under system.webServer:

<httpErrors>
  <remove statusCode="401" />
  <error statusCode="401" path="/yourapp/error/unauthorized" responseMode="Redirect" />
</httpErrors>

Then 2 create ErrorController.Unauthorized method 1 and corresponding custom view.

More Related questions