Question

I am using a custom authorize attribute in a ASP.NET MVC 5 application like following:

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext context)
    {
        if (context.HttpContext.Request.IsAuthenticated)
        {
            context.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);                
        }
        else
        {
            base.HandleUnauthorizedRequest(context);
        }
    }
}

In system.web section of my web.config I mentioned error paths like:

<system.web>
    <customErrors mode="On" defaultRedirect="/Error/Error">
      <error statusCode="403" redirect="/Error/NoPermissions"/>
    </customErrors>
</system.web>

But I am never redirected to my custom error page at /Error/NoPermissions. Instead the browser display the general error page saying "HTTP Error 403.0 - Forbidden".

Était-ce utile?

La solution 2

Thanks everyone, but problem is not with 403 code. Actually the problem was with the way i was trying to return 403. I just changed my code to throw an HttpException instead of returning the HttpStatusCodeResult and every things works now. I can return any HTTP status code by throwing HttpException exception and my customErrors configuration catches all of them. May be HttpStatusCodeResult is not doing the exact job I expected it to do.

I just replaced

context.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);

with

throw new HttpException((int)System.Net.HttpStatusCode.Forbidden, "Forbidden");

That's it.

Happy coding.

Autres conseils

[1]: Remove all 'customErrors' & 'httpErrors' from Web.config

[2]: Check 'App_Start/FilterConfig.cs' looks like this:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
    }
}

[3]: in 'Global.asax' add this method:

public void Application_Error(Object sender, EventArgs e)
{
    Exception exception = Server.GetLastError();
    Server.ClearError();

    var routeData = new RouteData();
    routeData.Values.Add("controller", "ErrorPage");
    routeData.Values.Add("action", "Error");
    routeData.Values.Add("exception", exception);

    if (exception.GetType() == typeof(HttpException))
    {
        routeData.Values.Add("statusCode", ((HttpException)exception).GetHttpCode());
    }
    else
    {
        routeData.Values.Add("statusCode", 500);
    }

    Response.TrySkipIisCustomErrors = true;
    IController controller = new ErrorPageController();
    controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
    Response.End();
}

[4]: Add 'Controllers/ErrorPageController.cs'

public class ErrorPageController : Controller
{
    public ActionResult Error(int statusCode, Exception exception)
    {
         Response.StatusCode = statusCode;
         ViewBag.StatusCode = statusCode + " Error";
         return View();
    }
}

[5]: in 'Views/Shared/Error.cshtml'

@model System.Web.Mvc.HandleErrorInfo
@{
    ViewBag.Title = (!String.IsNullOrEmpty(ViewBag.StatusCode)) ? ViewBag.StatusCode : "500 Error";
}

<h1 class="error">@(!String.IsNullOrEmpty(ViewBag.StatusCode) ? ViewBag.StatusCode : "500 Error"):</h1>

//@Model.ActionName
//@Model.ControllerName
//@Model.Exception.Message
//@Model.Exception.StackTrace

:D

I also had this issue. Code in the OP’s question is perfectly working except the custom error code in <system.web> section in the web.config file. To fix the issue what I need to do was add the following code to <system.webServer>. Note that ‘webserver’ instead of ‘web’.

<httpErrors errorMode="Custom" existingResponse="Replace">
  <remove statusCode="403" />
  <error statusCode="403" responseMode="ExecuteURL" path="/Error/UnAuthorized" />
</httpErrors>

If someone is using following environment, here is the complete solution:

The Environment:

  • Visual Studio 2013 Update 4
  • Microsoft .NET Framework 4.5.1 with ASP.NET MVC 5
  • Project: ASP.NET Web Application with MVC & Authentication: Individual User Account template

Custom Attribute class:

Add the following class to your web site’s default namespace. The reason explained here in the accepted answer Stack Overflow question: Why does AuthorizeAttribute redirect to the login page for authentication and authorization failures?

