Click here to Skip to main content
Click here to Skip to main content

How to create a serverfarm-compatible login for an ASP.NET application (plus general advice on how to secure it)

By , 13 May 2013
 

To use the full functionality of this example a running MySQL server and the ODBC connector for MySQL/.NET Framework (Version 3.5.1) are needed. 

Disclaimer

The software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and non-infringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.  

Please read the chapter "Regarding security" carefully before you are going to use this example in an actual application.

Contents 

  1. Introduction
  2. Background
  3. Concept Overview  
    1.  The Login process 
    2.  The Revisit process 
    3.  The Logout process
  4. Using the code
    1. Class files  (Business logic)
    2. The Application 
    3. Points of Interest
  5. Regarding security
  6. Extend this example 
  7. Publication History

Introduction     

This article shows you a nice and easy way to create a basic login functionality in ASP.NET using the .Net Framework 4. The special thing is that I am not using the built-in login functions which are provided by .Net 4 because they might not be working when the ASP.Net application is hosted on a server farm. Therefore this article shows how an ASP.NET app can be created even if there is no possibility to use the built-in classes and interfaces. Off course this example works for non-server-farm hosted applications as well as it does for server-farm hosted examples - It is just nice, easy and clean and helps you to create a login functionality for your ASP.NET application within half a day.

This example is using a master page to handle most of the login functionalities because I am not confident with user controls which are loaded on every single page just because someone was too lazy to add a master page and find out how he can access properties on the master page from the content page.

Background

As I am a passionate shooter and Member of the local shooting club I started developing a web-based portal were the local shooting club can announce shooting competitions and recruit staff for these competitions. Everyone who wants to attend a shooting competition can have a profile there and sign up for these competitions.  

They wanted to have a log-in functionality because (obviously) they don't wanted everyone to be able to create shooting competitions and valid registration data which is consistent is needed,

The problem number one I had while programming was: The app is located on a server farm. Therefore I was not able to use the Session class which comes built-in from ASP.NET. I decided to create my own log-in functionality. And because I was messing around with this task for a pretty long while until I found a solution I decided to share my knowledge with the rest of the ASP.NET developing world.

Concept Overview

I thought about which classes and ASP.NET pages I will need and made it to this draft diagram:

To provide the full Login and Session functionality I need (obviously) a class named "Session" and a class named "User". To read and write Session-related cookies and database entries I need a static class named "SessionManager". "Session" contains all the information which is necessarily in a user-session related way, e.g. a session key so it is not too easy to steal the session, a session ID to uniquely identify a session, off course the ID of the logged in user and an expiration date because otherwise the session would be available for ever and might give attackers an entry point.  The Master page has a property called "LoggedInUser" which is accessible by the several aspx-pages which are using the Master page. This property is accessing the "SessionManager" class to get the currently logged in user. 

The Login process

The Login process on my page looks as scripted here:

Source Description
Master page, DataAccess class

Get the user by the submitted credentials. When user with with submitted credentials does exist, set the LoggedInUser property of the Master Page, call the Login-Method of the SessionManager class and pass the user with the matching credentials as parameter.

SessionManager class - Login Method

Create a random session key and set the expiration date. Create the cookie* and a session object -> add the session key, the expiration date, the user id of the user to both of them. Add the cookie* to the HttpResponse*cookie: See more about cookies in the MSDN 

The Revisit process

When the user revisits the page after logging in, the verification process goes on as scripted here:

Source Description
Master page

Call the "GetCurrentSessionUser" method on the SessionManager class with the HttpRequest as parameter.

SessionManager class - GetCurrentSessionUser method

Gets the cookie* from the HttpRequest. If the cookie* where the user and session data is stored does not exist we are assuming that there is no user logged in and return simply "null". If the cookie* does exist the SessionManager checks for the session with the equivalent session key in the database. Afterwards it checks whether the session has already expired. The SessionManager gets the user data from the database and returns it to the caller of the method. When either the Session has expired or the Session can not be loaded from the database "null" is returned.*cookie: See more about cookies in the MSDN

