This week I've been working on a problem which at the beginning seemed to be just a matter of configuration but that then took some more time that I expected to resolve... so, here it is. In my ASP.NET 2.0 application I edit my web.config to set slidingExpiration=”true” and cookieless=”true” in Forms authentication. Basically what happens is that after half of the timeout set in <forms> element is elapsed, the user is redirected to the login page; e.g. I set <forms timeout="3" cookieless="UseUri" enableCrossAppRedirects="true" /> (timeout 3 minutes), if I postback the page after 1 minute and 35 seconds, I’m redirected to the login page (but the Forms authentication ticked should still be valid…). This can’t be a cookie problem because we are not using cookies…
First, a quick review of sliding expiration: "When the SlidingExpiration is set to true, the time interval during which the authentication cookie is valid is reset to the expiration Timeout property value. This happens if the user browses after half of the timeout has expired. For example, if you set an expiration of 20 minutes by using sliding expiration, a user can visit the site at 2:00 PM and receive a cookie that is set to expire at 2:20 PM. The expiration is only updated if the user visits the site after 2:10 PM. If the user visits the site at 2:09 PM, the cookie is not updated because half of the expiration time has not passed. If the user then waits 12 minutes, visiting the site at 2:21 PM, the cookie will be expired".
You can easilly reproduce the behavior yourself creating an ASP.NET 2.0 application (I used aspnetdb for authentication) with a DropDownList and a Submit button and assure you have the following in your web.config:
<authentication mode="Forms"> <forms name=".MYASPXAUTHTEST" loginUrl="login.aspx" enableCrossAppRedirects="true" protection="All" timeout="3" cookieless="UseUri" path="/" requireSSL="false" slidingExpiration="true" /> </authentication>
You can use the following code for your Default.aspx page:
<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Default test</title> </head> <body> <form id="form1" runat="server"> <div> <table> <tr> <td><asp:DropDownList ID="ddlTest" runat="server"> </asp:DropDownList></td> </tr> <tr><td> </td></tr> <tr> <td><asp:Button ID="btnSubmit" runat="server" Text="Button" /> </td> </tr> </table> </div> </form> </body> </html>
And in your code file:
Partial Class _Default Inherits System.Web.UI.Page Protected Sub Page_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load Response.Write("Submit at: " & DateTime.Now.ToString() & "<br>") Response.Write("Next submit at: " & _ DateTime.Now.AddSeconds(90).ToString() & "<br>") Response.Write("IsPostBack = " & IsPostBack) If Not IsPostBack Then ddlTest.Items.Add("<select>") ddlTest.Items.Add("test1") ddlTest.Items.Add("test2") ddlTest.Items.Add("test3") End If End Sub End Class
Remember you also need a login.aspx page with a standard input form to provide username and password; then to reproduce the behavior:
Well... it turned out that the combination of cookieless authentication and sliding expirations means that users will be randomly redirected back to their site anytime we detect that less than 50% of the TTL remains on the ticket. Because the ticket is cookieless, the only way we can refresh the ticket is to force a redirect with the new ticket placed in the Url used for the redirect.
Nice, but I still have a doubt: if we can renew the authentication ticked during a postback without going through the authentication process, why we can’t do it by changing the ticket in the Url? Isn’t this the same principle? Moreover, why the IsPostBack returns false even if that is a real postback?
The problem is that with cookieless forms authentication, the ticket is actually part of the Url. When the timeout is updated in the ticket, that means the serialized text representation of the ticket also changes. As a result we need to change the Url itself to reflect the new value. Unlike cookies, you can’t rewrite a Url on the server and have the client transparently pick up the new Url. Instead you have to force a redirect because the redirect forces the browser to reload the document and update the Url that you see in the address bar. That redirect process is also why IsPostBack returns false. The redirect is not a postback triggered by a client-side ASP.NET control. As a result, then the GET request for the redirect comes back to the server, the server doesn’t recognize it as a postback.
Thanks to Stefan Schackow for discussing this with me!
CheersCarlo
Interesting. The slidingexpiration attribute has a lot of issues. I'm using cookies and have issues across subdomains. The cookies are refreshed after half way through the forms timeout on the primary domain. As soon as it gets to the subdomain and waiting over half way through the expected timeout, the cookie doesn't get refreshed. So basically, if you doodle around on your subdomain sites, you can only do this for the length of time of your timeout before having to log-in again.
Uhm... just to be sure (sorry for the silly question): did you set SlidingExpiration="true" in the web.config for the subdomain?
For some reason, I knew I needed to set SlidingExpiration="true" on the subdomain sites, but it must have slipped my mind, but that fixed the site authentication issues. It was a hot issue a few weeks ago, and I used one of my MSDN Support tickets. Surprisingly, the support was pretty good. Felt like an idiot when I saw SlidingExpiration was missing, but got it fixed regardless.
Some other interesting session timeout issues:
I was caught up in trying to figure out why my sessions were only lasting 20 minutes (default settings) and found out that the app pool gets reset when there's no activity on the site, not to mention the default 29 hours app pool reset issue.
Thanks!
Why do you say "Surprisingly, the support was pretty good"? :-) Was this your first time, or did you had a bad experience before?
Just curious ;-)
It was the first time I had a support call to MS, and was surprised to really get an answer. We've all had are struggles finding answers on the MSDN site, so I thought I was going to be in for a load of trouble.
They were able to remote into my PC easily (a little too easy...) and help me out fairly quickly with someone that knew what they were doing. Usually the service industry doesn't provide very good customer service, but in this case, I can't complain at all. MS support really pulled through.
Good to know, thanks. :-)
Just a couple of words about the remote assistance: I guess you used Live Meeting or more likely Easy Assist (which is a cut down Live Meeting version customized for our support tasks), and both of them require the Support Engineer to create a dedicated session on our Live Meeting servers (this expires in a few hours if not used), then you have to connect to an https URL with a given password and install the EasyAssist ActiveX which should automatically uninstall at the end of the session. And of course the whole session goes through a protected https connection, then you have to explicitly share your desktop and we are connected in "view only" mode unless you explicitly grant us control.
This just to clarify for the people whom have not had a support call with us and might be a litte scared by "easily remote into my pc"... since you have some actions to take, it's supposed that you know what you're doing and we're not just "haking" in your system ;-)