locked
Issue with multi-user login page RRS feed

  • Question

  • User1482929570 posted

    I have a website (asp.net) that uses a custom-made log-in page to authenticate users. The code is very simple, it simply looks at the database to see whether username/password pair match those stored in the database and if the answer is yes, it creates a user object and stores it in the Session. It then redirects user to a new page called ViewActiveFiles.aspx (below):

    MyUser loggedinUser = userData.GetUser(UserIdTextBox.Text, PasswordTextBox.Text); //the class that reads the database
    if (loggedinUser != null)
    {
      Session.Timeout = 10;
      userid = loggedinUser.UserId;
      Session["User"] = loggedinUser;
      Response.ClearHeaders();
      Response.AddHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");
      Response.Redirect("./ViewActiveFiles.aspx");
    }

    So when I test the application on the server, for one user, this code works perfectly (as far as I can tell anyway). The problem occurs though, when a second user (different log-in) attempts to log-in at the same time. From my debugging I can see that the new login page reads database and creates a new user and stores the new user in the Session and redirects the page to VeiwActiveFiles. However, in the ViewActiveFiles in the PageLoad code, the Session["User"] returns null!!! I am not experienced in programming but this is specially weird to me since as I mentioned when I run the code with one user, everything works great. It's only when the second user logs in concurrently, that the session seems to lose the User object. For the second user, I have tried same computer but different browser, and different computer too, same issue.

    Any help is much appreciated!!

    Thursday, June 11, 2015 11:07 PM

Answers

  • User1482929570 posted

    Thanks for your help mgebhard. I figured out what the issue was. It was this line: Response.AddHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");

    I had originally copied it from some asp forum without knowing what it did, I still don't know what it does. But as soon as I commented out the line, my code works as expected. I guess one of those sent parameters was resetting the session somehow.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Saturday, June 13, 2015 5:20 PM

