1

I'm trying to create a persistent cookie (not using the built-in auth) when the user logs in.

The only way I could figure to do this was to create a cookie with an expiry date set to DateTime.Now.AddYears(1)

Since I want to allow the user to log out, I do have a logout button on the layout for every page behind the login form.

This logout button looks like this:

<li>@Html.ActionLink("Log Out", "Logout", "Home")</li>

The confusing thing to me is that this code doesn't get rid of the cookie from the browser. it takes me back to the login form, but I can still easily navigate back to the protected pages and it'll still remember who I am based on my previous login.

Here's my code:

    [AllowAnonymous]
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public ActionResult Login(User model)
    {
        try
        {
            DoLogin(model.EmailAddress, model.Password);
            return Json(new
            {
                Message = "Success",
                IsOK = bool.TrueString
            });
        }
        catch (Exception ex)
        {
            SendError("/", ex);
            return ReportError(ex, "USER LOGIN");
        }
    }

    private void DoLogin(string EmailAddress, string Password)
    {
        var user = db.Users.Include("UserRole").FirstOrDefault(x => x.EmailAddress == EmailAddress);

        if (Hashing.ValidatePassword(Password, user.Password))
            generateCookie(user);
    }

    private void generateCookie(Models.User u)
    {
        HttpCookie userCookie = new HttpCookie("Ortund");
        userCookie.Values["userid"] = Convert.ToString(u.Id);
        userCookie.Values["fname"] = u.FirstName;
        userCookie.Values["lname"] = u.LastName;
        userCookie.Values["role"] = u.UserRole.RoleName;
        userCookie.Expires = DateTime.Now.AddYears(1);
        Response.Cookies.Add(userCookie);
    }

So why isn't this clearing my cookie?

EDIT

So now I've modified my code (see below) as per suggestions in the answers on the question.

I added in a Logout Action on my controller and it does this:

    public ActionResult Logout()
    {
        Session.Clear();
        HttpCookie userCookie = new HttpCookie("Ortund");
        userCookie.Expires = DateTime.Now.AddYears(-1);
        Response.Cookies.Add(userCookie);

        return View("Index");
    }

While my login still appears to work okay, the Request Cookie isn't updating with the new login details. Here's what it looks like after I do the login:

    Request.Cookies["Ortund"] {System.Web.HttpCookie} System.Web.HttpCookie  
    Domain null string  
    Expires {0001-01-01 12:00:00 AM} System.DateTime  
    HasKeys true bool  
    HttpOnly false bool  
    Name "Ortund" string  
    Path "/" string  
    Secure false bool  
    Shareable false bool
Ortund
  • 8,095
  • 18
  • 71
  • 139
  • 1
    1. You need to write the expired cookie to the Response, not read and set it back to the Request. 2. FormsAuthentication.SignOut() might or might not remove the cookie, this depends on how you have forms authentication configured in the web.config. Looking at your code you might not have that configured correctly. – Igor Sep 14 '15 at 18:46
  • @Igor actually I don't have forms auth set up at all so I guess I can just take that out huh – Ortund Sep 14 '15 at 18:48
  • As an aside, using `action` as a querystring variable could be a bit confusing as the term action has specific meaning in MVC. Why not create an actual `Logout` action that performs the cookie invalidation and then redirects to the `Index` action? – Mister Epic Sep 14 '15 at 18:50

2 Answers2

3

You need to write the expired cookie to the Response, not read and set it back to the Request.

var cookie = Request.Cookies["Ortund"];
cookie.Expires = DateTime.Now.AddSeconds(-1);
Response.Cookies.Add(cookie);

I would do some reading on how to implement Forms Authentication in c#. This will give you a better starting point then trying to roll your own authentication cookie which will lead to security holes. The standard forms authentication already has much to offer and a simple API to access it including ways to encrypt the ticket, provide identity information etc.

Alternatively you can also look into asp.net identity. This is Microsoft's newest API to managing identities and providing authentication and authorization. It also integrates with SSO like using Facebook or Microsoft identity stores to authenticate the user.

