23

I've read

How to easily redirect if not authenticated in MVC 3? and Redirect to AccessDenied page when user is not authorized but the link from an answer (means http://wekeroad.com/2008/03/12/aspnet-mvc-securing-your-controller-actions/) doesn't work.

I put

[Authorize(Users = "test")]
    public class RestrictedPageController: Controller
    {

        public ActionResult Index()
        {
           return View();
        }

 ....
    }

And in my web.config, I have already

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

accordingly with https://stackoverflow.com/a/6770583/998696

But when I want to access /RestrictedPage/Index, it must redirect me to other page (from other controller). Instead of this, the error appears like:

Server Error in '/Project' Application.

The view 'LogOn' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Account/LogOn.aspx
~/Views/Account/LogOn.ascx
~/Views/Shared/LogOn.aspx
~/Views/Shared/LogOn.ascx
~/Views/Account/LogOn.cshtml
~/Views/Account/LogOn.vbhtml
~/Views/Shared/LogOn.cshtml
~/Views/Shared/LogOn.vbhtml

Before login, the Logon page form appears correctly but the above error appears when accessing /RestrictedPage/Index page. I can login with user different one authorized to access RestrictedPage page.

Where is my mistake and how setup redirection ?

Community
  • 1
  • 1
Snake Eyes
  • 16,287
  • 34
  • 113
  • 221

5 Answers5

65

The default Authorize attribute behaves in such a way that when the user is not authenticated or authenticated but not authorized then it set the status code as 401 (UnAuthorized). When the filter sets the status code as 401 the ASP.NET framework checks if the website has forms authentication enabled and if it is then redirects to loginUrl parameter set up there.

If you want to change that behavior say you want to redirect the user to an AccessDenied controller if the user is authenticated but not authorized then you have to extend the Authorize attribute and override the HandleUnauthorizedRequest method.

For ex.

public class CustomAuthorize: AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            filterContext.Result = new HttpUnauthorizedResult();
        }
        else
        {
           filterContext.Result = new RedirectToRouteResult(new 
               RouteValueDictionary(new { controller = "AccessDenied" }));
        }
    }
}

You can override the HandleUnauthorizedRequest as per your need and then you have to mark the controller actions to use the CustomAuthorize attribute instead of the built-in one.

VJAI
  • 32,167
  • 23
  • 102
  • 164
  • 1
    Upvote and accepted! One remarks: must use `protected override void HandleUnauthorizedRequest( AuthorizationContext filterContext ) {` to override the method, otherwise won't work – Snake Eyes Jun 07 '12 at 09:56
  • 1
    This is the one solution I keep running into, but is this really still the only way to do it? Having to recreate the `[Authorize]` filter just to change the path seems overkill. – Lukas Jul 14 '20 at 21:50
  • If you want to need authorization across your project then add this line to AppStart/FilterConfig: `filters.Add(new AuthorizeAttribute());` – Web Developer Apr 28 '22 at 17:08
7

I like Mark's Answer,
but I don't want to change all of my action attributes
from [Authorize] to [CustomAuthorize]

I edit Login() action on AccountController
and check Request.IsAuthenticated before show view
I think, if the authenticated user go to /Account/Logon,
I will redirect to /Error/AccessDenied.

    [AllowAnonymous]
    public ActionResult Login(string returnUrl)
    {
        if (Request.IsAuthenticated)
        {
            return RedirectToAction("AccessDenied", "Error");
        }

        ViewBag.ReturnUrl = returnUrl;

        return View();
    }
Yuthasak
  • 71
  • 1
  • 2
  • but in this case, if you are already logged in and go to login page, you will see a `/error/accessDenied` instead of `/account/login`, right? If you don't wish to change `[Authorize]` to `[CustomAuthorize]` you can always name it `YourBrand.YourProject.Security.AuthorizeAttribute` and then only make a reference to `YourBrand.YourProject.Security` or something – percebus Feb 05 '16 at 22:11
  • 1
    I like this solution, but if they are already authenticated but not authorized, then I would redirect to a non-error home page where everyone is authorized, and include an optional ViewBag status message that says something like $"You are not authorized to view {returnUrl}". – reasonet Jun 07 '19 at 18:05
3

Since I did not want to override AuthorizeAttribute I used filter

public class RedirectFilter : ActionFilterAttribute
{
   public override void OnActionExecuting(ActionExecutingContext filterContext)
    {

        if (!IsAuthorized(filterContext))
        {
            filterContext.Result =
                new RedirectToRouteResult(new RouteValueDictionary(new {controller = "AccessDenied"}));
        }
    }

    private bool IsAuthorized(ActionExecutingContext filterContext)
    {
        var descriptor = filterContext.ActionDescriptor;
        var authorizeAttr = descriptor.GetCustomAttributes(typeof(AuthorizeAttribute), false).FirstOrDefault() as AuthorizeAttribute;

        if (authorizeAttr != null)
        {
            if(!authorizeAttr.Users.Contains(filterContext.HttpContext.User.ToString()))
            return false;
        }
        return true;

    }
}
Riddik
  • 2,565
  • 1
  • 11
  • 21
2

Place "/Account/LogOn" Instead of "~/Account/LogOn"

karaxuna
  • 26,752
  • 13
  • 82
  • 117
1

Yes, it is correct as you mentioned in web.config

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

redirection is looking for Account controller and LogOn actionresult. If you want to redirect your page, change there instead of account and logon

manny
  • 1,878
  • 2
  • 15
  • 31