Jan
11
Written by:
Don Worthley
1/11/2008 12:43 AM
Have you been to a site recently where the login button contained a checkbox which said ‘Remember Me.’ I love that feature! Yes! I’m going to be remembered and all I have to do is click on a checkbox.
Well, it turns out that a lot of sites (the one’s built using Microsoft’s ASP.NET 2.0) say they’ll remember you, but most of the time they don’t. I know. That’s life. But it doesn’t have to be that way!
For ASP.NET developers, this is a frustrating issue, especially for developers who are moving from a 1.1 version of their product to the new (well, now old) 2.0 version of the product. It turns out that the ASP.NET team at Microsoft decided to make a change to the way ‘Remember Me’ is implemented behind the scenes.
One of the Content Management Systems that I encourage clients to use is DotNetNuke, and Cathal Connolly from the core team did a great job of explaining the history of this issue in the DNN blog. Scott Guthrie has also posted regarding this issue on his blog.
To restate the issue, since this is a separate blog post, the authentication cookie in ASP.NET supported a ‘Remember Me’ option through a Boolean parameter which basically set an explicit expiration on the authentication cookie used by Forms authentication. For the 1.1 version of ASP.NET, the team decided to hardcode the expiration to 50 years. As you can imagine, the security folks didn’t like the idea of cookies sitting around on people’s machines for 50 years.
So, Microsoft changed the timeout to coincide with the forms authentication timeout set inside of web.config. The idea was that if members wanted a persistent cookie, they could just bump the forms authentication timeout up to some really high number. The problem with this, as Cathal points out, is that this removes the option to set a shorter timeout for those who choose not to use the ‘Remember Me’ option. So, if you’re forgetful like me and you log into someone else’s workstation to show them something, that browser instance could be up for days leaving open the possibility that anyone using the workstation could access the site with your privileges.
Code Alert - If you don't like reading code, skip the rest of this post.
The first thing that many try in their quest for a solution to this problem is to manually set the expiration of the cookie that’s created. In fact, this is the case with the code in the DNN login control (the UserLogin procedure in UserController.vb )
If user.IsSuperUser Then
AddEventLog(portalId, user.Username, user.UserID, PortalName, IP, UserLoginStatus.LOGIN_SUPERUSER)
Else
AddEventLog(portalId, user.Username, user.UserID, PortalName, IP, UserLoginStatus.LOGIN_SUCCESS)
End If
' set the forms authentication cookie ( log the user in )
FormsAuthentication.SetAuthCookie(user.Username, CreatePersistentCookie)
'check if cookie is persistent, and user has supplied
‘custom value for expiration
If CreatePersistentCookie = True Then
Dim PersistentCookieTimeout As Integer
If Not Config.GetSetting("PersistentCookieTimeout") Is Nothing Then PersistentCookieTimeout = _
Integer.Parse(Config.GetSetting("PersistentCookieTimeout")) 'only use if non-zero, otherwise leave as asp.net value
If PersistentCookieTimeout <> 0 Then
'locate and update cookie
Dim authCookie As String = FormsAuthentication.FormsCookieName
For Each cookie As String In HttpContext.Current.Response.Cookies
If cookie.Equals(authCookie) Then
HttpContext.Current.Response.Cookies(cookie).Expires = _
DateTime.Now.AddMinutes(PersistentCookieTimeout)
End If
Next
End If
End If
End If
You can see that the cookies collection is enumerated until the authentication cookie is found and the expires property is set to some configurable value.
Unfortunately, this doesn’t work as expected. The reason is that there are actually two entities with an expiration: the cookie and the authentication ticket stored in the cookie. So, how do you set the expiration property of the authentication ticket? In fact, how do you access the ticket?
To access the ticket, you have to manually create it. So, the DNN code above could be re-written as follows:
If user.IsSuperUser Then
AddEventLog(portalId, user.Username, user.UserID, PortalName, IP, UserLoginStatus.LOGIN_SUPERUSER)
Else
AddEventLog(portalId, user.Username, user.UserID, PortalName, IP, UserLoginStatus.LOGIN_SUCCESS)
End If
'check if cookie is persistent, and user has supplied custom value for expiration
If CreatePersistentCookie = True Then
Dim PersistentCookieTimeout As Integer
If Not Config.GetSetting("PersistentCookieTimeout") Is Nothing Then PersistentCookieTimeout = Integer.Parse(Config.GetSetting("PersistentCookieTimeout")) 'only use if non-zero, otherwise leave as asp.net value
If PersistentCookieTimeout <> 0 Then
'manually create authentication cookie
'first, create the authentication ticket
Dim AuthenticationTicket As FormsAuthenticationTicket = _
New FormsAuthenticationTicket(user.Username, True, PersistentCookieTimeout)
'encrypt it
Dim EncryptedAuthTicket As String = _
FormsAuthentication.Encrypt(AuthenticationTicket)
Dim AuthCookie As HttpCookie = _
New HttpCookie(FormsAuthentication.FormsCookieName, EncryptedAuthTicket)
'set cookie expiration to correspond with ticket expiration.
AuthCookie.Expires = AuthenticationTicket.Expiration
HttpContext.Current.Response.Cookies.Set(AuthCookie)
End If
End If
Else
' set the forms authentication cookie ( log the user in )
FormsAuthentication.SetAuthCookie(user.Username, False)
End If
The key to this solution is getting access to the authentication ticket by creating the ticket manually. You can see that in the constructor for the FormsAuthenticationTicket, we are able to pass in an integer which sets the timeout in minutes for the ticket.
Even if you’re not using DNN but you do need to provide two separate timeouts--one for your users who want to persist their cookies, and one for those who don’t--you can use code similar to the code above. Also, if you’re using the ASP.NET login control, you may find this thread on the asp.net forums interesting. Stefan Schackow does a great job of explaining how you can use the LoggedIn event to manually configure the authentication ticket.
Tags:
2 comment(s) so far...
that's an interesting distinction that I hadn't considered (i did know that the expiry of the "ticket" was stored as a part of the encrypted value). My understanding is that when the user revisits the site, the forms authentication code in the framework resets both the cookie and the ticket expiry, so your solution won't work. Please try to test it by visting multiple times. I'll put together some test code myself this weekend and see if this works.
By Cathal on
1/11/2008 7:49 AM
|
I have been logged in for a couple days now to the DNN installation where I tested this feature with a PersistentCookieTimeout set to 5256000 and a forms authentication timeout of 1 minute. If I log out and log back in without the ‘Remember Me’ option, my ticket expires after 1 minute.
Also, I figured you were probably aware of the distinction and if you’re like me you may have tested this very scenario before but due to some strange permutation in the set of variables in your test environment have come to the conclusion that it didn’t work.
By Don Worthley on
1/12/2008 2:24 AM
|