Application_AuthenticateRequest keeps calling infinite redirects

4.4k Views Asked by At

This is a continuation of my previous question here.

I have tried to use the built in ASP Login mechanism but it didn't work for me. The primary reason being, I'm asked to keep it bare and simple.

Now, here is where I stand:

Web.config

<system.web>
    <sessionState timeout="10" />

    <authentication mode="Forms">
        <forms timeout="30" loginUrl="~/SecuredArea/LogInOut/log-in.aspx" />
    </authentication>

    <authorization>
        <allow users="?" />
    </authorization>

</system.web>

<location path="SecuredArea/AdminArea">
    <system.web>
        <authorization>
            <allow roles="administrators" />
            <deny users="*" />
        </authorization>
    </system.web>
</location>

<location path="SecuredArea/EmployeeArea">
    <system.web>
        <authorization>
            <allow roles="employees" />
            <deny users="*" />
        </authorization>
    </system.web>
</location>

Global.asax

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
    if (HttpContext.Current.User != null)
    {
        if (HttpContext.Current.User.Identity.IsAuthenticated)
        {
            IIdentity userId = HttpContext.Current.User.Identity;

            //if role info is already NOT loaded into cache, put the role info in cache
            //if (HttpContext.Current.Cache[userId.Name] == null)
            //{
            //  string[] roles;

            //  if (userId.Name == "admin")
            //  {
            //    roles = new string[1] { "administrators" };
            //  }
            //  else if (userId.Name == "member1")
            //  {
            //    roles = new string[1] { "employees" };
            //  }
            //  else
            //  {
            //    roles = new string[1] { "public" };
            //  }

              //1 hour sliding expiring time. Adding the roles in cache. 
              //This will be used in Application_AuthenticateRequest event located in Global.ascx.cs 
              //file to attach user Principal object.
            //  HttpContext.Current.Cache.Add(userId.Name, roles, null, DateTime.MaxValue, TimeSpan.FromHours(1), CacheItemPriority.BelowNormal, null);
            //}

            //now assign the user role in the current security context
            HttpContext.Current.User = new GenericPrincipal(userId, (string[])HttpContext.Current.Cache[userId.Name]);
        }
    }
}

I have commented the confusing code here because I don't want to access DB and loop through all possible employees here. For the admin account, it's easy, but for employee account, it's not possible.

log-in.aspx.cs

protected void ButtonLogOn_Click(object sender, EventArgs e)
{
    if (String.IsNullOrEmpty(txtUserName.Value.Trim()) || String.IsNullOrEmpty(txtPassword.Value.Trim()))
    {
        labelMessage.Text = MessageFormatter.GetFormattedErrorMessage("You can login using a username and a password associated with your account. Make sure that it is typed correctly.");
    }
    else
    {
        try
        {
            LoginPage loginBack = new LoginPage();
            int result = loginBack.VerifyCredentials(txtUserName.Value.Trim(), txtPassword.Value.Trim());

            switch (result)
            {
                case -9:
                //System needs provisioning
                labelMessage.Text = MessageFormatter.GetFormattedErrorMessage("SMB Password Reset System need provisioning. Login as Administrator.");
                break;

                case 0:
                //Enroll-able User
                // Success, create non-persistent authentication cookie.
                FormsAuthentication.SetAuthCookie(txtUserName.Value.Trim(), false);

                FormsAuthenticationTicket ticketEmployee =
                    new FormsAuthenticationTicket(
                        1,                                                      // version
                        txtUserName.Value.Trim(),           // get username  from the form
                        DateTime.Now,                                   // issue time is now
                        DateTime.Now.AddMinutes(10),    // expires in 10 minutes
                        false,                                              // cookie is not persistent
                        "employees");

                HttpCookie cookieEmployee = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticketEmployee));
                Response.Cookies.Add(cookieEmployee);

                SiteLogin.PerformAuthentication(txtUserName.Value.Trim(), false);
                break;

                case 1:
                //User not in required directory group
                labelMessage.Text = MessageFormatter.GetFormattedErrorMessage("You cannot login because you are not authorized.");
                break;

                default:
                //Bad name and/or password                              
                labelMessage.Text = MessageFormatter.GetFormattedErrorMessage("You can login using a username and a password associated with your account. Make sure that it is typed correctly.");
                break;
            }
        }
        catch (MessageSecurityException expMse)
        {
            //Bad name and/or password
            Debug.WriteLine("Error: " + expMse.Message);
            labelMessage.Text = MessageFormatter.GetFormattedErrorMessage("You can login using a username and a password associated with your account. Make sure that it is typed correctly.");
        }
        catch (Exception exp)
        {
            labelMessage.Text = MessageFormatter.GetFormattedErrorMessage("Some general error has occured. Message reads: " + exp.Message);
        }
    }
}

