17

I overrides the class to perform custom Authorization

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAuthenticated)
        {
            filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult(403);
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
}

now in web.config i have configured the 403 error page

<customErrors defaultRedirect="/Shared/Error" mode="On">
  <error statusCode="403" redirect="/Shared/UnAuthorize" />
</customErrors>

but the browser still shows me default error page for 403, what i am missing here, any idea

Saboor Awan
  • 1,567
  • 4
  • 24
  • 37

7 Answers7

12

I know this is very old question, but I'm posting for someone who might have same issue. Like me, I had same issue and solved it. If you want to trigger the customErrors element in web.config, you can try below.

protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
    throw new HttpException(403, "Forbidden");
}
genki98
  • 680
  • 1
  • 11
  • 31
11

Just a small hint/note besides Max B. answer:

When I'm using custom errors I make an ErrorsController, and a UnAuthorize ActionResult and do the following:

<error statusCode="403" redirect="/Errors/UnAuthorize" />

This way I can add extra information or do other actions in my controller, for example:

  • Like logging to the database that someone tried to access an authenticated area.
  • Error counting.
  • Maybe a bug or report form that they can use to send the admin information.
  • ...

This way you have some more control on what's happening.

Kevin Cloet
  • 2,956
  • 1
  • 19
  • 36
  • actually i am doing the same, Shared is controller and Unauthorized is action, but still i am receiving the same default http 403 page error, not my defined page – Saboor Awan Aug 11 '11 at 09:40
  • 1
    very helpfull link http://stackoverflow.com/questions/2504923/how-to-redirect-authorize-to-loginurl-only-when-roles-are-not-used – Saboor Awan Aug 16 '11 at 11:24
  • 2
    @SaboorAwan. I tried the same with no luck. The controller method is never called. Do you solve the problem with this answer or with the link you posted? – Marco Dec 19 '12 at 14:40
6

I had the exact same issue as you had when i wrote my own custom AuthorizeAttribute. The custom errors page for 403 won't show up when I added "customErrors" tag in web.config. This is how I got it resolved:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAuthenticated)
        {
           filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary(
                    new
                        { 
                            controller = "Error", 
                            action = "Unauthorised" 
                        })
                ); 

        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
}

Assigned a route that I would like to display to filterContext.Result, instead of assigning 403 HttpStatusCode.

Amna Ali
  • 1,775
  • 1
  • 16
  • 19
3

Or you can do this alternative solution,instead of using :

filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult(403);

you can change it to :

if (filterContext.HttpContext.Request.IsAuthenticated)
        {               
            throw new UnauthorizedAccessException();
        }

And override method OnException(ExceptionContext filterContext) in your Controller/BaseController

protected override void OnException(ExceptionContext filterContext)
    {
        if (filterContext.ExceptionHandled)
        {
            return;
        }

        if (filterContext.Exception.GetType() == typeof(UnauthorizedAccessException))
        {   
            filterContext.Result = new ViewResult
            {
                ViewName = "~/Views/Error/NotAuthorized.cshtml"
            };
            filterContext.ExceptionHandled = true;
            return;
        }

        base.OnException(filterContext);
    }
Rivera
  • 69
  • 1
  • 2
  • 8
0

How to handle 401 (Unauthorized), 403 (Forbidden) and 500 (Internal Server Error) in mvc. For ajax/non-ajax calls and under aspx forms authentication.

It can be altered to handle various uncaught exceptions differently and react differently whether the request is ajax or not. The auth part allows it to bypass any regular mvc web forms redirect-to-login-page and instead return 401 unauthorized - then your client-side js framework can react to http status 401/403 more easily.

// FilterConfig.cs:
filters.Add(new ApplicationAuthorizeAttribute());
filters.Add(new ApplicationHandleErrorAttribute());

public class ApplicationAuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        // Note: To reach here, a Web.config path-specific rule 'allow users="?"' is needed (otherwise it redirects to login)

        var httpContext = filterContext.HttpContext;
        var request = httpContext.Request;
        var response = httpContext.Response;

        if (request.IsAjaxRequest())
        {
            response.SuppressFormsAuthenticationRedirect = true;
            response.TrySkipIisCustomErrors = true;
        }

        filterContext.Result = new HttpUnauthorizedResult();
    }
}

