The problem was that the Central Authentication Service (CAS) would invalidate the session tokens after 8 hours. And since most people start at 08h00, the problem would only manifest after 16h00. At this point, the authentication system and the response filter started a tug o’ war – the authentication system trying to redirect the response to the login page and the response filter trying to flush the filtered response back to the client.
Special thanks to Mike D for figuring it out, without even looking at a single line of code… I had to hand over one of my hats.
The solution was simple – to bypass the filter if the response is being redirected (301), line 11:
public class WebApplication : System.Web.HttpApplication { public WebApplication() { ReleaseRequestState += new EventHandler(OnReleaseRequestState); } void OnReleaseRequestState(object sender, EventArgs e) { // Ensure that the request is not being redirected before applying the filter if (HttpContext.Current.Response.StatusCode == (int)HttpStatusCode.Redirect) return; // Install the filter var response = HttpContext.Current.Response; var request = HttpContext.Current.Request; response.Filter = new SecurityFilterStream(response.Filter, request); } }
public class SecurityFilterStream : Stream { Stream Sink; HttpRequest Request; MemoryStream CachedStream = new MemoryStream(); bool IsClosing; bool IsClosed; public SecurityFilterStream(Stream sink, HttpRequest request) { Sink = sink; Request = request; } public override void Close() { IsClosing = true; Flush(); IsClosed = true; IsClosing = false; Sink.Close(); } public override void Flush() { if (IsClosing && !IsClosed) { // Get the response string from the stream Encoding encoding = HttpContext.Current.Response.ContentEncoding; string responseString = encoding.GetString(CachedStream.ToArray()); string filteredString = ""; // Loop through the response and alter the markup in individual elements using (StringReader reader = new StringReader(responseString)) { string line = string.Empty; do { line = reader.ReadLine(); if (line != null && line.Contains("sec-function")) { // [...] filteredString += line + "\r\n"; } } while (line != null); } // Overwrite the response with the altered string filteredString = filteredString.TrimEnd('\n').TrimEnd('\r'); byte[] buffer = encoding.GetBytes(filteredString); Sink.Write(buffer, 0, buffer.Length); CachedStream = new MemoryStream(); Sink.Flush(); } } public override int Read(byte[] buffer, int offset, int count) { return Sink.Read(buffer, offset, count); } public override void Write(byte[] buffer, int offset, int count) { CachedStream.Write(buffer, 0, count); } }