EDIT - based on request for more information

Here is some sample code using basic built in asp.net forms authentication. This example is cookie based. You would also need to configure the web.config to make use of it. Once this is done the rest happens automatically and you can get the user's information at any time using HttpContext.User.Identity. In the web.config you also need to add your decryption and validation keys.

public sealed class AuthController : Controller
{
    [AllowAnonymous] // get the view to login
    public ActionResult Login()
    {
        return View();
    }
    [HttpPost]
    [AllowAnonymous] // execute a login post
    public ActionResult ExecuteLogin(LoginModel model)
    {
        // validate credentials
        var ticket = new FormsAuthenticationTicket(1, model.UserName, DateTime.Now, DateTime.Now.AddHours(3), model.RememberMe, /*anything else you want stored in the ticket*/ null);
        var encryptedTicket = FormsAuthentication.Encrypt(ticket);
        var isSsl = Request.IsSecureConnection; // if we are running in SSL mode then make the cookie secure only

        var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
        {
            HttpOnly = true, // always set this to true!
            Secure = isSsl,
        };

        if (model.RememberMe) // if the user needs to persist the cookie. Otherwise it is a session cookie
            cookie.Expires = DateTime.Today.AddMonths(3); // currently hard coded to 3 months in the future

        Response.Cookies.Set(cookie);

        return View(); // return something
    }
    [Authorize] // a secured view
    public ActionResult SecuredView() {
        return View();
    }
    [Authorize] // log the user out
    public ActionResult Logout()
    {
        System.Web.Security.FormsAuthentication.SignOut();
        return RedirectToAction("Index", "Home");
    }
}

Here is the web.config changes

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
... 
  <system.web>
    <authentication mode="Forms">
      <forms name="myAuthCookie" ticketCompatibilityMode="Framework40" cookieless="UseCookies" requireSSL="false" timeout="180" protection="Encryption" />
    </authentication>
    <machineKey decryption="AES" decryptionKey="key here" validation="HMACSHA256" validationKey="validation key here" />
  </system.web>
</configuration>
Igor
  • 60,821
  • 10
  • 100
  • 175
  • Igor, if you could review the question so as to see the new edit and help out fixing the problem, I'd really appreciate it – Ortund Sep 14 '15 at 20:31
  • @Ortund I would not roll your own security, it's a bad idea. The symptom is that the cookie expiration flag is treated differently depending on the browser that receives it. Technically a browser might completely ignore the value and not do anything. Again, I would highly recommend you look into the forms authentication controls as already implemented by Microsoft. It's configurable in the web.config and you can use cookies to persist the users authenticated state and identity. When you expire a session using their mechanism you can be guaranteed that session is now invalid. – Igor Sep 14 '15 at 21:07
  • @Ortund - i added a sample you can use to get your authentication working using the basic api that is included in asp.net. – Igor Sep 14 '15 at 21:32
2

you need to write the cookie again into the browser

HttpCookie userCookie = new HttpCookie("Ortund");
userCookie.Expires = DateTime.Now.AddYears(-1);
Response.Cookies.Add(userCookie);

and not only

Request.Cookies["Ortund"].Expires = DateTime.Now.AddSeconds(1);

a good explanation why, it's in my own answer from same time ago...

Community
  • 1
  • 1
balexandre
  • 73,608
  • 45
  • 233
  • 342
  • Mkay so when I'm checking that the user has a valid cookie, its clear that checking `if (Request.Cookies["Ortund"] != null) { // show the secure stuff }` won't work because the cookie still exists... So how do I validate this? – Ortund Sep 14 '15 at 19:02
  • just delete the cookie on Logout, but you still need to **write** the cookie, not only to change the `Expiry` property. BTW, if I know the cookie name, I can always create it in the browser and login without even know a username or password ... **be careful** of what you're doing... (you don't even encode the values...) – balexandre Sep 14 '15 at 19:12
  • Please have another look at the question. I'm having trouble with the cookie now - The cookie values aren't changing when I try to login again – Ortund Sep 14 '15 at 20:27