[ACCEPTED]-ASP.NET MVC - How to show unauthorized error on login page?-authorization

Accepted answer
Score: 81

UPDATE (Jun 2015): @daniel-lidström has correctly pointed 17 out that you should not use Response.Redirect 16 in an ASP.NET MVC application. For more 15 information about why, please see this link: Response.Redirect and ASP.NET MVC – Do Not Mix.

UPDATE (Sep 2014): I'm 14 not sure when HandleUnauthorizedRequest 13 was added to the AuthorizeAttribute, but 12 either way I've been able to refine the 11 AuthorizeRedirect code into something smaller 10 and simpler.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeRedirect : AuthorizeAttribute
{
    public string RedirectUrl = "~/Error/Unauthorized";

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        base.HandleUnauthorizedRequest(filterContext);

        if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
        {
            filterContext.Result = new RedirectResult(RedirectUrl);
        }
    }
}

Original Answer Below (still fully functional)

I've left this answer here as 9 it still gives you an insight as to how 8 the Authorization pipeline works.

For anyone 7 still landing here, I've edited Ben Scheirman's 6 answer to automatically redirect to an unauthorized 5 page when the user is logged in but not 4 authorized. You can change the redirect 3 path using the name parameter RedirectUrl.

EDIT: I've 2 made the solution thread-safe thanks to 1 the advice of Tarynn and MSDN

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeRedirect : AuthorizeAttribute
{
    private const string IS_AUTHORIZED = "isAuthorized";

    public string RedirectUrl = "~/error/unauthorized";

    protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
    {
        bool isAuthorized = base.AuthorizeCore(httpContext);

        httpContext.Items.Add(IS_AUTHORIZED, isAuthorized);

        return isAuthorized;
    }

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

        var isAuthorized = filterContext.HttpContext.Items[IS_AUTHORIZED] != null 
            ? Convert.ToBoolean(filterContext.HttpContext.Items[IS_AUTHORIZED]) 
            : false;

        if (!isAuthorized && filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
        {
            filterContext.RequestContext.HttpContext.Response.Redirect(RedirectUrl);
        }
    }
}
Score: 30

You can look for the ?ReturnUrl= querystring value, or 5 you can create your own authorization filter 4 & set a field in TempData indicating the reason.

Here 3 is a simple custom filter that will do the 2 trick:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{

    // NOTE: This is not thread safe, it is much better to store this
    // value in HttpContext.Items.  See Ben Cull's answer below for an example.
    private bool _isAuthorized;

    protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
    {
        _isAuthorized = base.AuthorizeCore(httpContext);
        return _isAuthorized;
    }

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

        if(!_isAuthorized)
        {
            filterContext.Controller.TempData.Add("RedirectReason", "Unauthorized");
        }
    }
}

Then in your view, you can do something 1 like this:

@if(TempData["RedirectReason"] == "Unauthorized")
{
    <b>You don't have permission to access that area</b>
}

(Though I'd recommend a better approach than these magic strings, but you get the point)

Score: 6

Ben Cull's method works well, but remember 11 there are two AuthorizeAttribute classes 10 - one in System.Web.HTTP (used by Web API), and 9 the other in System.Web.Mvc. Ben's method 8 uses the System.Web.Mvc class. For clarity, I 7 suggest using the fully qualified path.

If 6 you're using Web API alongside MVC, you 5 will need to implement two filters:

public class AuthorizeRedirectMVCAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        base.HandleUnauthorizedRequest(filterContext);

        if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
        {
            filterContext.Result = new RedirectResult("~/Account/AccessDenied");
        }
    }
}

public class AuthorizeRedirectAPIAttribute : System.Web.Http.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        base.HandleUnauthorizedRequest(actionContext);

        if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden);
        }
    }
}

Note 4 that asp.net will let you decorate your 3 MVC controller with an API filter - it just 2 won't work the way you expect, so keep your 1 attribute names explicit.

Score: 4

If you have a controller and don't want 12 to have a url in you code you can redirect 11 this way as well. It will not change the 10 url in the address bar of the browser so 9 the user will never see the url for the 8 unauthorized page. This was written in 7 MVC 3. This method will also work if you 6 want to redirect them to a login page or 5 if you want to redirect them to a page to 4 just tell them they aren't authorized. I 3 had section in the program that some user 2 didn't have rights to but they were logged 1 in so this is what I used.

public class AuthorizedRedirect : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        bool isAuthorized = base.AuthorizeCore(httpContext);
        return isAuthorized;
    }
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
    filterContext.RequestContext.RouteData.Values["controller"] = "error";
    filterContext.Result = new ViewResult { ViewName = "unauthorized" };
}
Score: 2

And an even simpler version that utilizes 4 FormsAuthentication settings. For those 3 not familiar with Contract, Contract.Requires 2 is a .NET 4 addition. Pros and cons to using 1 Code Contracts.

public class RequiresAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        Contract.Requires(filterContext != null);

        HttpContextBase context = filterContext.RequestContext.HttpContext;

        if (context.User.Identity.IsAuthenticated)
        {
            // user does not possess the required role permission
            string url = context.GetCustomErrorUrl(401);
            context.Response.Redirect(url);
        }
        else
        {

            // redirect the user to the login page
            string extraQueryString  = context.Request.RawUrl;
            FormsAuthentication.RedirectToLoginPage(extraQueryString);
        }
    }
}
Score: 1

Going further from divide_byzero's answer 6 even if you don't have a controller you 5 can still use the HandleUnauthorizedRequest 4 to change the redirect.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class AuthoriseRedirect : AuthorizeAttribute
    {
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            filterContext.RequestContext.HttpContext.Response.Redirect("UrlToRedirectTo");
        }
    }

Comes in handy if 3 you have a legacy webforms site that you 2 will be converting to MVC over a longer 1 period of time.....!

Score: 0

I like what Brian Vander Plaats posted, just 1 added few improvements:

/// <summary>
/// Authorize or redirect to an unauthorized MVC action if the user does not have the required roles
/// (an unauthenticated user will be redirected to the defualt sign in action)
/// <para>Decorate an action or a controller like this [AuthorizeAndRedirect(Roles = "RoleName")]</para>
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class AuthorizeOrRedirectAttribute : System.Web.Mvc.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        base.HandleUnauthorizedRequest(filterContext);

        if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
        {
            var routeData = new RouteData();
            routeData.Values.Add("controller", "Error");
            routeData.Values.Add("action", "Unauthorized");
            filterContext.Result = new RedirectToRouteResult(routeData.Values);
        }
    }
}

/// <summary>
/// Authorize or redirect to an unauthorized API action if the user does not have the required roles
/// (an unauthenticated user will be redirected to the defualt sign in action)
/// <para>Decorate an action or a controller like this [AuthorizeAndRedirect(Roles = "RoleName")]</para>
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class AuthorizeOrRedirectApiFilterAttribute : System.Web.Http.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        base.HandleUnauthorizedRequest(actionContext);

        if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
        }
    }
}

More Related questions