views:

643

answers:

4

Essentially I want to show a friendly message when someone is not part of a role listed in my attribute. Currently my application just spits the user back to the log in screen. I've read a few posts that talk about creating a custom attribute that just extends [AuthorizeAttribute], but I'm thinking there's got to be something out of the box to do this?

can someone please point me in the right direction of where I need to look to not have it send the user to the log in form, but rather just shoot them a "not authorized" message?

+1  A: 

If simplicity or total control of the logic is what you want you can call this in your action method:

User.IsInRole("NameOfRole");

It returns a bool and you can do the rest of your logic depending on that result.

Another one that I've used in some cases is:

System.Web.Security.Roles.GetRolesForUser();

I think that returns a string[] but don't quote me on that.

EDIT: An example always helps...

public ActionResult AddUser()
{
    if(User.IsInRoles("SuperUser")
    {
        return View("AddUser");
    }
    else
    {
        return View("SorryWrongRole");
    }
}

As long as your return type is "ActionResult" you could return any of the accepted return types (ViewResult, PartialViewResult, RedirectResult, JsonResult...)

DM
so using that logic, I'd need to return partial views with "friendly messages" correct? Is it not possible to encompass the entire action method with an attribute that does the same thing?
Kyle
I added an example above. Your other option is of course writing your own attribute like you mentioned (which would be the cleanest although tougher to unit test), but that is definitely not an out-of-the-box approach.
DM
A: 

The out-of-the-box behavior is that the [Authorize] attribute returns an HTTP 401. The FormsAuthenticationModule (which is loaded by default) intercepts this 401 and redirects the user to the login page. Take a look at System.Web.Security.FormsAuthenticationModule::OnLeave in Reflector to see what I mean.

If you want the AuthorizeAttribute to do something other than return HTTP 401, you'll have to override the AuthorizeAttribute::HandleUnauthorizedRequest method and perform your custom logic in there. Alternatively, just change this part of ~\Web.config:

<forms loginUrl="~/Account/LogOn" timeout="2880" />

And make it point to a different URL, like ~/AccessDenied.

Levi
+1  A: 

I ran into this issue a few days ago and the solution is a bit detailed but here are the important bits. In AuthorizeAttribute the OnAuthorization method returns a HttpUnauthorizedResult when authorization fails which makes returning a custom result a bit difficult.

What I ended up doing was to create a CustomAuthorizeAttribute class and override the OnAuthorization method to throw an exception instead. I can then catch that exception with a custom error handler and display a customized error page instead of returning a 401 (Unauthorized).

public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    public virtual 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 /* data */);
        }
        else {
            // auth failed, redirect to login page
            // filterContext.Result = new HttpUnauthorizedResult();

            throw new HttpException ((int)HttpStatusCode.Unauthorized, "Unauthorized");                
        }
    }
}

then in your web.config you can set custom handlers for specific errors:

    <customErrors mode="On" defaultRedirect="~/Error">
        <error statusCode="401" redirect="~/Error/Unauthorized" />
        <error statusCode="404" redirect="~/Error/NotFound" />
    </customErrors>

and then implement your own ErrorController to serve up custom pages.

On IIS7 you need to look into setting Response.TrySkipIisCustomErrors = true; to enable your custom errors.

Todd Smith
A: 

I might be a little late in adding my $0.02, but when you create your CustomAuthorizationAttribue, you can use the AuthorizationContext.Result property to dictate where the AuthorizeAttribute.HandleUnauthorizedRequest method directs the user.

Here is a very simple example that allows you to specify the URL where a user should be sent after a failed authorization:

public class Authorize2Attribute : AuthorizeAttribute
{
    // Properties

    public String RedirectResultUrl { get; set; }

    // Constructors

    public Authorize2Attribute()
        : base()
    {
    }

    // Overrides

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (String.IsNullOrEmpty(RedirectResultUrl))
            base.HandleUnauthorizedRequest(filterContext);

        else
            filterContext.Result = new RedirectResult(RedirectResultUrl);
    }
}

And if I wanted to redirect the user to /Error/Unauthorized as suggested in a previous post:

[Authorize2(Roles = "AuthorizedUsers", RedirectResultUrl = "/Error/Unauthorized")]
public ActionResult RestrictedAction()
{
    // TODO: ...
}
crazyarabian