Session and SSO issues running Kentico 13 .NET Core on a load balanced environment

Dean Lynn asked on May 22, 2023 09:55

I am running a Kentico v13.0.52 solution with a .NET Core website.

A number of configuration elements may contribute to the issues I am seeing with sessions and SSO when operating on a load balanced environment.

Note: Both sessions and SSO work as expected with the following configuration on local (a single server environment). It is when deployed to a load balanced environment that the issues arise.

I have configured distributed SQL server caching:

services.AddDistributedSqlServerCache(options =>
{
   options.ConnectionString = Configuration.GetConnectionString("SessionConnectionString");
   options.SchemaName = "dbo";
   options.TableName = "Cache";
});   

Kentico sets session options internally; following Kentico guidance I am configuring settings of session via PostConfigure:

services.PostConfigure<SessionOptions>(sessionOptions =>
{
   sessionOptions.IdleTimeout = TimeSpan.FromMinutes(20);
   sessionOptions.Cookie.IsEssential = false;
   sessionOptions.Cookie.Name = ".ClientName.Session";
   sessionOptions.Cookie.HttpOnly = true;
   sessionOptions.Cookie.SameSite = SameSiteMode.None;
});

The session cookie is set to non-essential via SessionOptions, however, it remains configured as essential via Kentico's CookieHelper.

The management of the session cookie has been moved alongside all other Kentico cookie registrations:

[assembly: RegisterModule(typeof(ClientNameCookieRegistrationModule))]

public class ClientNameCookieRegistrationModule : Module
{
    public ClientNameCookieRegistrationModule() : base("ClientNameCookieRegistrationModule")
    {
    }

    protected override void OnInit()
    {
        base.OnInit();

        ... 
        CookieHelper.RegisterCookie(".ClientName.Session", CookieLevel.Essential);        
        ...
    }
}

.NET Core is configured to avoid checking of cookie consent, as Kentico will be managing cookie levels:

services.Configure<CookiePolicyOptions>(options =>
{
   options.CheckConsentNeeded = context => false;
   options.MinimumSameSitePolicy = SameSiteMode.None;
   options.Secure = CookieSecurePolicy.Always;
});

Data protection is set to use SQL server to store keys using:

services.AddDbContext<DataProtectionContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DataProtectionConnectionString")));  

services.AddDataProtection().PersistKeysToDbContext<DataProtectionContext>().

I also have SSO implemented for Google and Facebook:

services.AddAuthentication()
   .AddFacebook(facebookOptions =>
   {
      facebookOptions.AppId = Configuration.GetValue<string>("ExternalAuthentication:FacebookAppId");
      facebookOptions.AppSecret = Configuration.GetValue<string>("ExternalAuthentication:FacebookAppSecret");
   })
   .AddGoogle(googleOptions =>
   {
      googleOptions.ClientId = Configuration.GetValue<string>("ExternalAuthentication:GoogleClientId");
      googleOptions.ClientSecret = Configuration.GetValue<string>("ExternalAuthentication:GoogleClientSecret");
});

My deployed application is running within a load balanced environment, without sticky sessions enabled.

I can see the data protection table containing key entries and the cache table containing cache entries, meaning the servers are able to connect and work with the tables.

However, there are 2 problems which I believe are linked to either the distributed cache (session) or the use of DPAPI keys (or both!).

The first is that even though the servers can both access the cache table, they do not appear to share its entries. Both servers create unique entries for the same user as the website is browsed, instead of one server creating the entry and the other accessing that shared entry.

I am testing this using basic read/writes to session via:

HttpContext.Session.GetString()
HttpContext.Session.SetString()

The second problem is related to the implementation of SSO via Google and Facebook. As the user moves through the login process, they see a variety of issues related to moving between servers midway through the process.

I believe the second problem with SSO is potentially related to the first problem (both servers working with unique session entries).

Based on the information provided, can anyone see any noticeable errors in the configuration?

Can anyone provide guidance on configuring SSO with Kentico Xperience 13 within a load balanced environment, using SQL Server as the database?

Update 22 May 2023 09:18:

In gathering this information I have realised that the session cookie configuration could be an area that may be causing conflicts within Kentico and its cookie management (?). However, on a single server environment (local) there are no issues with neither session nor SSO suggesting the cookie management (in relation to maintaining cookies between requests) should be working as expected.

Update 22 May 2023 11:24:

I am currently investigating the possibility of middleware ordering in Startup.cs having a negative impact on the handling of sessions. app.UseKentico() internally calls app.UseSession() unless an appsettings.json key called CMSAutoAddSessionMiddleware is set to false. If set to false, app.UseSession() can be manually positioned in the middleware order.

Update 22 May 2023 17:21:

Further investigation has highlighted a missing setting from my data protection configuration; .NET will create an application discriminator based on the web root by default. On a load balanced environment, if the web roots are different (for me they include a server identifier e.g. CD1, CD2) then the discriminator will be different, documentation suggests that this will cause isolation between the two applications.

Further reading: https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-6.0#data-protection-and-app-isolation

What I think I need is the SetApplicationName line added below, but this is yet to be tested (waiting on exclusive access to our load balanced test environment):

services.AddDataProtection()
   .SetApplicationName("ClientName")
   .PersistKeysToDbContext<DataProtectionContext>();

Thanks!

Correct Answer

Dean Lynn answered on May 25, 2023 10:49

I have been able to resolve this issue.

My updates to the question above outline the changes made, but I believe the most important addition was the inclusion of the application name here:

services.AddDataProtection()
   .SetApplicationName("ClientName")
   .PersistKeysToDbContext<DataProtectionContext>();

It is worth noting that the inclusion of CMSAutoAddSessionMiddleware in my appsettings.json file also allowed me to improve the position of UseSession() in the middleware ordering, a worthwhile configuration change to gain more control over the Startup.cs file.

0 votesVote for this answer Unmark Correct answer

   Please, sign in to be able to submit a new answer.