Master page

Sets the "LoggedInUser" property to the value which was received from the "GetCurrentSessionUser" method (either a "User" object or a "null" value).

The Logout process

It is handled as scripted here: 

Source Description
Master page

Call the "Logout" method on the SessionManager class with the HttpResponse  asparameter.

SessionManager class - Logout method

Gets the cookie where the Session data is stored from the HttpResponse and deletes it. Afterwards the Session with the Session key which was stored in the deleted cookie is deleted from the database.*cookie: See more about cookies in the MSDN

 Master page

Redirects the request to the Index page (or any other page - free choose-able)   

Using the code   

In the chapter "Using the code" you get an overview about the code and an explanation why the code does what in a particular way.

Class files  (Business logic)

This chapter gives an overview over the different class files which are included in the solution and provides additional information to them.

User.cs

A normal class to store an user of the application. 

Fields
  • id
  • surname
  • lastname
  • password
  • mailadress
  • street
  • postCode
  • city
  • homePhone
  • cellPhone 
Properties
  • For each field listed above is a property (get and set) available)
  • FullName 
    • returns the first name and the last name of the user as formatted string (Last_name First_name) 
 Methods
  • SerializeFromSqlDataReader(OdbcDataReader databaseReader)
    public void SerializeFromSqlDataReader(OdbcDataReader databaseReader)
    {
        Int32.TryParse(databaseReader[0].ToString(), out id);
        surname = databaseReader[1].ToString();
        lastname = databaseReader[2].ToString();
        password = databaseReader[3].ToString();
        mailadress = databaseReader[4].ToString();
        street = databaseReader[5].ToString();
        postCode = databaseReader[6].ToString();
        city = databaseReader[7].ToString();
        homePhone = databaseReader[8].ToString();
        cellPhone = databaseReader[9].ToString();
    } 

Session.cs

A normal class to store information of a Session.

Fields
  • id
    • Unique identification in the database; primary key
  • key
    • Session key to identify a session at the server side when a postback occurs.
  • expireDate
    • DateTime which defines when a session is expired.
  • foreignIdUser
    • Id of the user which is logged in with this session
Properties
  • For each field listed above is a property (get and set) available.
 Methods 
  • SerializeFromSqlDataReader(OdbcDataReader databaseReader)
    public void SerializeFromSqlDataReader(System.Data.Odbc.OdbcDataReader reader)
    {
        Int32.TryParse(reader[0].ToString(), out id);
        key = reader[1].ToString();
        DateTime.TryParse(reader[2].ToString(), out expireDate);
        Int32.TryParse(reader[3].ToString(), out foreignIdUser);
    }

SessionManager.cs

A static class to handle Session cookies and Session database entries

Fields
  • This class has no fields defined
