26

I'd like [Authorize] to redirect to loginUrl unless I'm also using a role, such as [Authorize (Roles="Admin")]. In that case, I want to simply display a page saying the user isn't authorized.

What should I do?

royco
  • 5,409
  • 13
  • 60
  • 84
  • I asked the same question here: http://stackoverflow.com/questions/2322366/how-do-i-serve-up-an-unauthorized-page-when-a-user-is-not-in-the-authorized-roles – Robert Harvey Mar 24 '10 at 02:25
  • 1
    Robert: Thanks. I searched for similar posts before posting, but didn't see yours. Which solution did you go with? – royco Mar 24 '10 at 02:39
  • 2
    I ended up rolling my own `AuthorizeRoles` attribute, using the code from `AuthorizeAttribute` and modifying it. If you can wait until tomorrow, I'll post the code. – Robert Harvey Mar 24 '10 at 05:01

3 Answers3

26

Here is the code from my modified implementation of AuthorizeAttribute; I named it SecurityAttribute. The only thing that I have changed is the OnAuthorization method, and I added an additional string property for the Url to redirect to an Unauthorized page:

// Set default Unauthorized Page Url here
private string _notifyUrl = "/Error/Unauthorized"; 

public string NotifyUrl { 
    get { return _notifyUrl; } set { _notifyUrl = value; } 
}

public override void OnAuthorization(AuthorizationContext filterContext) {
    if (filterContext == null) {
        throw new ArgumentNullException("filterContext");
    }

    if (AuthorizeCore(filterContext.HttpContext)) {
        HttpCachePolicyBase cachePolicy =
            filterContext.HttpContext.Response.Cache;
        cachePolicy.SetProxyMaxAge(new TimeSpan(0));
        cachePolicy.AddValidationCallback(CacheValidateHandler, null);
    }

    /// This code added to support custom Unauthorized pages.
    else if (filterContext.HttpContext.User.Identity.IsAuthenticated)
    {
        if (NotifyUrl != null)
            filterContext.Result = new RedirectResult(NotifyUrl);
        else
           // Redirect to Login page.
            HandleUnauthorizedRequest(filterContext);
    }
    /// End of additional code
    else
    {
         // Redirect to Login page.
        HandleUnauthorizedRequest(filterContext);
    }
}

You call it the same way as the original AuthorizeAttribute, except that there is an additional property to override the Unauthorized Page Url:

// Use custom Unauthorized page:
[Security (Roles="Admin, User", NotifyUrl="/UnauthorizedPage")]

// Use default Unauthorized page:
[Security (Roles="Admin, User")]
dimiguel
  • 1,429
  • 1
  • 16
  • 37
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
  • Thanks, this works well. Instead of modifying AuthorizeAttribute.cs, I simply inherited from it. Now I can use Roles with [Authorize] and my code will be vastly simplified. Thanks again. – royco Mar 25 '10 at 08:00
  • Robert: I need to call HandleUnauthorizedRequest(filterContext) from within AuthorizeCore, where filterContext is not available. I could just pass filterContext to AuthorizeCore intead of filterContext.HttpContext. Thoughts? – royco Mar 31 '10 at 04:47
  • @Bob: AuthorizeCore is just a local method, so you ought to be able to do whatever you want with it. – Robert Harvey Mar 31 '10 at 13:37
  • Although take note of the comment text in that method regarding thread safety. – Robert Harvey Mar 31 '10 at 16:48
  • How can I make the url be dynamic, eg, in my development envirment, the url is ip://xxx/Error/Unauthorized, but in production server is ip://xxx/yyy/Error/Unauthorized. seems dynamic can not be applied to attribute. – Timeless Jul 27 '14 at 05:08
  • 2
    This code: `cachePolicy.AddValidationCallback(CacheValidateHandler, null);` makes no sense, you cannot pass a type (CacheValidateHandler) as a parameter? – Quango Sep 09 '15 at 13:51
  • @Quango: `CacheValidateHandler` is a *method* (not shown here). You can see an example of it [here](http://stackoverflow.com/q/1485640/102937). – Robert Harvey Jan 20 '17 at 16:53
  • Which versions of .net core and Microsoft.AspNet.Mvc did you use ? @RobertHarvey – hkyaaa Oct 20 '21 at 09:04
  • @hkyaaa: Well, since this question is nearly 12 years old, I would imagine that it was ASP.NET MVC 2.0. See [here](https://en.wikipedia.org/wiki/ASP.NET_MVC) for a version history. – Robert Harvey Oct 20 '21 at 13:23
18

Extend the AuthorizeAttribute class and override HandleUnauthorizedRequest

public class RoleAuthorizeAttribute : AuthorizeAttribute
{
    private string redirectUrl = "";
    public RoleAuthorizeAttribute() : base()
    {
    }

    public RoleAuthorizeAttribute(string redirectUrl) : base()
    {
        this.redirectUrl = redirectUrl;
    }

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAuthenticated)
        {
            string authUrl = this.redirectUrl; //passed from attribute

            //if null, get it from config
            if (String.IsNullOrEmpty(authUrl))
                authUrl = System.Web.Configuration.WebConfigurationManager.AppSettings["RolesAuthRedirectUrl"];

            if (!String.IsNullOrEmpty(authUrl))
                filterContext.HttpContext.Response.Redirect(authUrl);
        }

        //else do normal process
        base.HandleUnauthorizedRequest(filterContext);
    }
}

Usage

[RoleAuthorize(Roles = "Admin, Editor")]
public class AccountController : Controller
{

}

And make sure you add your AppSettings entry in the config

<appSettings>
  <add key="RolesAuthRedirectUrl" value="http://mysite/myauthorizedpage" />
</appSettings>
codingbiz
  • 26,179
  • 8
  • 59
  • 96
2

The easiest way I've found is to extend and customize the AuthorizeAttribute so that it does something different (i.e., not set an HttpUnauthorizedResult) when the Role check fails. I've written an article about this on my blog that you might find useful. The article describes much what you are wanting, though it goes further and allows the user who "owns" the data to also have access to the action. I think it should be fairly easy to modify for your purposes -- you'd just need to remove the "or owner" part.

tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • Link only answers are frowned upon because if the link no longer works, the answer becomes useless, please include the content of the link in the answer here. – Erik Philips Jan 30 '15 at 20:13
  • 1
    The answer references my own blog post and does describe the general approach - and thus isn't a "link only" answer. I fail to see the need to replicate the entire post here. – tvanfosson Jan 31 '15 at 17:02