protected void ButtonAdminLogOn_Click(object sender, EventArgs e)
{
    if (String.IsNullOrEmpty(txtUserName.Value) || String.IsNullOrEmpty(txtPassword.Value))
        labelMessage.Text = MessageFormatter.GetFormattedErrorMessage("<strong>Login Please!</strong><hr/>You can login using a username and a password associated with your account. Make sure that it is typed correctly.");
    else
    {
        //if the log-in is successful
        if (txtUserName.Value == "admin" && txtPassword.Value == "AlphaBeta")
        {
            // Success, create non-persistent authentication cookie.
            FormsAuthentication.SetAuthCookie(txtUserName.Value.Trim(), false);

            FormsAuthenticationTicket ticketAdmin =
                new FormsAuthenticationTicket(
                    1,                                                      // version
                    txtUserName.Value.Trim(),           // get username  from the form
                    DateTime.Now,                                   // issue time is now
                    DateTime.Now.AddMinutes(10),    // expires in 10 minutes
                    false,                                              // cookie is not persistent
                    "administrators");

            HttpCookie cookieAdmin = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticketAdmin));
            Response.Cookies.Add(cookieAdmin);

            SiteLogin.PerformAdminAuthentication(txtUserName.Value.Trim(), false);
        }
        else
        {
            labelMessage.Text = MessageFormatter.GetFormattedErrorMessage("<strong>Login Failed!</strong><hr/>The username and/or password you entered do not belong to any Administrator account on our system.<br/>You can login using a username and a password associated with your account. Make sure that it is typed correctly.");
        }
    }
}

Lastly, utility class: SiteLogin.cs

public sealed class SiteLogin
{       
    public static void PerformAuthentication(string userName, bool remember)
    {
        FormsAuthentication.RedirectFromLoginPage(userName, remember);

        if (HttpContext.Current.Request.QueryString["ReturnUrl"] == null)
        {
            RedirectToDefaultPage();
        }
        else
        {
            HttpContext.Current.Response.Redirect(HttpContext.Current.Request.QueryString["ReturnUrl"]);
        }
    }

    public static void PerformAdminAuthentication(string userName, bool remember)
    {
        FormsAuthentication.RedirectFromLoginPage(userName, remember);

        if (HttpContext.Current.Request.QueryString["ReturnUrl"] == null)
        {
            RedirectToAdminDefaultPage();
        }
        else
        {
            HttpContext.Current.Response.Redirect(HttpContext.Current.Request.QueryString["ReturnUrl"]);
        }
    }

    /// <summary>
    /// Redirects the current user based on role
    /// </summary>
    public static void RedirectToDefaultPage()
    {
        HttpContext.Current.Response.Redirect("~/SecuredArea/EmployeeArea/EmployeeDefaultPage.aspx");
    }

    /// <summary>
    /// Redirects the current user based on role
    /// </summary>
    public static void RedirectToAdminDefaultPage()
    {
        HttpContext.Current.Response.Redirect("~/SecuredArea/AdminArea/AdminDefaultPage.aspx");
    }

    public static void LogOff()
    {
        // Put user code to initialize the page here
        FormsAuthentication.SignOut();

        //// Invalidate roles token
        //Response.Cookies[Globals.UserRoles].Value = "";
        //Response.Cookies[Globals.UserRoles].Path = "/";
        //Response.Cookies[Globals.UserRoles].Expires = new System.DateTime(1999, 10, 12);

        //Set the current user as null
        HttpContext.Current.User = null;
    }
}

Now, I'm getting a seriously inconsistent behavior whenever I try to login. Biggest issue is that once I try to access any of the protected pages for Admin or Employee, i get redirected to the Login page. I provide the details and attempt to login and in both (the simple admin login and the complex employee login) cases, I get an error in browser. IE wasn't making much sense but Firefox complain made some sense:

The page isn't redirecting properly Pale Moon has detected that the server is redirecting the request for this address in a way that will never complete. This problem can sometimes be caused by disabling or refusing to accept cookies.

I'm having a bad time debugging this but it seems that one inside Global.asax's Application_AuthenticateRequest() method, the method is just called over and over.

Funny thing is that if I go to the unprotected pages, I can see myself as logged in and get the "Welcome Admin!, Logout" in my header. This is dynamicly done through Master page

<div class="info-area">
    <asp:LoginView ID="HeadLoginView" runat="server" EnableViewState="false">
        <LoggedInTemplate>
            Welcome <span class="bold">
                <asp:LoginName ID="HeadLoginName" runat="server" />
            </span>! |
        </LoggedInTemplate>
    </asp:LoginView>
    <asp:LoginStatus ID="HeadLoginStatus" runat="server" LogoutAction="Redirect" LogoutText="Logout" LogoutPageUrl="~/SecuredArea/LogInOut/log-out.aspx" />
</div>

Can anyone identify the issue? I really need to close this thing today. Thanks.

EDIT

I have used fiddler and I see that once I press login, an infinite loop is created. I can show the behavior through images:

Thrown to login page upon accessing a secured area

Thrown to login page upon accessing a secured area

Entered the credentials and pressed the login button

Entered the credentials and pressed the login button

Credentials accepted and redirected back to secure page and redirected again to login and so forth

Credentials accepted and redirected back to secure page and redirected again to login and so forth

I choose the Cookies Tab of fiddler because there was a clear change detected there.

0

There are 0 best solutions below