Question

I have switched SQL-Server Reporting Services 2012 (SSRS 2012) to forms authentication so we can use it over internet.

I could not find a forms-authentication sample for SSRS 2012 anywhere, so I had to take the SSRS 2008R2 one, and adapt it for 2012, for Single-Sign-On (SSO).

At that point everything seemed to be working as expected; I even managed to get SSO working across domains.

But now I have a problem:

I was testing all reports (more than 200) with Google Chrome, because I had to insert a little JavaScript that alters td border-size for that the HTML displays right in non-IE5-QuirksMode. After about the 50th report, I suddenly got:

"HTTP 400 Bad Request - Request Too Long"

After that, I could not view any other report, not even those that did work previously.

The problem seems to be caused by too many cookies, and indeed, when I deleted a few "*_SKA" (Session Keep Alive?) cookies, it began working again.

SSRS Sucks

My problem now is that I don't know what causes this "cookie overflow". I also don't know, if this a bug in Chrome, a bug in vanilla SSRS or a bug caused by the new forms authentication.

All I do in the new forms-authentication that has something to do with cookies is this:

using System;
using System.Collections.Generic;
using System.Text;


namespace FormsAuthentication_RS2012
{


    internal class FormsAuthenticationWorkaround
    {

        public static void RedirectFromLoginPage(string strUser, bool createPersistentCookie)
        {
            //string url = System.Web.Security.FormsAuthentication.GetRedirectUrl(strUser, true);
            string url = GetRedirectUrlWithoutFailingOnColon(strUser, createPersistentCookie);
            SQL.Log("User: '" + strUser + "' ReturnUrl", url);

            if (System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Response != null)
                System.Web.HttpContext.Current.Response.Redirect(url);
        }


        // https://github.com/mono/mono/blob/master/mcs/class/System.Web/System.Web.Security/FormsAuthentication.cs
        // @MSFT: WTF are u guys smoking ?
        public static string GetRedirectUrlWithoutFailingOnColon(string userName, bool createPersistentCookie)
        {
            if (userName == null)
                return null;

            System.Web.Security.FormsAuthentication.SetAuthCookie(userName, true, "/");

            string returnUrl = null;

            if (System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Request != null)
                returnUrl = System.Web.HttpContext.Current.Request.QueryString["ReturnUrl"];

            if (returnUrl != null)
                return returnUrl;

            returnUrl = System.Web.Security.FormsAuthentication.DefaultUrl;
            return returnUrl;
        }


    }


}

And as this code creates the "sqlAuthCookie" that one sees at the bottom. There is only one "sqlAuthCookie" so I don't think this can possibly be a forms-authentication bug.

The problem seem to be the SKA cookies, that AFAIK have nothing to do with forms-authentication and everything to do with Vanilla SSRS.

The only other thing that I could see as a reason for this is the change in forms-authentication-cookie timeout to 720 minutes that I entered in the forms-authentication section in the web.config file.

  <authentication mode="Forms">
    <forms loginUrl="logon.aspx" name="sqlAuthCookie" timeout="720" path="/">
    </forms>
  </authentication>

Does anybody know what I can do to prevent getting flooded by Session Keep-Alive cookies (except for deleting those cookies manually)?

It's no problem for me per se, apart from it being highly annoying, but it's going to be a problem because the users probably won't be very understanding of that...

Was it helpful?

Solution

Issue listed as fixed in SQL Server 2012 SP1 CU7. (see comments from Microsoft in the connect issue)
But still present in SQL-Server 2014.


The later section applies, if you can't install SQL Server 2012 SP1 CU7:

OK, got the answer myself.

The keep-alive cookie is issued every time one opens a report.
Now, that becomes a problem when one opens (or refreshs, or changes to another page), say, more than 110 - 120 reports, without closing the browser.

So we safeguard by deleting the excess cookies, and set a safe boundary at appx. 1/2 of the assumed maximum of 120 cookies.

The cookies are HttpOnly, and expire when one closes the browser (session cookies).
They are non-secure HttpOnly cookies, which is why I failed in my attempt to delete them via JavaScript.
So it becomes necessary to delete them on the server side. Since we can't modify ReportServer, we have to use inline-scripting.

<body style="margin: 0px; overflow: auto">


<script type="text/C#" runat="server">
protected string ClearSessionKeepAliveCookiesToPreventHttp400HeaderTooLong()
{
    if(Request == null || Request.Cookies == null)
        return "";

    if(Request.Cookies.Count < 60)
        return "";

    // System.Web.HttpContext.Current.Response.Write("<h1>"+Request.Cookies.Count.ToString()+"</h1>");
    for(int i = 0; i < Request.Cookies.Count; ++i)
    {
        if(StringComparer.OrdinalIgnoreCase.Equals(Request.Cookies[i].Name, System.Web.Security.FormsAuthentication.FormsCookieName))
            continue;

        if(!Request.Cookies[i].Name.EndsWith("_SKA", System.StringComparison.OrdinalIgnoreCase))
            continue;

        if(i > 60)
            break;

        //System.Web.HttpContext.Current.Response.Write("<h1>"+Request.Cookies[i].Name+"</h1>");

        System.Web.HttpCookie c = new System.Web.HttpCookie( Request.Cookies[i].Name );
        //c.Expires = System.DateTime.Now.AddDays( -1 );
        c.Expires = new System.DateTime(1970, 1 ,1);
        c.Path = Request.ApplicationPath + "/Pages";
        c.Secure = false;
        c.HttpOnly = true;

        // http://stackoverflow.com/questions/5517273/httpcookiecollection-add-vs-httpcookiecollection-set-does-the-request-cookies
        //Response.Cookies[Request.Cookies[i].Name] = c;
        //Response.Cookies.Add(c);
        Response.Cookies.Set(c);
    }

    return "";
}