Properties
  • This class has no properties defined
 Methods 
  • CreateSession(User user, HttpResponse response) 

    • Creates a new Session, inserts the new session into the database and adds a cookie containing the session information to the HttpResponse
    • public static void CreateSession(User user, HttpResponse response)
      {
          Session session = new Session();
          session.ExpireDate = DateTime.Now.AddHours(5);
          session.ForeignIdApplicationUser = user.Id;
          session.Key = GetSessionKey(50);
          DataAccess dataAccess = new DataAccess();
          dataAccess.InsertSession(session);
          HttpCookie sessionCookie = 
            new HttpCookie("MarcoBertschi_CP_BasicASPDotNetLoginExample");
          sessionCookie["SessionUqKey"] = session.Key;
          sessionCookie["SessionUserId"] = user.Id.ToString();
          response.Cookies.Add(sessionCookie);
      }
  • GetCurrentSessionUser(HttpRequest request)

    • Loads the Key of the current session from the Cookie, gets the Session information from the database and returns the User which is assigned to the session and returns him. If there is no cookie or the sessions' ExpireDate is smaller than the current time "null" is being returned. 
    • public static User GetCurrentSessionUser(HttpRequest request)
      {
          User currentUser = null;
          try
          {
              string sessionKey = request.Cookies[
                "MarcoBertschi_CP_BasicASPDotNetLoginExample"]["SessionUqKey"];
              DataAccess database = new DataAccess();
              Session session = database.GetSessionByKey(sessionKey);
              if (session.ExpireDate > DateTime.Now)
              {
                  currentUser = 
                    database.GetApplicationUserById(session.ForeignIdApplicationUser);
              }
          }
          catch (Exception)
          {
              Console.WriteLine("Not able to get Cookie from Request.");
          }
          return currentUser;
      }
  • GetCurrentSessionInformation(HttpRequest request) 

    • Loads the Key of the current session from the Cookie, gets the Session information from the database and returns the session information to the caller of the method. 
    • internal static Session GetCurrentSessionInformation(HttpRequest request)
      {
          Session session = null;
          try
          {
              string sessionKey = 
                request.Cookies[
                "MarcoBertschi_CP_BasicASPDotNetLoginExample"]["SessionUqKey"];
              DataAccess database = new DataAccess();
              session = database.GetSessionByKey(sessionKey);
          }
          catch (Exception)
          {
              Console.WriteLine("Not able to get Session information from Request.");
          }
          return session;
      }
  • Logout(HttpRequest request) 

    Deletes the Session cookie from the HttpRequest and deletes the Session with the key which was stored in the deleted Session cookie from the database. 

    internal static Session GetCurrentSessionInformation(HttpRequest request)
    {
        Session session = null;
        try
        {
            string sessionKey = 
              request.Cookies["MarcoBertschi_CP_BasicASPDotNetLoginExample"]["SessionUqKey"];
            DataAccess database = new DataAccess();
            session = database.GetSessionByKey(sessionKey);
        }
        catch (Exception)
        {
            Console.WriteLine("Not able to get Session information from Request.");
        }
        return session;
    }
  • GetSessionKey(int length)

    Creates a new session identifier key. This method is only within the "SessionManager" class available and is not exhibited to public. 

    private static string GetSessionKey(int length)
    {
        // give character which you want in random generated session key.
        char[] allowCharacter = 
          "abcdefgijkmnopqrstwxyzABCDEFGHJKLMNPQRSTWXYZ23456789!?".ToCharArray();
        Random RandChar = new Random();
        string randomSessionKey = "";
        for (int i = 0; i < length; i++)
        {
            randomSessionKey += allowCharacter[RandChar.Next(0, allowCharacter.Length)];
        }
        return randomSessionKey;
    }

DataAccess.cs

A normal DataAccess class to access data which is stored in the database. Remember that you have to set the connection parameters of your MySQL server to run the App with its full functionality:

#region MySQL Database connection
 /// <summary>
 /// Connects the MySQL Database
 /// </summary>
 /// <returns>Connection to the MySQL database</returns>
 private OdbcConnection Connect()
 {
   string conString = "DRIVER={MySQL ODBC 3.51 Driver};" + 
     "Server=DEFINE;Port=DEFINE;User=DEFINE;Password=DEFINE;Database=basicaspnetlogin;";
   throw new NotImplementedException(
     "Define Server, User, port and password for MySQL Database!");
   OdbcConnection dbConnection = new OdbcConnection(conString);
   dbConnection.Open();
 
   return dbConnection;
 }
#endregion

I do not describe it further because there is nothing which is important related to this article. Remember the Database server and Connector requirements written down at the top of the page!

The Application

This chapter is dedicated to the application (Look and feel including code behind) and will explain how to use the previous listed classes.  

Look and feel

The page looks by default like this:

