12

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...

DatumPoint
  • 419
  • 4
  • 21
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442

3 Answers3

8

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">
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
  • Closing the browser did not work for me with chrome. I had to manually delete the cookies. I will try your server side solution soon. Thanks! – kravits88 Feb 27 '14 at 06:15
  • This didn't work for me until I changed `c.Path = Request.ApplicationPath + "/Pages";` to `c.Path = Request.Cookies[i].Path;` – masty Mar 23 '14 at 22:41
  • @masty: Funny, Request.Cookies[i].Path does not work for me. Neither does c.Path = Request.Cookies[i].Path + "/Pages"; Have you installed ServicePack 1 + latest cumulative updates ? – Stefan Steiger Mar 26 '14 at 14:25
  • I've verified and the issue is really fixed in SQL Server 2012 SP1 CU7. – Isantipov Oct 03 '14 at 13:24
  • I've added a question on the linked Microsoft Connect issue, but I'm wondering if anyone knows whether this issue exists in SQL Server 2014, and if so, what update or service pack fixes it. – Paul Karlin Dec 15 '14 at 20:20
  • 1
    @Paul Karlin: Yes, it also exists in SQL-Server 2014, and it isn't fixed. There is the 1st service pack out, but our admin hasn't installed it, yet - so I can't say if the service pack fixes this issue. – Stefan Steiger Aug 03 '15 at 10:06
  • I tried to update the ReportViewer.aspx page with the code that u mentioned above but no luck. – Vivek Patel Mar 08 '18 at 19:24
  • @StefanSteiger I am still getting that error even after making changes as you suggested. – Vivek Patel Mar 09 '18 at 20:53
6

You can set KeepSessionAlive to false on the ReportViewer Control http://msdn.microsoft.com/en-us/library/microsoft.reporting.webforms.reportviewer.keepsessionalive(v=vs.100).aspx

graham mendick
  • 1,839
  • 1
  • 17
  • 15
  • Nice find. I'll try that. – Stefan Steiger May 18 '14 at 19:15
  • FYI, if you are using the "standard" ReportViewer, this setting can be changed in Reporting Services\ReportServer\Pages\ReportViewer.aspx by adding `KeepSessionAlive="false"` to the RS:ReportViewerHost tag. – Bob Albright Dec 15 '14 at 21:08
  • @graham mendick: On the other hand, if you do that, you'll get a session expired error message if you click on export after 5 to 10 minutes of inactivity. – Stefan Steiger Sep 29 '15 at 11:48
4

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

Community
  • 1
  • 1
  • 1
    You might use a newer SQL-server version. I had to use HTTP-only cookies, a brand of cookies you can't set with JavaScript. I actually tried this first, and it failed. They might have changed their implementation.My code applies to SQL-Server 2012 ReportingServices, Version 11.0.5343.0. – Stefan Steiger Feb 14 '16 at 14:48
  • Yes, I only resorted to this solution because the SKA cookies appeared not to be HTTP-only. I have a custom authentication cookie I'm passing from the hosting site to SSRS, which is HTTP-only, so I was surprised to see that the keep-alives were different. I'm also using SSRS 2012, version 11.0.2100.60. Not sure if the difference is due to the version or a different configuration. – DicreetAndDiscrete Feb 15 '16 at 16:33
  • Which page you make these changes? ReportViewer.aspx that lives under C:\Program Files\Microsoft SQL Server\MSRS13.MSSQLSERVER\Reporting Services\ReportServer\Pages for example? – Vivek Patel Mar 08 '18 at 19:00
  • @Vivek Patel : Yes, exactly that, unless you have yet another named instance of SSRS. – Stefan Steiger Mar 08 '18 at 20:31