3

I have two sites that I'm trying to implement single sign on for by setting up shared session state. I have followed the steps here:

  1. Configured the SQL server to support session state

  2. Set the session state property in both web configs, same in each application:

    <sessionState mode="SQLServer" allowCustomSqlDatabase="true" sqlConnectionString="Data Source=sqlserver;user id=user;password=password;Application Name=MySession" cookieless="false" />
    
  3. Added a machine key property:

    <machineKey validationKey="xxxx" decryptionKey="yyyy" validation="SHA1" decryption="AES" />
    
  4. Implemented the HttpModule to modify the domain session cookie to use the top level domain:

    using System;
    using System.Web;
    using System.Reflection;
    using System.Configuration;
    
    namespace CSASPNETShareSessionBetweenSubDomainsModule
    {
        /// <summary> 
        /// A HttpModule used for sharing the session between Applications in  
        /// sub domains. 
        /// </summary> 
        public class SharedSessionModule : IHttpModule
        {
            // Cache settings on memory. 
            protected static string applicationName = ConfigurationManager.AppSettings["ApplicationName"];
            protected static string rootDomain = ConfigurationManager.AppSettings["RootDomain"];
    
            #region IHttpModule Members 
            /// <summary> 
            /// Initializes a module and prepares it to handle requests. 
            /// </summary> 
            /// <param name="context"> 
            /// An System.Web.HttpApplication 
            /// that provides access to the methods, 
            /// properties, and events common to all application objects within  
            /// an ASP.NET application. 
            /// </param> 
            public void Init(HttpApplication context)
            {
                // This module requires both Application Name and Root Domain to work. 
                if (string.IsNullOrEmpty(applicationName) ||
                    string.IsNullOrEmpty(rootDomain))
                {
                    return;
                }
    
                // Change the Application Name in runtime. 
                FieldInfo runtimeInfo = typeof(HttpRuntime).GetField("_theRuntime",
                    BindingFlags.Static | BindingFlags.NonPublic);
                HttpRuntime theRuntime = (HttpRuntime)runtimeInfo.GetValue(null);
                FieldInfo appNameInfo = typeof(HttpRuntime).GetField("_appDomainAppId",
                    BindingFlags.Instance | BindingFlags.NonPublic);
    
                appNameInfo.SetValue(theRuntime, applicationName);
    
                // Subscribe Events. 
                context.PostRequestHandlerExecute += new EventHandler(context_PostRequestHandlerExecute);
            }
    
            /// <summary> 
            /// Disposes of the resources (other than memory) used by the module 
            /// that implements. 
            /// </summary> 
            public void Dispose()
            {
            }
            #endregion
    
            /// <summary> 
            /// Before sending response content to client, change the Cookie to Root Domain 
            /// and store current Session Id. 
            /// </summary> 
            /// <param name="sender"> 
            /// An instance of System.Web.HttpApplication that provides access to 
            /// the methods, properties, and events common to all application 
            /// objects within an ASP.NET application. 
            /// </param> 
            void context_PostRequestHandlerExecute(object sender, EventArgs e)
            {
                try
                {
                    HttpApplication context = (HttpApplication) sender;
    
                    // ASP.NET store a Session Id in cookie to specify current Session. 
                    HttpCookie cookie = context.Response.Cookies["ASP.NET_SessionId"];
    
                    if (context.Session != null &&
                        !string.IsNullOrEmpty(context.Session.SessionID))
                    {
                        // Need to store current Session Id during every request. 
                        cookie.Value = context.Session.SessionID;
    
                        // All Applications use one root domain to store this Cookie 
                        // So that it can be shared. 
                        if (rootDomain != "localhost")
                        {
                            cookie.Domain = rootDomain;
                        }
    
                        // All Virtual Applications and Folders share this Cookie too. 
                        cookie.Path = "/";
                    }
                }
                catch (Exception ex)
                {
                    return;
                }
            }
        }
    }
    

... and added it to the web config:

<modules runAllManagedModulesForAllRequests="true">
  <add name="SharedSessionModule" type="CSASPNETShareSessionBetweenSubDomainsModule.SharedSessionModule, CSASPNETShareSessionBetweenSubDomainsModule, Version=1.0.0.0, Culture=neutral"/>      
</modules>

Additionally, in site B, my UserService is pointing to site B, so they are sharing the same pool of users.

When I log into site A, I see these cookies: a session cookie where the domain is the top level domain, as well as another session cookie with the full domain and an empty value.

enter image description here

When I go to site B, I am not logged in, but I do see the matching session cookie from site A, as well as a second empty session cookie with the domain of site B:

enter image description here

I am able to log into site B, and I am now logged in on both sites. When I try to log out of site A, nothing happens; when I log out of site B, I get logged out of site B AND site A.

What is wrong with my session state? Is it because of that extra empty ASP.NET_SessionId cookie, and if so, how do I get rid of that? Is there a way I can check the session state in my code and log the user in if that session cookie exists, or force the session to use correct ASP.NET_SessionId cookie?

EDIT: I added the code to remove the duplicate cookies, but when I debug, my code is only seeing one cookie:

enter image description here

but when I check the cookies in Chrome inspector, there are two.

EDIT 2: So I resolved the duplicate cookie issue now, but SSO still isn't working. I opened up site A, logged in, opened up site B. Checked the cookies on both, and they both have one single ASP.NET_SessionId cookie with the same Value and Domain, but I'm still not logged into site B

2

I've seen this happen with the Forms Authentication cookie before. Multiple instances of the same cookie get added and the most specific domain wins. I ended up creating a method to remove all but the first instance of the cookie from the HttpContext.Current.Response.Cookies collection. I found the first cookie in the collection to be the correct one in my case but YMMV.

In your case, I'd recommend checking to see which cookie it is in the Response.Cookies collection (or perhaps adjusting it to find the one with the shortest Domain string and using that one).

public static void RemoveAllButFirstCookieByName(ref HttpCookieCollection httpCookieCollection, string cookieName)
{
    if (cookieName == null)
        throw new ArgumentNullException(nameof(cookieName));

        HttpCookie authCookie = null;
    for (var i = 0; i < httpCookieCollection.Count; i++)
    {
        HttpCookie httpCookie = httpCookieCollection[i];
        if (httpCookie != null && httpCookie.Name.Equals(cookieName))
        {
            authCookie = httpCookie;
            break;
        }
    }
    if (authCookie != null)
    {
        httpCookieCollection.Remove(cookieName);
        httpCookieCollection.Add(authCookie);
    }
}
  • So I added that, but here's the really weird thing-- when I debug my code, it's only showing ONE cookie, but when I view the cookies in the browser there are two. I'll add a screenshot in the original post. When I inspect httpCookieCollection, there is only one key (ASP.NET_SessionId) and the entryTable only has one value in it, which is the correct, altered session cookie. However, in the browser there is still the old cookie – Erica Stockwell-Alpert Nov 2 '18 at 14:06
  • @EricaStockwell-Alpert If you run a network traffic tracer like Fiddler, can you find the response on which that second cookie is set? – Dan Sinclair Nov 2 '18 at 14:16
  • It looks like the login page (i.e. homepage) return "Set-Cookie: ASP.NET_SessionID=xxxxxxxx; domain=topleveldomain"... and then there is a call to load a stylesheet on my site (mysite.com/styles/fonts/roboto-regular.woff), which returns a 404 status, which syas "Response sent 26 bytes of Cookie data: Set-Cookie: ASP.NET_SessionId=;path=/" – Erica Stockwell-Alpert Nov 2 '18 at 14:23
  • 1
    I added to .woff mime type and that seems to have fixed the issue! Which makes me think that the extra session cookie might be coming from the 404 page? – Erica Stockwell-Alpert Nov 2 '18 at 14:28
  • @EricaStockwell-Alpert Yep, it seems like whatever response gets sent when the 404 happens is setting a cookie and it's probably happening outside of the context of Sitecore, so your cookie removal code is not being executed. – Dan Sinclair Nov 2 '18 at 14:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.