[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((int)System.Net.HttpStatusCode.Forbidden);

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

Then add the following code the web.config file

<system.webServer>
   <httpErrors errorMode="Custom" existingResponse="Replace">
      <remove statusCode="403" />
      <error statusCode="403" responseMode="ExecuteURL" path="/Error/UnAuthorized" />
   </httpErrors>
</system.webServer>

Following article explain more about this: ASP.NET MVC: Improving the Authorize Attribute (403 Forbidden)

And httpErrors in web.config section in this article: Demystifying ASP.NET MVC 5 Error Pages and Error Logging

Then add the ErrorController.cs to Controllers folder

public class ErrorController : Controller
{
    // GET: UnAuthorized
    public ActionResult UnAuthorized()
    {
        return View();
    }

    public ActionResult Error()
    {
        return View();
    }

}

Then add a UnAuthorized.cshtml to View/Shared folder

@{
    ViewBag.Title = "Your Request Unauthorized !"; //Customise as required
 }
 <h2>@ViewBag.Title.</h2> 

This will show customised error page instead of browser generated error page.

Also note that for the above environment, it is not required to comment the code inside RegisterGlobalFilters method added by the template as suggested in one of the answers.

Please note that I just cut and paste code from my working project therefore I used Unauthorized instead OP’s NoPermissions in the above code.

These seem to be complicated workarounds. Here is basically all you need to do to get your custom error pages (CEP) working:

  1. Add an Error Controller in your controllers folder.
  2. Inside your Error Controller annotate the class with [HandleError].
  3. For each error you want to display a CEP for, create an ActionResult method.
  4. Create a View for each CEP inside ~/Views/Shared/Error folder, and customize it how you like. (The Error folder should have been created when you created your controller. If it did not, you will need to create the Error folder first.)
  5. Open the web.config file at the root level *Note: there are two (2) web.config files. One in your Views folder, the other at the Root level of your application.
  6. Inside <system.web>, add <customError mode="On" defaultRedirect="~/Error/Error">. Any statusCode you don't have a CEP for will be handled by the defaultRedirect.
  7. For each error code you have a CEP for; add <error statusCode="[StatusCode]" redirect="~/Error/[CEP Name]">. You can leave off the file extension.

Controller Example:

namespace NAMESPACE_Name.Controllers
{
    [HandleError]
    public class ErrorController : Controller
    {
        // GET: Error
        public ActionResult BadRequest()
        {
            return View();
        }

        public ActionResult Error()
        {
            return View();
        }

        public ActionResult Forbidden()
        {
            return View();
        }

        public ActionResult InternalServerError()
        {
            return View();
        }

        public ActionResult NotFound()
        {
            return View();
        }

        public  ActionResult NotImplemented()
        {
            return View();
        }

        public ActionResult ServerBusyOrDown()
        {
            return View();
        }

        public ActionResult ServerUnavailable()
        {
            return View();
        }

        public ActionResult Timeout()
        {
            return View();
        }

        public ActionResult Unauthorized()
        {
            return View();
        }
    }
}

View Example:

@{ 
    Layout = "~/Views/Shared/_FullWidthLayout.cshtml";
    ViewBag.Title = "404 Error";
}
<div class="opensans margin-sides text-center">
    <div class="text-center">
        <h1 class="text-normal">Uh oh! Something went wrong!</h1>
        <div class="img-container text-center">
            <div class="centered">
                <h1 class="bold">404 - Not Found</h1>
            </div>
            <img class="img text-center" src="~/Images/BackgroundImg.png" style="opacity: 0.15;" />
        </div>
        <p class="text-left">
            This is usually the result of a broken link, a web page that has been moved or deleted, or a mistyped URL.
            <ol class="text-left">
                <li>Check the URL you entered in the address bar for typos,</li>
                <li>If the address you entered is correct, the problem is on our end.</li>
                <li>Please check back later as the resource you requested could be getting worked on,</li>
                <li>However, if this continues for the resource you requested, please submit a <a href="mailto:EmailAddress?subject=Website%20Error">trouble ticket</a>.</li>
            </ol>
        </p>
    </div>
</div>

Web.Config Example:

<customErrors mode="On" defaultRedirect="~/Error/Error">
  <!--The defaultRedirect page will display for any error not listed below.-->
  <error statusCode="400" redirect="~/Error/BadRequest"/>
  <error statusCode="401" redirect="~/Error/Unauthorized"/>
  <error statusCode="403" redirect="~/Error/Forbidden"/>
  <error statusCode="404" redirect="~/Error/NotFound"/>
  <error statusCode="408" redirect="~/Error/Timeout"/>
  <error statusCode="500" redirect="~/Error/InternalServerError"/>
  <error statusCode="501" redirect="~/Error/NotImplemented"/>
  <error statusCode="502" redirect="~/Error/ServerUnavailable"/>
  <error statusCode="503" redirect="~/Error/ServerBusyOrDown"/>
</customErrors>

That's it! Step-by-step solution to a problem that really shouldn't be a problem! Again, any statusCode you don't have a CEP for, will be handled by the defaultRedirect page.

since I ran into a very similar issue I wanted to shed more light on it.

customErrors will only capture actual http exceptions thrown in your ASP.NET application. The HttpStatusCodeResult doesn't throw an exception though. It just writes a response with the according status code, which makes more sense in your example.

If you are running on IIS 7.0 or higher you should be using httpErrors now, as this will show you custom error pages in all cases. This is an IIS level setting.

I wrote a whole blog post about this to explain the differences: http://dusted.codes/demystifying-aspnet-mvc-5-error-pages-and-error-logging

Update

You only need to do that special redirect for 403 errors. All other 500 errors should take effect through your defaultRedirect="/Error/Error" setting in customErrors. However, you need to remove or comment out the HandleErrorAttribute registration in the App_Start/FilterConfig.cs file for custom errors to actually work. Otherwise, that attribute will redirect all errors to the Error.cshtml file in the Views/Shared directory.

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        // remove this line below
        //filters.Add(new HandleErrorAttribute());
    }
}

Original Answer

As far as I know, you cannot use customErrors in the web.config to handle 403 errors for some reason. I feel your pain as it seems like something that should be as simple as the code you already have, but apparently 403 errors are treated as a web server concern.

What you can do instead is just redirect the user to your desired "NoPermissions" page like this:

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext context)
    {
        if (context.HttpContext.Request.IsAuthenticated)
        {
            context.Result = new RedirectToRouteResult(new RouteValueDictionary(new
            {
                action = "NoPermissions",
                controller = "Error",
                area = ""
            }));
        }
        else
        {
            base.HandleUnauthorizedRequest(context);
        }
    }
}

The request will have a 200 status code instead of a 403, but if you can live with that, this is an easy workaround.

Here is a similar SO question for more info: Returning custom errors.

Also, this article explains how to go the IIS route: http://kitsula.com/Article/MVC-Custom-Error-Pages

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top