ASP.NET MVC user friendly 401 error
Asked Answered
G

4

18

I have implemented errors handling in ASP.NET MVC site in a way like suggests this post.

With 404 errors all works fine. But how correctly show user friendly screen for a 401 error? They usually do not throw Exception that can be handled inside Application_Error() but rather action returns HttpUnauthorizedResult. One possible way is to add following code to the end of Application_EndRequest() method

if (Context.Response.StatusCode == 401)
{
    throw new HttpException(401, "You are not authorised");
    // or UserFriendlyErrorRedirect(new HttpException(401, "You are not authorised")), witout exception
}

But inside Application_EndRequest() Context.Session == null, errorController.Execute() fails because it cannot use default TempDataProvider.

  // Call target Controller and pass the routeData.
  IController errorController = new ErrorController();
  errorController.Execute(new RequestContext(    
       new HttpContextWrapper(Context), routeData)); // Additional information: The SessionStateTempDataProvider requires SessionState to be enabled.

So, can you suggest some best practices how to 'user friendly handle' 401 in ASP.NET MVC application?

Thanks.

Girhiny answered 5/11, 2009 at 11:17 Comment(1)
#2581096Riddell
G
14

Look at the HandleErrorAttribute. Subclass from it or add your own implementation which will handle all the status codes you're interested in. You can make it to return a separate error view for each error type.

Here is an idea of how to create your handle error exception filter. I've thrown out most of the stuff to only focus on our essentials. Absolutely have a look at the original implementation to add arguments checks and other important things.

public class HandleManyErrorsAttribute : FilterAttribute, IExceptionFilter
{
    public virtual void OnException(ExceptionContext filterContext)
    {
        if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
            return;

        Exception exception = filterContext.Exception;

        string viewName = string.Empty;
        object viewModel = null;
        int httpCode = new HttpException(null, exception).GetHttpCode();
        if (httpCode == 500)
        {
            viewName = "Error500View";
            viewModel = new Error500Model();
        }
        else if (httpCode == 404)
        {
            viewName = "Error404View";
            viewModel = new Error404Model();
        }
        else if (httpCode == 401)
        {
            viewName = "Error401View";
            viewModel = new Error401Model();
        }

        string controllerName = (string)filterContext.RouteData.Values["controller"];
        string actionName = (string)filterContext.RouteData.Values["action"];
        filterContext.Result = new ViewResult
        {
            ViewName = viewName,
            MasterName = Master,
            ViewData = viewModel,
            TempData = filterContext.Controller.TempData
        };
        filterContext.ExceptionHandled = true;
        filterContext.HttpContext.Response.Clear();
        filterContext.HttpContext.Response.StatusCode = httpCode;

        filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
    }
}

Then you "decorate" your controller actions with this attribute:

[HandleManyErrors]
public ActionResult DoSomethingBuggy ()
{
    // ...
}
Gereron answered 5/11, 2009 at 11:20 Comment(5)
As I understand this solution requires to decorate with HandleManyErrors attribute each Controller or potentially secure Action (action that can lead to 401). But it would be nice to have global 401 handling (in Global.asax for example), or I am not right and this is bad idea?Girhiny
With MVC model, you don't always know in advance whether a route is okay or not. A route can resolve to reach some controller action but after checking the supplied parameter with the database you may decide the route with this parameter cannot retrieve the needed resource so you return a 4xx error. With this dynamic behavior, it is easier to check things and throw an HttpException from your controller actions knowing they will be handled by the attribute to return an error page.Gereron
For static behavior, you can add the last "catchall" route to match all routes that were not caught by previously defined rules. The catchall will invoke some controller action that will simply throw an HttpException. This way you have static error handling logic in your Global.asax accompanied by dynamic error handling directly in respective controller actions.Gereron
@Girhiny You can always create a BaseController and attach the attribute to that.Lorilee
@Girhiny Just edit App_Start\FilterConfig.cs in your project and add a line in the RegisterGlobalFilters override like filters.Add(new HandleManyErrorsAttribute());. That will apply it to every action across the board.Shaw
I
6

I managed to solve this in a very simple way. I wanted to show a custom page for logged-in users ("you have no permission bla bla...") and redirect unauthenticated users to the login page (the default behaviour). So I implemented a custom AuthorizeAttribute (say CustomAuthorizeAttribute) with the HandleUnauthorizedRequest method overwritten in a way that if the user is authenticated I set the Result property of the filterContext argument with a ViewResult (in the Shared folder) called AccessDenied.aspx

protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
    {
        base.HandleUnauthorizedRequest(filterContext);
    }
    else
    {
        filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
    }
}

Then you have to use this new attribute instead. Regards.

Inelastic answered 22/1, 2010 at 12:54 Comment(2)
For anyone wondering, this attribute is only mentioned on MSDNs Framework 4.0. It is not available in 3.5Abdomen
This may not work with "windows" authentication. Tried and it failed unfortunately..Misgovern
M
0

If you're using ASP.NET MVC, you're more than likely to use IIS, so why don't you just set up IIS to use your custom 401 error page for that Web Application / Virtual Directory?

Madriene answered 5/11, 2009 at 11:40 Comment(2)
I need different behavior for Full Postback & AJAX requests and more control over error processingGirhiny
Because you might want more fine grain control on exactly how the 404 is presented (HTML and HTTP). You may also want other info on your 404 page like dynamic content that will only work within ur app. you may want to log specific messages and track 404 paths with your apps logging engine - so it all has to be done inside the application.Melitta
R
-1

In one of my project, I use the code from uvita.

I have ASP.NET MVC2 and I use Active Directory authentication without login page. I have a NoAuth.aspx page that use site master page, integrate the web application layout.

This is the web.config.

<system.web>
    <authentication mode="Windows" />
    <roleManager enabled="true" defaultProvider="AspNetWindowsTokenRoleProvider">
       <providers>
          <clear />
          <add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider"
          applicationName="/" />
      </providers>
    </roleManager>
</system.web>

The new class CustomAutorizeAttribute

using System.Web.Mvc;
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
        else
        {
           filterContext.Result = new ViewResult { ViewName = "NoAuth"};
        }
    }
}

and the controller

[CustomAuthorize(Roles = "ADRole")]
public class HomeController : Controller
{
    public HomeController()
    {
    }
}
Rittenhouse answered 19/10, 2015 at 14:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.