Click here to Skip to main content
15,889,542 members
Articles / Programming Languages / C#
Tip/Trick

Setup your ASP.NET Membership Login in MVC (or any project you want)

Rate me:
Please Sign up or sign in to vote.
4.44/5 (6 votes)
6 Mar 2015CPOL5 min read 25.4K   16   4
aspnet Membership Login in any project you want.

Introduction

We could encounter something like this:

The company has a Silverlight navigation application in C# with aspnet authentication implemented. Tables like "aspnet_Membership" ("aspnet_...") were set in the database.

So now, we need to create an MVC project using a custom login so we don't use all the related tables of the aspnet membership and create some custom tables and logic, but still need to use the main login tables.

In this article, I'll explain how to use your aspnet membership Login (with "Keep me signed in" option) and Roles in any project you want, and still using the same database with the same login/membership tables you have.

I have used this with C# in an MVC5 project with EntityFramework, but you can adapt the code to any project or language you want.

The Code

The database tables that I'll use look like this:

Image 1 Image 2

Image 3

Let's See the Login Methods

First, you need to declare some constants:

C#
private const int maxInvalidPasswordAttempts = 5;
private const int passwordAttemptWindow = 30; // minutes

The maxInvalidPasswordAttempts defines the maximum attempts the user can try and fail the login.

The passwordAttemptWindow defines the window of time after the "5" attempts the user can't login even if it inserts the right one.

Now let's try and check the login inserted by the user:

C#
public static string userNameAuthenticated { get; set; }
public static string fullNameAuthenticated { get; set; }
public static Guid userIdAuthenticated { get; set; }

public static bool ValidateLogin(string userName, string password)
{
    userNameAuthenticated = "";
    fullNameAuthenticated = "";
    userIdAuthenticated = Guid.Empty;
    bool isValid = false;

    using (var db = GetDB())
    {
        var user = db.aspnet_Users.SingleOrDefault(x => x.UserName == userName);
        if (user != null)
        {
            aspnet_Membership membership = db.aspnet_Membership.SingleOrDefault
			(x => x.UserId.Equals(user.UserId));
            if (membership != null)
            {
                isValid = CryptoHelper.ValidatePassword
			(password, membership.Password, membership.PasswordSalt);

                if (isValid && (!membership.IsLockedOut || (membership.IsLockedOut &&
                membership.FailedPasswordAttemptWindowStart.AddMinutes(passwordAttemptWindow) 
			<= DateTime.Now)))
                {
                    userIdAuthenticated = user.UserId;

                    var coll = db.collaborators.SingleOrDefault
                    (x => x.fk_UserId != null && x.fk_UserId.Value.Equals(user.UserId));
                    if (coll != null)
                        fullNameAuthenticated = coll.name;
                }
                else if (!isValid && !membership.IsLockedOut)
                {
                    UpdateLockoutState(membership, db);
                }
            }
        }
    }

    if (isValid)
        userNameAuthenticated = userName;

    return isValid;
}

private static void UpdateLockoutState(aspnet_Membership membership, medic56_crmBdEntities db)
{
    if (membership.FailedPasswordAttemptCount == 5)
        return;
    else
    {
        if (membership.FailedPasswordAttemptCount == 0)
        {
            membership.FailedPasswordAttemptWindowStart = DateTime.Now;
        }

        membership.FailedPasswordAttemptCount += 1;

        if (membership.FailedPasswordAttemptCount == 5)
        {
            membership.IsLockedOut = true;
            membership.LastLockoutDate = membership.FailedPasswordAttemptWindowStart;
        }

        db.SaveChanges();
    }
}

The GetDB() equals to new myDB_Entities().

In the ValidateLogin method, we check if the user exists, if the password is valid and if the user is locked or not and act accordingly.

Microsoft encrypts the password and saves it encrypted in the database. So we need to encrypt the inputted password and check it with the database value.

C#
isValid = CryptoHelper.ValidatePassword(password, membership.Password, membership.PasswordSalt)

After that, check if the user can login or not and if not, we will change the lockoutState.

C#
if (isValid && (!membership.IsLockedOut || (membership.IsLockedOut &&
membership.FailedPasswordAttemptWindowStart.AddMinutes(passwordAttemptWindow) <= DateTime.Now)))
{
    userIdAuthenticated = user.UserId;

    var coll = db.collaborators.SingleOrDefault(x => x.fk_UserId != null && 
					x.fk_UserId.Value.Equals(user.UserId));
    if (coll != null)
       fullNameAuthenticated = coll.name;
}
else if (!isValid && !membership.IsLockedOut)
{
    UpdateLockoutState(membership, db);
}

Going back to the encryption, we need to use this to do that:

C#
public class CryptoHelper
{
    public static string GetRandomSalt(int size = 24)
    {
        var random = new RNGCryptoServiceProvider();
        var salt = new byte[size];
        random.GetBytes(salt);
        return Convert.ToBase64String(salt);
    }

    public static string HashPassword(string pass, string salt)
    {
        byte[] bytes = Encoding.Unicode.GetBytes(pass);
        byte[] src = Convert.FromBase64String(salt);
        byte[] dst = new byte[src.Length + bytes.Length];
        Buffer.BlockCopy(src, 0, dst, 0, src.Length);
        Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
        HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
        byte[] inArray = algorithm.ComputeHash(dst);
        return Convert.ToBase64String(inArray);
    }

    public static bool ValidatePassword(string enteredPassword, string storedHash, string storedSalt)
    {
        var hash = HashPassword(enteredPassword, storedSalt);
        return string.Equals(storedHash, hash);
    }
}

The GetRandomSalt method is to use in a user creation scenario.

To Finish - If the Login is valid and the user will have access to the website/application, we do this:

C#
public static void ConfirmLogin()
{
    if (userIdAuthenticated.ToString().Trim() != "")
        using (var db = GetDB())
        {
            aspnet_Membership membership = db.aspnet_Membership.SingleOrDefault
						(x => x.UserId.Equals(userIdAuthenticated));
            if (membership != null)
            {
                membership.LastLoginDate = DateTime.Now;
                membership.IsLockedOut = false;
                membership.LastLockoutDate = Convert.ToDateTime("01-01-1754");
                membership.FailedPasswordAttemptWindowStart = Convert.ToDateTime("01-01-1754");
                membership.FailedPasswordAttemptCount = 0;
                db.SaveChanges();
            }
        }
}
 

Now let's check the user access to the application/website

If the method ValidateLogin returns true we need to check if the user have permission to login. This must be done BEFORE doing the method ConfirmLogin():

C#
public static bool hasApplicationAccess()
{
    return (!Authentication.userIdAuthenticated.Equals(Guid.Empty) && hasRolesAccess());
}

static bool hasRolesAccess()
{
    using (var db = GetDB())
        {
            List<Guid> userRoles = db.aspnet_UsersInRoles.Where(x => x.UserId.Equals(Authentication.userIdAuthenticated)).Select(x => x.RoleId).ToList();

            return db.aspnet_Roles.Where(x => userRoles.Contains(x.RoleId)).Any(x => x.LoweredRoleName == "administration" || x.LoweredRoleName == "backoffice");
        }
}

Using method hasRolesAccess() i'm saying only the users with Admin or BackOffice permissions can log in.

 

Now you could say "But in this methods where is the 'Keep me signed in' option?"

Well let's take a look:

Keep me signed in option

To do this we must remember one simple thing: never store the user password in a cookie or anywhere for that matter.

First thing to do is change the web.config file with the following code: (put this inside the <system.web> tag)

<sessionState timeout="30"/>
<authentication mode="Forms">
   <forms loginUrl="~/Login" timeout="30"/>
</authentication>

In the Login code we did earlier we had 3 variables that we'll need in the next pieces of code:

C#
public static string userNameAuthenticated { get; set; }
public static string fullNameAuthenticated { get; set; }
public static Guid userIdAuthenticated { get; set; }

Let's go back a bit and remember the order of the code:

  1. ValidateLogin (if valid goes to 2.)
  2. hasApplicationAccess (if valid goes to 3.)
  3. finally ConfirmLogin

After this 3 methods we will set the Keep me signed in option:

C#
FormsAuthentication.SetAuthCookie(userNameAuthenticated, keepSignedIn);

System.Web.HttpContext.Current.Session["UserName"] = Authentication.userNameAuthenticated;
System.Web.HttpContext.Current.Session["FullUserName"] = !string.IsNullOrWhiteSpace(Authentication.fullNameAuthenticated) ? Authentication.fullNameAuthenticated : Authentication.userNameAuthenticated;
System.Web.HttpContext.Current.Session["UserId"] = Authentication.userIdAuthenticated.ToString();

FormsAuthenticationTicket ticket = null;

if (keepSignedIn)
{
    System.Web.HttpContext.Current.Session.Timeout = 600; // 10 h
    ticket = new FormsAuthenticationTicket(1, "UserName", DateTime.Now, DateTime.Now.AddHours(10), false, userNameAuthenticated);
}
else
{
    System.Web.HttpContext.Current.Session.Timeout = 30; // 30 min
    ticket = new FormsAuthenticationTicket(1, "UserName", DateTime.Now, DateTime.Now.AddMinutes(30), false, userNameAuthenticated);
}

string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
cookie.Expires = ticket.Expiration;
this.Response.SetCookie(cookie);

url = Url.Action("Index", "Home");

The first line of code is to set an authentication cookie for the user in question. (If you want to know more about FormsAuthentication.SetAuthCookie() you can check the microsoft library MSDN).

Then i'm defining the Session variables to help me check later if the user is logged in or not:

C#
System.Web.HttpContext.Current.Session["UserName"] = Authentication.userNameAuthenticated;
System.Web.HttpContext.Current.Session["FullUserName"] = !string.IsNullOrWhiteSpace(Authentication.fullNameAuthenticated) ? Authentication.fullNameAuthenticated : Authentication.userNameAuthenticated;
System.Web.HttpContext.Current.Session["UserId"] = Authentication.userIdAuthenticated.ToString();

After that, we create a FormsAuthenticationTicket to set the username inside and define the expiration date of the ticket. If the user sets to true the Keep me signed in option i'm setting to 10 hours from 'now' the expiration date, if it's false i'm setting to 30 minutes.

For last, we need to encrypt the ticket and set it in a cookie. After that just redirect to the logged in home page:

C#
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
cookie.Expires = ticket.Expiration;
this.Response.SetCookie(cookie);

url = Url.Action("Index", "Home");

 

Check if the user is logged in

We always need to check if the user is logged in or not to view a webpage. If your using MVC we could do something like this:

C#
public class BackOfficeController : Controller
{
    public ActionResult Index()
    {
        if (!LoginController.isUserAuthenticated())
            return RedirectToAction("Index", "Login");

        return View();
    }
}

With the following code we can check if the user is already logged in using the session variables or the previous created cookie:

C#
public static bool isUserAuthenticated()
{
    if (System.Web.HttpContext.Current.Session["UserId"] != null && System.Web.HttpContext.Current.Session["UserId"].ToString().Trim() != "")
        return true;
    else
    {
        // Check forms authentication cookie
        if (System.Web.HttpContext.Current.Request.Cookies != null && System.Web.HttpContext.Current.Request.Cookies.AllKeys.Contains(FormsAuthentication.FormsCookieName))
        {
            try
            {
                HttpCookie cookie = System.Web.HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
                FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
                if (ticket.Name == "UserName")
                {
                    string userName = ticket.UserData;
                    // Set User Data
                    if (Authentication.ValidateLoggedInUser(userName))
                    {
                        if (RoleManager.hasApplicationAccess())
                        {
                            System.Web.HttpContext.Current.Session["UserName"] = Authentication.userNameAuthenticated;
                            System.Web.HttpContext.Current.Session["FullUserName"] = !string.IsNullOrWhiteSpace(Authentication.fullNameAuthenticated) ? Authentication.fullNameAuthenticated : Authentication.userNameAuthenticated;
                            System.Web.HttpContext.Current.Session["UserId"] = Authentication.userIdAuthenticated.ToString();

                            return true;
                        }
                    }
                }
            }
            catch (Exception) { }
        }
    }

    return false;
}

Notice that after getting the username (string userName = ticket.UserData;) we need to validate again if it's a valid user or not and set the static variables with data (the user could be deleted or had the permissions changed). Then let's check out the method ValidateLoggedInUser():

C#
public static bool ValidateLoggedInUser(string userName)
{
    userNameAuthenticated = "";
    fullNameAuthenticated = "";
    userIdAuthenticated = Guid.Empty;
    bool isValid = false;

    using (var db = GetDB())
    {
        var user = db.aspnet_Users.SingleOrDefault(x => x.UserName == userName);
        if (user != null)
        {
            aspnet_Membership membership = db.aspnet_Membership.SingleOrDefault(x => x.UserId.Equals(user.UserId));
            if (membership != null)
            {
                isValid = true;

                userIdAuthenticated = user.UserId;

                var coll = db.collaborators.SingleOrDefault(x => x.fk_UserId != null && x.fk_UserId.Value.Equals(user.UserId));
                if (coll != null)
                    fullNameAuthenticated = coll.name;
            }
        }
    }

    if (isValid)
        userNameAuthenticated = userName;

    return isValid;
}

The above method can be in the same class as the other one defined earlier ValidateLogin().

After this method we used the hasApplicationAccess() method we previously created to check the user access:

if (Authentication.ValidateLoggedInUser(userName))
{
   if (RoleManager.hasApplicationAccess())
   {

 

To Finish - I just need to explain why the use of Session variables and Cookies and not use only one thing.

  1. With the session variables if you have a webpage opened and you do an "open in new tab" option (or simply click a link to get to a new page), the application will check if the user is logged in or not by the session settings you defined. wich is more quicker because you don't need to access the Database. (notice that with the cookie we access the database in the method ValidateLoggedInUser())
  2. Imagine you closed all the webpages. So when you open a new page the application will check the method isUserAuthenticated(). When inside this method, the session variables are empty but the Cookie has the value defined.

 

And now you know how to set up the login with roles and keep me signed in option.

If you have any questions about this or other matters, feel free to contact me.

Happy coding with some nice coffee of course. :)

License

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


Written By
Software Developer
Portugal Portugal
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionRoles Pin
Terppe9-Mar-15 22:53
Terppe9-Mar-15 22:53 
AnswerRe: Roles Pin
Alexandre Paula3-Jun-15 6:20
professionalAlexandre Paula3-Jun-15 6:20 
GeneralGood timing Pin
smoore49-Mar-15 6:29
smoore49-Mar-15 6:29 
QuestionSecurity confussions Pin
kiquenet.com9-Mar-15 2:54
professionalkiquenet.com9-Mar-15 2:54 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.