I am in a situation where my application is mixed classic ASP and ASP.Net. I am currently migrating the classic ASP app page-by-page into ASPX as I am developing new functionality. When I have to touch an old page, I convert it.
In an attempt to make them appear to be one app to the user, I need to manage the timeout of the two applications to appear as one. The classic ASP app was using Session variables for this, but obviously that would not work easily in a mixed environment. Luckily, the ASP app is not heavily dependent on Session variables; it only uses a few that are set during login, and a couple on particular pages. So after logging in the login redirects to a classic ASP page to set the few necessary Session variables, and that page redirects to the home page.
- Login using the ASPX login page
- Create a cookie containing context
- Redirect to asp page setting session variables
- Redirect to ASPX home page
Initially, I thought it would be easy to manage the timeouts, just use the cookie generated by the ASPX login page. The classic ASP pages can check the cookie and update its expiration if needed. Of course, it was not that simple. The cookie that is created by the ASPX pages contains the authentication ticket generated by the current http context. Its expiration is essentially the same as the cookie, based on the time set in web.config. The problem lies with the classic ASP pages. They can update the cookie’s expiration, but not the ticket. If a user spends too much time using classic ASP pages, the ticket in the cookie will be expired next time the user visits an ASPX page, even if the cookie is good. The ASPX pages consider a user timed out if either the cookie has expired or the ticket has expired. They will need to login again without actually timing out simply due to using classic ASP pages for a little too long.
My solution results from the ratio of classic ASP to ASPX pages. Right now it is about 5 classic ASP per ASPX page. Using this ratio I changed the authentication ticket timeout to be 5 times as long as the cookie timeout. In order to make this work the ASPX application needs to manage the cookie created by ASPX pages instead of allowing it to occur manually. At the login page, I allow the ASPX cookie to be created as normal using my own CreateAuthCookie.
Public Shared Function CreateAuthCookie(ByVal UserID As String) As HttpCookie Const TicketLife As Double = 4 Dim Timeout As String = ConfigurationSettings.AppSettings("Timeout").ToString
Dim principalText As String Dim buffer As New IO.MemoryStream Dim formatter As New Runtime.Serialization.Formatters.Binary.BinaryFormatter
formatter.Serialize(buffer, HttpContext.Current.User) buffer.Position = 0 principalText = Convert.ToBase64String(buffer.GetBuffer())
'create a forms ticket
Dim ticket As New FormsAuthenticationTicket(1, HttpContext.Current.User.Identity.Name, _
DateTime.Now, DateTime.Now.AddMinutes(Double.Parse(Timeout) * TicketLife), _
False, principalText)
'Encrypt the ticket
Dim encTicket As String = FormsAuthentication.Encrypt(ticket)
'This is the cookie used by both ASP and ASP.Net, ASP never retrieves the value
Dim TheCookie As New HttpCookie(FormsAuthentication.FormsCookieName)
TheCookie.Path = FormsAuthentication.FormsCookiePath
TheCookie.Value = encTicket
TheCookie.Expires = DateTime.Now.AddMinutes(Double.Parse(Timeout))
Return TheCookie
End Function
In the Global.asax, I added code to take the cookie, get the ticket from it and use the ticket to set the HttpContext.Current.User and update its expiration if necessary. If the cookie or the ticket has expired here it will automatically redirect to the login page as a function of ASP.Net.
Private Sub Global_AcquireRequestState(ByVal sender As Object, ByVal e As EventArgs) _
Handles MyBase.AcquireRequestState Dim cookie As HttpCookie = Request.Cookies.Get(FormsAuthentication.FormsCookieName) If Not cookie Is Nothing Then 'Cookie found, decrypt the value and recreate the authentication ticket
' and user context for the page Dim ticket As FormsAuthenticationTicket Try If cookie.Value = "" Then
'For some reason the cookie exists but there is no value,
' remove the cookie try again cookie.Expires = DateTime.Now.AddMinutes(-15) Response.Redirect("../login/login.aspx", False) Else ticket = FormsAuthentication.Decrypt(cookie.Value) End If FormsAuthentication.RenewTicketIfOld(ticket) Dim buffer As New IO.MemoryStream(Convert.FromBase64String(ticket.UserData)) Dim formatter As New Runtime.Serialization.Formatters.Binary.BinaryFormatter HttpContext.Current.User = CType(formatter.Deserialize(buffer), IPrincipal) Catch ex As Exception
'Could not set the ticket, remove the cookie cookie.Expires = DateTime.Now.AddMinutes(-15) Response.Redirect("../login/login.aspx", False) End Try End If End Sub
All my ASPX pages inherit from my own base page instead of System.Web.UI.Page. In the PreRender event for my base page my CreateAuthCookie gets set again and added to the Response. My base page has already taken the UserID from the HttpContext and set it as a page property.
Private Sub Page_PreRender(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.PreRender Dim AuthCookie As HttpCookie = CreateAuthCookie(Me.User_ID.ToString) Response.Cookies.Add(AuthCookie) End Sub
The classic ASP pages use an include file that just increments the cookie timeout. It also checks the status of the ASP session and resets it if the cookie is still good.
Dim xQString if Request.QueryString <> "" then
xQString = "?" & Request.QueryString
end if
If Request.Cookies("EnCoreUser") = "" then
'No cookie, session expired
Session.Abandon
Response.Cookies("EnCoreUser").Expires = DateAdd("n", -15, Now())
response.redirect "../login/login.aspx?ReturnUrl=" & _
Server.URLEncode(Request.ServerVariables("URL") & xQString)
else
'Got aspx cookie, check asp session
if Session("SessionId") <> Session.SessionID then
if Request.Cookies("asp") = "" then
response.redirect "../login/login.aspx?ReturnUrl=" & _
Server.URLEncode(Request.ServerVariables("URL") & xQString)
else
'Cookie is good but classic ASP session ended. Recreate the
' session without forcing another login.
Response.Redirect "../login/verify.asp?userid=" & user_id & "&url=" & _
Server.URLEncode(Request.ServerVariables("URL") & xQString)
end if
else
'All is well, so reset the cookie
Dim sTempx sTempx = CStr(Request.Cookies("EnCoreUser"))
Response.Cookies("EnCoreUser").Path = "/"
Response.Cookies("EnCoreUser").Expires = DateAdd("n", 35, Now())
Response.Cookies("EnCoreUser") = sTempx
end if
end if
As the ratio classic ASP pages to ASPX pages changes, all I need to do is reduce the multiplier to shorten the ticket length accordingly. Eventually, of course, all this work will get tossed out when there are no more classic ASP pages.