Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make Authorize attribute return custom 403 error page instead of redirecting to the Logon page

[Authorize] attribute is nice and handy MS invention, and I hope it can solve the issues I have now

To be more specific:

When current client isn't authenticated - [Authorize] redirects from secured action to logon page and after logon was successful - brings user back, this is good.

But when current client already authenticated but not authorized to run specific action - all I need is to just display my general 403 page.

Is it possible without moving authorization logic within controller's body?

Update: The behavior I need in should be semantically equals to this sketch:

public ActionResult DoWork()
{
    if (!NotAuthorized())
    {
        // this should be not redirect, but forwarding 
        return RedirectToAction("403");         
    }

    return View();
}

so - there should no any redirect and url should be stay the same, but contents of the page should be replaced with 403-page

Update 2: I implemented sketch in this way:

[HandleError]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        ViewData["Message"] = "Welcome to ASP.NET MVC!";

        return View();
    }

    [CustomActionFilter]
    public ActionResult About()
    {
        return View();
    }

    public ActionResult Error_403()
    {
        return Content("403");
    }
}

public class CustomActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.Result = new ContentResult { Content = "403" };
    }
}

And can't get how to properly forward execution to HomeController.Action_403() so it display 403.

Update 3:

filterContext.Result = new ViewResult() { ViewName = "Error_403" };

so this is an answer on how to render specific view template... but still have no idea how to run another controller - anyway, it's enough good solution.

like image 866
zerkms Avatar asked Apr 05 '10 14:04

zerkms


2 Answers

What I would do is subclass AuthorizeAttribute and override its HandleUnauthorizedRequest to return HTTP status code 403 if user is authenticated. I would then add a system.webServer\httpErrors section to my Web.Config to replace the default 403 with my custom page (this last part requires IIS 7+). Here's how:

public class MyAuthorizeAttribute : AuthorizeAttribute {
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) {
        if (filterContext.HttpContext.User.Identity.IsAuthenticated)
            filterContext.Result = new HttpStatusCodeResult(403);
        else
            filterContext.Result = new HttpUnauthorizedResult();
    } 
}

<configuration>
  <system.webServer>
    <httpErrors errorMode="Custom" existingResponse="Replace">
      <remove statusCode="403" />
      <error statusCode="403" responseMode="ExecuteURL" path="/Error/MyCustom403page" />
    </httpErrors>
  </system.webServer>
</configuration>
like image 100
Stop Putin Stop War Avatar answered Nov 13 '22 01:11

Stop Putin Stop War


You should be able to create your own class that derives from AuthorizeAttribute and override the AuthorizeCore method to provide the authorization mechanism that you want, so that you can apply your custom authorization code by using an attribute instead of moving it into the controller.

If you need more fine-grained control over authorization, then I recommend that you create an implementation of the IActionFilter interface (on an attribute, then apply the attribute to your methods). This will allow you to intercept calls before they go to the controller, and provide alternate actions before your controller method is called.

This is achieved by implementing the OnActionExecuting method on the IActionFilter interface. If your logic determines that you should not make the call to the controller at all, and you want to provide an ActionResult to be processed instead, then you would set the Result property on the ActionExecutingContext instance passed into the method. By doing this, that ActionResult is processed instead of going to the controller method to get an ActionResult.

If you want to return a 403 error code, then you can't use the ContentResult class. You will have to create your own class that derives from ActionResult and override the ExecuteResult method to set the StatusCode property on the HttpResponseBase to 403, like so:

internal class Http403Result : ActionResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        // Set the response code to 403.
        context.HttpContext.Response.StatusCode = 403;
    }
}

public class CustomActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.Result = new Http403Result();
    }
}

Of course, you can generalize the Http403Result class to take a constructor which will accept the status code that you want to return, but the concept remains the same.

like image 24
casperOne Avatar answered Nov 13 '22 01:11

casperOne