When clicking on the button on the top right-hand side (loginLinkButton if there is no user logged in - otherwise there is the logoutLinkButton located) a simple ASP.Net-panel becomes visible. It contains two text boxes to enter email address and password, including a button to log in and another button to cancel the operation. After clicking both of the buttons the panel will be hidden again.

Use the code - Check whether there is a user logged in  

The setting the controls visibility of the master page happens all on the Page_Init event on the master page (and there are different things happening whether a user is logged in or not): 

public User LoggedInUser
{
    get
    {
       return SessionManager.GetCurrentSessionUser(Request);
    }
}
 
protected void Page_Init(object sender, EventArgs e)
{
   VerifyUserLogin();
}
 
private void VerifyUserLogin()
{
   if (LoggedInUser != null)//There is a user signed in
   {
      logoutLinkButton.Text = string.Format("{0} {1}  (Logout)", 
           LoggedInUser.Lastname, LoggedInUser.Surname);
      Session currentSession = SessionManager.GetCurrentSessionInformation(Request);
      logoutLinkButton.ToolTip = string.Format(
        "Session Information>> Key={0}|Expriation Date={1} at {2}", 
        currentSession.Key, currentSession.ExpireDate.ToLongDateString(), 
        currentSession.ExpireDate.ToLongTimeString());
      logoutLinkButton.Visible = true;
      loginLinkButton.Visible = false;
   }
   else//No user signed in
   {
      loginLinkButton.Text = "Login";
      logoutLinkButton.Visible = false;
      loginLinkButton.Visible = true;
   }
}

The LoggedInUser property returns the currently logged in user by using the GetCurrentSessionUser(HttpRequest request) method of the SessionManager class. The method VerifyUserLogin() is setting the visibility of the loginLinkButton and the logoutLinkButton. This must be done in the code behind of the master page because both controls are placed on the master page. If a user is logged in the logoutLinkButton becomes visible and the loginLinkButton becomes invisible (and the other way around if there is no user logged in).

On a normal aspx-page I am using a bit of a different approach: 

I can still use a LoggedInUser property but this is now a bit different looking than the property in the master page code behind file:

private User LoggedInUser
{
    get
    {
        return ((
         MarcoBertschi.CP.BasicASPDotNetLogin.Design.
             MasterPages.LoginEnabled)Master).LoggedInUser;
    }
}

The property LoggedInUser on the content page is now accessing the property LoggedInUser on the master page:

return ((
  MarcoBertschi.CP.BasicASPDotNetLogin.Design.MasterPages.LoginEnabled)Master).LoggedInUser; 

 This causes a central place where we can get the currently logged in user and allows us to do the main user authentication at a centralized page and we are able to switch the authentication mechanism for the whole web application in a quick and easy way and we are not bound to the class "SessionManager" which means we can change to the authentication mechanisms provided by ASP.NET if they become useable for server farms or the application is being moved away from a server farm to a single server. 

The check whether a user is logged in works on a content page the same way as on the master page.

Points of Interest  

It was a real surprise for me to hear that the built-in ASP.NET login utilities and classes are not working on server farm based hosting providers but there was a silent confidence somewhere in my head that there will be a nice and quite easy solution for the issue

I struggled many times when developing this Login functionality. My main problem during the first approaches was that I always lost either the whole user data or the session data when a post-back occurred on my page.

I was a bit surprised by the last approach which worked and I described in this article. In the end I can say it was fun finding a solution for this tricky problem and I am confident that this article will help many other guys who have the same problem as I had.

Regarding security

After reading Espen Harlinn's article "Security: It’s Getting Worse"  I thought that it is good an reasonable to add another chapter to this article to clarify some points regarding the security of my snippet. Most of them seemed obvious to me when I wrote this article, but some of them also came to my mind while reading Espens's before mentioned article.

As Espen stated in his article, it is really really really bad practice to store a username and/or the password in a cookie. Even when you have encrypted the content of the cookie, it makes your application vulnerable to attacks and exploits. Every code can be knocked out - Especially when you provide such fragile information as passwords and usernames are!