public class ApplicationHandleErrorAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext context)
    {
        var exception = context.Exception is AggregateException
            ? ((AggregateException)context.Exception).InnerExceptions.First()
            : context.Exception;
        var request = context.HttpContext.Request;
        var response = context.HttpContext.Response;
        var isAjax = request.IsAjaxRequest();

        if (exception is MyCustomPermissionDeniedException)
        {
            filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden);
            response.TrySkipIisCustomErrors = isAjax;
            filterContext.ExceptionHandled = true;
            return;
        }

#if DEBUG
        if (!isAjax)
        {
            // Show default aspx yellow error page for developers
            return;
        }
#endif

        var requestUri = request.Url == null ? "" : request.Url.AbsoluteUri;
        MyCustomerLogger.Log(exception, requestUri);

        response.Clear();
        response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;

#if DEBUG
        var errorMessage = exception.Message;
#else
        var errorMessage = "An error occurred, please try again or contact the administrator.";
#endif

        response.Write(isAjax
            ? JsonConvert.SerializeObject(new {Message = errorMessage})
            : errorMessage);
        response.End();
        response.TrySkipIisCustomErrors = true;
        context.ExceptionHandled = true;
    }
}

Web.config:

<system.webServer>

<authentication mode="Forms">
  <forms name=".MYAUTHCOOKIE" protection="All" loginUrl="/Account/Login" timeout="18000" slidingExpiration="true" enableCrossAppRedirects="false" />
</authentication>

<authorization>
  <deny users="?" />
</authorization>

</system.webServer>

<!-- ajax api security done via ApplicationAuthorizeAttribute -->
<location path="api">
  <system.web>
    <authorization>
      <allow users="?"/>
    </authorization>
  </system.web>
</location>

Additional route for web service api requests: (put above regular mvc route)

// This route has special ajax authentication handling (no redirect to login page)
routes.MapRoute(
    name: "DefaultApi",
    url: "api/{controller}/{action}/{id}",
    defaults: new { id = UrlParameter.Optional }
);

Sample client side code for jquery to handle the error:

$.ajaxSetup({
    complete: function onRequestCompleted(xhr, textStatus) {
        if (xhr.readyState == 4 && xhr.status == 401) {
            // Not needed with smart status: && xhr.responseText.substring(0, 150).indexOf("<title>Log in") != -1
            //location.href = "/Account/Login";
            alert("Your session has timed out.");
        }
    }
});

Alternatively, you could make all auth go through ApplicationHandleErrorAttribute, and get rid of that web.config deny users="?". But I have a legacy aspx page which doesn't hit the mvc filtering so I want that deny users="?".

Curtis Yallop
  • 6,696
  • 3
  • 46
  • 36
  • You should actually be able to F12 (resharper decompile? ms symbol server?) in to: ControllerActionInvoker, HandleErrorAttribute, AuthorizeAttribute. This is very enlightening reading. ControllerActionInvoker uses each of the 4 filter-types differently. (auth, action, result, exception) eg Go to your controller, hit F12 on base class "Controller", F12 on "IAuthorizationFilter", Shift-F12 on "OnAuthorization". – Curtis Yallop May 27 '15 at 17:49
0

1-create a class call LoggedOrAuthorizedAttribute

    public class LoggedOrAuthorizedAttribute: AuthorizeAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            base.OnAuthorization(filterContext);
            CheckIfUserIsAuthenticated(filterContext);
        }

        private void CheckIfUserIsAuthenticated(AuthorizationContext filterContext)
        {
            // If Result is null, we're OK: the user is authenticated and authorized. 
            if (filterContext.Result == null)
                return;

            // If here, you're getting an HTTP 401 status code. In particular,
            // filterContext.Result is of HttpUnauthorizedResult type. Check Ajax here. 
            if (filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                filterContext.Result = new RedirectResult("/Error/Error401");
            }
        }
     }

2-Add the attribute you created to the top of your actions

    [LoggedOrAuthorizedAttribute(Roles = "Admin")]
    public ActionResult Index()
    {
        return View();
    }

    [LoggedOrAuthorizedAttribute(Roles = "User")]
    public ActionResult IndexUser()
    {
        return View();
    }
Diako Hasani
  • 1,384
  • 13
  • 14
0

To me it seems that the the HttpStatusCodeResult(403) is in the wrong if branch. In my opinion the code should look like this:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
    {
        if (!filterContext.HttpContext.Request.IsAuthenticated)
        {
            base.HandleUnauthorizedRequest(filterContext);
            filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult(403);
        }
    }
}
hydr
  • 408
  • 3
  • 10