Question

I have decorated my controller with an Authorize attribute, as so:

[Authorize(Roles="ExecAdmin")]

If I try to go to that controller after logging in as a user who is not ExecAdmin, it does appear to be attempting to redirect to a login page. BUT, the page it is attempting to redirect to is not my login page, it is a view called LogOnUserControl.ascx. This is a partial view that is not displayed by my login page.

I have no idea why it is doing this -- or maybe it is trying to redirect to some other page altogether, one which does display LogOnUserControl.ascx. Or maybe it is looking for anything with "LogOn" in the name? (Though the name of my login view is LogOn.aspx...)

How can I tell it what page to redirect to?

UPDATE: I do have this in the global.asax

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
    HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
    if (authCookie == null || authCookie.Value == "")
    {
        return;
    }
    FormsAuthenticationTicket authTicket = null;
    try
    {
        authTicket = FormsAuthentication.Decrypt(authCookie.Value);
    }
    catch
    {
        return;
    }
    string[] roles = authTicket.UserData.Split(new char[] { ';' });
    //Context.ClearError(); 
    if (Context.User != null)
    {
        Context.User = new System.Security.Principal.GenericPrincipal(Context.User.Identity, roles);
    }
}

... since I am using a non-standard way of defining roles; i.e., I am not using ASP.NET membership scheme (with role providers defined in web.config, etc.). Instead I am setting roles this way:

// get user's role
string role = rc.rolesRepository.GetUserType(rc.loginRepository.GetUserID(userName)).ToString();

// create encryption cookie
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
        1,
        userName,
        DateTime.Now,
        DateTime.Now.AddMinutes(120),
        createPersistentCookie,
        role //user's role 
        );

// add cookie to response stream
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);

System.Web.HttpCookie authCookie = new System.Web.HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);

(This is called after the user has been validated.)

Not sure how this could be impacting the whole thing, though ...

UPDATE: Thanks to Robert's solution, here's how I solved it -- extend AuthorizeAttribute class:

public class AuthorizeAttributeWithMessage : AuthorizeAttribute
{
    private string _message = "";
    public string Message
    {
        get { 
            return _message; 
        }
        set { 
            _message = value;
        }
    }

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAuthenticated)
        {
            // user is logged in but wrong role or user:
            filterContext.Controller.TempData.Add("Message", Message);
        }
        base.HandleUnauthorizedRequest(filterContext);
    }
}

Then in the LogOn view:

<% 
    if (HttpContext.Current.Request.IsAuthenticated)
    {
        // authenticated users should not be here
        Response.Redirect("/Home/Index");
    }
%>

And in the home page view:

<% if (TempData != null && TempData.Count > 0 && TempData.ContainsKey("Message"))
   { %>
<div class="largewarningtext"><%= TempData["Message"]%></div>
<% } %>

And atop the affected controllers:

[AuthorizeAttributeWithMessage(Roles = "Consultant,ExecAdmin", Message = "You do not have access to the requested page")]

This has the advantage of ALWAYS redirecting any authenticated user who ends up on Logon.aspx -- authenticated users should not be there. If there is a message in the TempData, it will print it out on the home page; if not, it will at least have done the redirect.

Was it helpful?

Solution

Login page is configured within web.config file.

But you probably already know that. The real problem here is a bit more complicated. I guess you're onto something very interesting here, since Login page barely authenticates a user. It doesn't check its authorization for a particular resource (which is your case here where authorization fails) so this shouldn't redirect to login page in the first place.

Checking AuthorizeAttribute source code, you should get a 401: Unauthorize Request response from the server. It doesn't redirect you to the login page (as I anticipated in the previous paragraph, since login is too stupid for that. So there most be something else in your code that doesn't work as it should.

Edit

As this page states:

If the site is configured to use ASP.NET forms authentication, the 401 status code causes the browser to redirect the user to the login page.

Based on this information it's actually forms authentication that sees this 401 and redirects to login (configured as you described in the comment).

But. It would be nice to present some message to the user why they were redirected to login page in the first place. No built-in functionality for that... Still this knowledge doesn't solve your problem, does it...

Edit 2

There are two patterns you can take that actually look very similar to the user, but work diferently on the server.

Simpler one

  1. Write your own authorization attribute (simply inherit from the existing one and add an additional public property Message to it), where you can also provide some sort of a message with attribute declaration like ie.

    [AuthorizeWithMessage(Role = "ExecAdmin", Message = "You need at least ExecAdmin permissions to access requested resource."]
    
  2. Your authorization attribute should populate TempData dictionary with the provided message (check documentation about TempData that I would use in this case) and then call into base class functionality.

  3. change your login view to check for the message in the TempData dictionary. If there is one, you can easily present it to the already authenticated user (along with a link to some homepage that they can access), so they will know why they are presented with a login.

Complex one

  1. create your own authorization filter (not inheriting from the original) and provide your own redirection to some authorization login view that would serve the login in case user has insufficient rights.

  2. create your custom login view that can in this case be strong type. Your authorization filter could populate it with the correct model. This model will include the message string and it can also provide the route link to a page where a user can go.

  3. custom configuration classes that serve this configuration of custom login page.

You could as well configure various different route definitions based on user rights. So for some rights they'd be presented with some page, but if they have some other rights, their route would point to a different route.

Which one to choose?

Go with the simpler one if it satisfies your needs, but if you want more control of the whole process I'd rather go with the complex one. It's not so complicated and it would give you full control of the insufficient login process. You could make it a much better experience for the users.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top