All replies

  • User475983607 posted

    Have you considered using ASP's built in authentication/authorization framework?  This change alone will make your application much more robust and extensible.

    Regarding, the current error.  You'll need to show us the rest of your code.  For example, the MyUser class and userData.GetUser() method.

    Regarding logging in twice.  Are you logging in on a different machine or the same machine? 

    Friday, June 12, 2015 9:03 AM
  • User1482929570 posted

    Yes, I did consider using the built in authentication but since this is my first project, I don't feel comfortable using the built-in functionality as I'm afraid when it comes to maintainability, I wouldn't be able to understand what's going on behind the scenes. Hopefully for the future projects.

    Here's my code for userData.GetUser() method:

    public MyUser GetUser(string userName, string password)

    {

      DataSet myDataSet = new DataSet();

      string strCommandText = "SELECT Client.ClientId,  LTRIM(RTRIM(FirstName)) AS FirstName, " +

      " LTRIM(RTRIM(MiddleName)) AS MiddleName, LTRIM(RTRIM(LastName)) AS LastName, " +

      " LTRIM(RTRIM(Type)) AS PersonType, ClientType, Email, Address, City, postalCode " +

      "  FROM UserPassword, PersonTypes, Client " +

      " WHERE LTRIM(RTRIM(UserId)) = @UserId " +

      "   AND LTRIM(RTRIM(Password)) = @Password " +

      "   AND Client.ClientType = PersonTypes.Id " +

      "   AND Client.ClientId = UserPassword.ClientId";

      // Create the command

      SqlCommand myCommand = new SqlCommand(strCommandText, myConnection);

      // Add the parameters

      myCommand.Parameters.AddWithValue("@UserId", userName);

      myCommand.Parameters.AddWithValue("@Password", password);

      // Open the connection

      myConnection.Open();

      // Execute the query

      SqlDataReader myReader = myCommand.ExecuteReader();

      MyUser user = null;

      if (myReader.Read())

      {

        user = new MyUser();

        user.FirstName = Convert.ToString(myReader["FirstName"]);

        user.MiddleName = Convert.ToString(myReader["MiddleName"]);

        user.LastName = Convert.ToString(myReader["LastName"]);

        user.UserId = Convert.ToInt32(myReader["ClientId"]);

        user.RoleDescription = Convert.ToString(myReader["PersonType"]);

        user.RoleId = Convert.ToInt32(myReader["ClientType"]);

        user.EmailAddress = Convert.ToString(myReader["Email"]);

        user.Address = Convert.ToString(myReader["Address"]);

        user.City = Convert.ToString(myReader["City"]);

        user.PostalCode = Convert.ToString(myReader["PostalCode"]);

      }

      // Close the connection

      myConnection.Close();

      return user;

    }

    I also have the following as global variables in my UserPasswordDB class that GetUser accesses:

    private static string strConnectionString = ConfigurationManager.ConnectionStrings["SqlConnectionString"].ConnectionString;

    private SqlConnection myConnection = new SqlConnection(strConnectionString);

    Here below is my MyUser class, just a bunch of properties:

    public class MyUser

        {

            public int UserId { get; set; }

            public int RoleId { get; set; }

            public string RoleDescription { get; set; }

            public string FirstName { get; set; }

            public string MiddleName { get; set; }

            public string LastName { get; set; }

            public string EmailAddress { get; set; }

            public string Address { get; set; }

            public string City { get; set; }

            public string PostalCode { get; set; }

            public string GetFullName()

            {

                return FirstName + " " + LastName;

            }

        }

    To answer your last question, I have tried both: I have logged in with different users on different machines. Also, logged in with different users using same machine but different browsers. In both scenarios, I get the same problem.

    Friday, June 12, 2015 11:07 AM
  • User475983607 posted

    Nothing really sticks out as a problem except the query. Are you sure the query is returning one and only one record? Have you run the query against the DB directly to verify?

    Yes, I did consider using the built in authentication but since this is my first project, I don't feel comfortable using the built-in functionality as I'm afraid when it comes to maintainability, I wouldn't be able to understand what's going on behind the scenes. Hopefully for the future projects.

    Actually the contrary is true, your current implementation can have serious issues as there is no guarantee session will persist as you expect. Session is stored in server memory (default InProc) and managed in the worker process. Session persists as long as the worker process is running. If the worker process restarts, Session is lost for all users.

    Shared hosting providers recycle worker process as they see fit. This keeps the server memory healthy for all shared apps.  Also, certain errors cause the worker process to restart. Therefore, the recommended process when dealing with Session is to check if Session is null and if it is null rebuild Session. That's tough to do if session is persisting authentication data.

    On the other hand the built authentication framework uses tokens persisted on the user's machine. If the client has a token that means the client has authenticated. On each request thereafter (after authentication) the token is passed to the server. The framework automatically converts the token into a type which is available within the request context. This type of authentication framework is much more extensible.

    Friday, June 12, 2015 12:49 PM
  • User1482929570 posted

    I haven't run the query directly. I'll do that and advise.

    As mentioned previously, I'm new to this game so I had no idea about the characteristics of Session that you explained (thanks for the info). I still like to figure out what my problem is. But maybe I'll copy my project and try to add the built-in authentication framework.

    Friday, June 12, 2015 3:13 PM
  • User1482929570 posted

    I just ran the query directly against the DB and got one row back which is what I was expecting. Also, the more I think about it, it cant be a db issue since in my second log-in, the log-in page does create a MyUser object by the right user information and stores it in Session (I saw this in my debugging). It's just that something happens after the call to Response.Redirect("./ViewActiveFiles.aspx"); and before loading the next page that the session either loses its MyUser object or something happens to the Session itself that the MyUser object goes missing. Any other ideas?

    Friday, June 12, 2015 10:38 PM
  • User475983607 posted

    I don't see anything wrong with  the posted code.  You may have a bug elsewhere or something is causing the app pool to restart.  You can verify if the session restarts by monitoring a change in the session Id.

    What does viewActiveFiles.aspx do? Deleting or modifying files in wwwroot can cause the app pool to restart.

    Saturday, June 13, 2015 6:57 AM
  • User1482929570 posted

    Thanks for your help mgebhard. I figured out what the issue was. It was this line: Response.AddHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");

    I had originally copied it from some asp forum without knowing what it did, I still don't know what it does. But as soon as I commented out the line, my code works as expected. I guess one of those sent parameters was resetting the session somehow.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Saturday, June 13, 2015 5:20 PM