</script>

<%=ClearSessionKeepAliveCookiesToPreventHttp400HeaderTooLong()%>

    <form style="width:100%;height:100%" runat="server" ID="ReportViewerForm">

OTHER TIPS

I was having a lot of difficulty implementing different solutions to this problem because of our site's architecture - for whatever reason, my coworkers had originally decided to use iframes with links to the reports instead of a ReportViewer control, and I was loathe to try and change this so late in the development process due to a simple cookie issue.

Solutions I tried which did not work:

  1. Implementing Stefan's code-behind fix - The server code on my page could not access cookies being set in the embedded iframe document
  2. Changing cookies from the parent document in javascript - For understandable security reasons, I could not access cookies in the iframe from client-side code either
  3. Tried passing parameters into the report URL to tell it not to keep the session alive - Tried appending "&rs:KeepSessionAlive=False", which did not cause an error, but did not work
  4. *Toyed* with the idea of injecting javascript into the reports themselves - Considering this would involve changing some 50-odd reports and screwing up the exported/saved reports feature, this was not an option

Finally, after poking around the server, I realized that the Report Server "Pages" folder (C:\Program Files\Microsoft SQL Server\MSRS11.SQLEXPRESS\Reporting Services\ReportServer\Pages) contained a "ReportViewer.aspx" document.

And what do you know? It's just a simple ASP.NET page with a header where you can add your own javascript!?

So, here's what DID work for me:

I just added the client-side cookie-setting code I had found elsewhere below to delete all cookies on the ReportViewer page, and everything suddenly worked! Only one keep-alive cookie at a time!

<%@ Register TagPrefix="RS" Namespace="Microsoft.ReportingServices.WebServer" Assembly="ReportingServicesWebServer" %>
<%@ Page Language="C#" AutoEventWireup="true" Inherits="Microsoft.ReportingServices.WebServer.ReportViewerPage" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
 <head id="headID" runat="server">
  <title><%= GetPageTitle() %></title>
 </head>
 <body style="margin: 0px; overflow: auto">
  <form style="width:100%;height:100%" runat="server" ID="ReportViewerForm">
   <asp:ScriptManager ID="AjaxScriptManager" AsyncPostBackTimeout="0" runat="server" />
   <RS:ReportViewerHost ID="ReportViewerControl" runat="server" />
  </form>
  <script language="javascript" type="text/javascript">
      // Beginning of inserted cookies management code
function createCookie(name, value, days) {
    if (days) {
        var date = new Date();
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
	var expires = "; expires=" + date.toUTCString();
    }
    else var expires = "";

    document.cookie = name + "=" + value + expires;
}

function readCookie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') c = c.substring(1, c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
}

function eraseCookie(name) {
    createCookie(name, "", -1);
}

var getCookies = function () {
    var pairs = document.cookie.split(";");
    var cookies = {};
    for (var i = 0; i < pairs.length; i++) {
        var pair = pairs[i].split("=");
        cookies[pair[0]] = unescape(pair[1]);
    }
    return cookies;
}

var pairs = document.cookie.split(";");
var cookies = {};
for (var i = 0; i < pairs.length; i++) {
    var pair = pairs[i].split("=");
    cookies[pair[0]] = unescape(pair[1]);
}
var keys = [];
for (var key in cookies) {
    if (cookies.hasOwnProperty(key)) {
        keys.push(key);
    }
}
for (index = 0; index < keys.length; ++index) {
    eraseCookie(keys[index]);
}

      // End of inserted cookies management logic

      //Beginning of pre-existing code
Sys.WebForms.PageRequestManager.prototype._destroyTree = function(element) {
    var allnodes = element.getElementsByTagName('*'),
        length = allnodes.length;
    var nodes = new Array(length);
    for (var k = 0; k < length; k++) {
        nodes[k] = allnodes[k];
    }
    for (var j = 0, l = nodes.length; j < l; j++) {
        var node = nodes[j];
        if (node.nodeType === 1) {
            if (node.dispose && typeof (node.dispose) === "function") {
                node.dispose();
            }
            else if (node.control && typeof (node.control.dispose) === "function") {
                node.control.dispose();
            }
            var behaviors = node._behaviors;
            if (behaviors) {
                behaviors = Array.apply(null, behaviors);
                for (var k = behaviors.length - 1; k >= 0; k--) {
                    behaviors[k].dispose();
                }
            }
        }
    }
}
  </script>
 </body>
</html>

Please note that there was some pre-existing code in the page which I did not replace.

Hope this helps somebody else, as I struggled with this one for a while!

NOTE: Please note that, in my case, the Session Keep Alive (SKA) cookies were not HTTP-only, so I was able to access them from the client-side, albeit only the client-side within the Report Server itself. enter image description here

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top