Ticket Caching Failing With Visual Studio 2017
See original GitHub issueThe TL;DR is that when running a CAS-enabled application in Visual Studio 2017 (IIS Express), the .NET cache provider (System.Web.Caching.Cache
) is immediately expiring the new service tickets as soon as they are added to the cache. Thus, no service ticket is ever able to be validated. Below is how I arrived at this conclusion.
Recently my development computer at work bit the dust, prompting me to reformat and re-install Windows 10. I took that opportunity to also upgrade to Visual Studio 2017 Enterprise (from 2015 Enterprise). When I began working on one of my CAS-enabled applications in VS2017, I immediately got the problem of a redirect cycle between my application and the CAS server.
First thing I checked was that it wasn’t another timing problem with the tolerance level for the SAML tickets, which it wasn’t. No matter what value I set for ticketTimeTolerance, the redirects still occurred. I debugged about all I could think of in my application with no indication of why the SAML ticket validation was failing and thus redirecting back to the CAS server. Even turning on the tracing per the instructions in this project’s README didn’t shine light on the source of the problem.
I finally broke down and added the DotNetCasClient project to my solution so that I could step through the code as the process was happening. Eventually I noticed that the service ticket was being added to the cache, but in a subsequent call to retrieve the ticket from the cache, null
was returned. I added some code for debugging to InsertTicket(). I was able to see that, immediately after inserting the ticket into the cache the cache item count increased by one. However, after a few debugging steps, the cache count decreased by one. I read in the comments for RemoveExpiredTickets() that the cache will automatically remove any expired items, so this got me thinking that the problem was with the expiration date for the cache item being inserted. By pure luck I was able to notice that the dates for the forms authentication ticket were using UTC while the expiration date for the cache item was in local time. Furthermore, I noticed the comments for the Insert()
method of System.Web.Caching.Cache
recommend to always use UTC dates as issues can occur with daylight savings time. The particular problem I am having wasn’t related to DST, but on a whim I changed the insert statement from:
HttpContext.Current.Cache.Insert(GetTicketKey(casAuthenticationTicket.ServiceTicket), casAuthenticationTicket, null, expiration, Cache.NoSlidingExpiration);
to:
HttpContext.Current.Cache.Insert(GetTicketKey(casAuthenticationTicket.ServiceTicket), casAuthenticationTicket, null, expiration.ToUniversalTime(), Cache.NoSlidingExpiration);
Immediately my application worked again. To double-check that the fix was correct I have created another dummy web application that consumes CAS in Visual Studio 2017. Using the local date/time, the redirect loop occurs. Modify it to use UTC date/time, the application works as expected.
I created the same dummy application in a copy of Visual Studio 2015 I have, targeting the same version of the .NET framework (v4.5.2). Using the same configuration settings for the client in web.config, I did the same tests, except this time the caching of the service tickets works regardless of using local or UTC time for expiration. As best as I can tell, the only difference between the projects is Visual Studio 2015 versus 2017. They target the same framework version, have all the same NuGet packages/versions, and use the same web.config settings.
Using those same dummy projects (one in 2015, the other in 2017) I basically copied the code from ContainsTicket() to list the contents of the cache on each page load. Using a special query string parameter, I could instruct the page load code to add a simple string value as an object to the cache with a +1 minute expiration date. In VS2017, using the local date for expiration, the string would be gone from the cache on the next load, even though the second request was only a few seconds after adding the object to the cache. Using the UTC date, the string would stay in the cache for a minute, then be removed. Switching to VS2015 yielded the expected result that it did not matter which form of the date/time was used for expiration. The string value would stay in cache for 1 minute, then expire.
According to the MSDN Documentation for the Insert() method, the recommendation for the absoluteExpiration
parameter value is for it to be a UTC date/time, not a local one. It is laughable, though, that the examples on the same page use DateTime.Now
, against their own recommendation. In any case, I think it would be a good thing to change the expiration to a UTC date/time regardless of it being the absolute root cause of the problem I am experiencing.
I believe the change would need to be made at the point where the expiration date is set, which is when the forms authentication ticket is being created in CreateFormsAuthenticationTicket() of CasAuthentication.cs
. The fromDate
and toDate
variables can be set as UTC dates, and the client has no problem in VS2017. I will create a pull request soon with the suggested changes along with documentation as to why UTC dates are needed. In the meantime, if anyone wants to weigh in on why this is happening between VS2015 (and earlier) and VS2017, it would be appreciated!
Issue Analytics
- State:
- Created 6 years ago
- Reactions:3
- Comments:38 (20 by maintainers)
Top GitHub Comments
Heads up everyone!
We now have a pre-release NuGet package feed for alpha/beta/unstable versions of the DotNetCasClient package. It can be found here: https://www.myget.org/gallery/dotnetcasclient-prerelease
Can you guys/gals please connect to the new feed and test the 1.1.0-beta0001 version of the package to see if this resolves the problem for you? Report back if you have any problems or if the issue was resolved for you.
There were some other changes in this pre-release version, those can be found in the ReleaseNotes.md file found in the release/1.1.0 branch right now.
Thanks!
@hokiecoder @danransom @webMan1 @serac @sbubaron @nicswan @scottt732 @mmoayyed
As a note, this issue manifested for me because .NET 4.7 was pushed as a critical update last week so was automatically added to production servers (yay!). So I imagine this will quickly become an issue for others as well. Thanks to @hokiecoder / @danransom for the fix. I was able to roll my own version and fix the issue for at least one of my apps.