Let's come to the point - The session key can be stolen from a cookie, that is fully correct. But there is also a session time-out defined which causes the user to be logged out after a specified time. Why does this session time-out work without exploring the software? Simples. The date on which the session expires is stored at the database. Should anyone be able to hack your database he most likely steal the data from the database, and not just save some session keys from expiring. Anyways - Whenever your webserver database gets hacked you should at least be able to reckon when this worst case happens.
If your database is secured enough and can't be accessed from anywhere else than the localhost you are save.

Which leads me to another really remarkable topic: If you do not understand anything from server configuration in general and IIS configuration in special - Do not touch it. Hire a specialist with the necessary knowledge, or leave it to a professional hoster. If you do not leave the configuration of all the server stuff, which is security-vulnerable, you behave the same as someone who goes to an Orthopedist to get his eyes checked. It will end in tears, because whoever does this ends up having healthy feet - But gets run over by a truck because he can't see anything. 

Also, the passwords are not encrypted in my example - Add the encryption when you want this thing to work secure! You also maybe want to rethink the password policy - Every type of password is allowed.
If you are ever going to send passwords by email, remember that EVERYONE on the internet can catch the email containing the password and log-in instead of the user who should have received the password. The best way to solve this issue is to encrypt the email.

Aah, encryption. I have heard of it, but no one has interest in hacking my application. This. Is. Wrong. You have never been so wrong. Every hacker is interested in hacking an application. The easier you make it, the higher is the chance that thousands of amateur hackers go and try to steal data from your application. Encrypt whatever you do when a user is logged in (as Espen's written it in his article - use Transport level security (TLS). And the latest SSL version). Why? Because if you do not, the whole application has a damn huge security leak. Which exploits a very very easy way to steal sessions from your users. Because the session key will be transmitted in clear-text when no SSL is used everyone who can use a network sniffing tool (As WireShark, for example) can steal a session from one of your users.

That is all so far, remember the content of this chapter when using my Login functionalities and you and your users will be pretty safe.

Extend this example

You can easily extend this example. If you need to assign different roles to a user you can just add a class called Role and add a field of type Role to the User class. The Role class itself could have a description, a name an Id and a permission level. By using the permission level field of the Role class you can afterwards have different permissions for different pages and different users in a nice, easy and quick way.

Publication History 

Thanks to Espen Harlinn who's article "Security: It’s Getting Worse" has inspired me to write the chapter "Regarding security".

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Marco Bertschi
Software Developer Apprenticeship
Switzerland Switzerland
Member
Marco made his first steps in programming with C# and the .NET framework. Afterwards he switched over to C++ development with Visual Studio and the Qt framework. Java is also a part of his experience but he ain't gonna use it if you do not force him with all your strength to do so.
 
As a part of the "generation Facebook" he grew up with social networks, video platforms as YouTube and Vimeo are, Smartphones and HD screens but also remembers the noisy annoying analog modems and Windows 95 including DOS games.
 
He never gets enough sleep and beside working in the R&D department of a international medical diagnostic company he is the guy behind the SMGT Web-Portal (A web application to help local shooting clubs organizing their events and staff based on ASP.NET and a MySQL server) and contributing articles to the CodeProject. If he isn't either coding or sleeping you can meet him at the Muay Thai training or somewhere else in central Switzerland.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5mvpMichael Haephrati10 Mar '13 - 11:12 
GeneralRe: My vote of 5professionalMarco Bertschi13 May '13 - 10:01 
QuestionWill this approach support multiple usersmemberRam Sagar Mourya7 Feb '13 - 19:51 
AnswerRe: Will this approach support multiple usersprofessionalMarco Bertschi13 May '13 - 10:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130513.1 | Last Updated 13 May 2013
Article Copyright 2013 by Marco Bertschi
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid