Implementing "Remember Me" Feature in ASP.NET MVC

Kassem picture Kassem · Apr 11, 2011 · Viewed 65.7k times · Source

I'm trying to implement a "remember me" feature to my login form. I am using ASP.NET MVC as my web application. I managed to get the cookie stuff working, but I failed to automatically login the user in case he/she checked the remember me checkbox before. I know what the problem is but I do not know how to solve it.

In my HomeController I have the following:

private LoginViewModel CheckLoginCookie()
{
    if (!string.IsNullOrEmpty(_appCookies.Email) && !string.IsNullOrEmpty(_appCookies.Password))
    {
        var login = new LoginViewModel
                        {
                            Email = _appCookies.Email,
                            Password = _appCookies.Password
                        };

        return login;
    }
    return null;
}


public ActionResult Index()
{
    var login = CheckLoginCookie();
    if (login != null)
        return RedirectToAction("Login", "User", login);

    var viewModel = new HomeIndexViewModel
                        {
                            IntroText =
                                "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
                            LastMinuteDeals = new List<ItemsIndexViewModel>(),
                            TrustedDeals = new List<ItemsIndexViewModel>()
                        };
    return View(viewModel);
}

And in my UserController, I have the Login action method:

public ActionResult Login()
{
    return PartialView(new LoginViewModel());
}

[HttpPost]
public ActionResult Login(LoginViewModel dto)
{
    bool flag = false;
    if (ModelState.IsValid)
    {
        if (_userService.AuthenticateUser(dto.Email, dto.Password, false)) {
            var user = _userService.GetUserByEmail(dto.Email);
            var uSession = new UserSession
            {
                ID = user.Id,
                Nickname = user.Nickname
            };
            SessionManager.RegisterSession(SessionKeys.User, uSession);
            flag = true;

            if(dto.RememberMe)
            {
                _appCookies.Email = dto.Email;
                _appCookies.Password = dto.Password;
            }
        }
    }
    if (flag)
        return RedirectToAction("Index", "Home");
    else
    {
        ViewData.Add("InvalidLogin", "The login info you provided were incorrect.");
        return View(dto);
    }
}

So basically, what I thought I would do is to redirect the user from the Index action result on the home controller in case there was a login cookie. But the problem is that the RedirectToAction will trigger the GET Login action method and not the POST which takes care of logging in the user.

Am I going completely wrong about this? Or is there some way I could call the POST Login method using RedirectToAction or any other way?

Answer

David Glenn picture David Glenn · Apr 11, 2011

First off, you should never store the user's credentials in a cookie. It's incredibly insecure. The password will be passed with every request as well as being stored in plain text on the user's machine.

Second, don't reinvent the wheel, especially when security is concerned, you'll never get it right.

ASP.Net already provides this functionality securely with Forms Authenitcation and Membership Providers. You should take a look into that. Creating a default MVC project will include the basic authentication setup. The official MVC site has more.

Update

You can still use .NET forms authentication without implementing a membership provider. At a basic level it would work like this.

You enable forms authentication in you web.config

<authentication mode="Forms">
  <forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>

You decorate the actions or the controllers you would like to secure with the [Authorize] attribute.

[Authorize]
public ViewResult Index() {
  //you action logic here
}

Then create a basic login action

[HttpPost]
public ActionResult Login(LoginViewModel dto) {

  //you authorisation logic here
  if (userAutherised) {
    //create the authentication ticket
    var authTicket = new FormsAuthenticationTicket(
      1,
      userId,  //user id
      DateTime.Now,
      DateTime.Now.AddMinutes(20),  // expiry
      rememberMe,  //true to remember
      "", //roles 
      "/"
    );

    //encrypt the ticket and add it to a cookie
    HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName,   FormsAuthentication.Encrypt(authTicket));
    Response.Cookies.Add(cookie);

    return RedirectToAction("Index");

  }

}