Login reset/lost in asp.net core containers in kubernetes

Viewed 635

newbie to containers and coming back to asp.net in nearly a decade and i assumed this would be answered somewhere already but can't seem to locate it ! I have a simple asp.net app running on 3 nodes, the login is per node apparently and is lost when the request is served by a different node. i assumed there would be distributed session management tutorial but coming up short here, any pointers/how-to ?

FYI - in dev for now but for a public facing cloud solution azure/linode/... running 3 pods

The configuration/logging information is attached below

Redis is working on the localhost - not in a docker container

the behaviour is as follows

  • say user is logged into pod1, whenever request hits pod1, it shows logged in,
  • if the request hits pod2/pod3, it shows not logged in !!!

It looks like something else is required to make the session use redis !!!


public void ConfigureServices(IServiceCollection services)
{
    var cx = Configuration["RedisCache:ConnectionString"];
var redis = ConnectionMultiplexer.Connect(cx); 
 services.AddDataProtection().PersistKeysToStackExchangeRedis(redis, "DataProtectionKeys");

    services.AddSession(options =>
    {
        options.IdleTimeout = TimeSpan.FromMinutes(20);
    });
    services.AddControllersWithViews();
    services.AddRazorPages();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseMigrationsEndPoint();
    }
    else
    {
        app.UseDeveloperExceptionPage();
        //app.UseExceptionHandler("/Error");
        app.UseHsts();
    }
    if (env.IsDevelopment())
    {
        app.UseHttpsRedirection();
    }

    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    // Adds session middleware to pipeline
    app.UseSession();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
        endpoints.MapRazorPages();
    });
}

APPSETTINGS.JSON

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=host.docker.internal;Database=SAMPLE;Trusted_Connection=false;user id=XXX;password=XXX;MultipleActiveResultSets=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "RedisCache": {
    "ConnectionString": "host.docker.internal:6379,ssl=True,abortConnect=False"
  },
  "AllowedHosts": "*"
}

I have the following logging information In Index.cshtml.cs

public void OnGet()
{
    var str = $"({Environment.MachineName}) || {HttpContext.Session.Id} || {DateTime.UtcNow.ToString("hh:mm:ss")}: Current({User.Identity.Name})";
    _logger.LogWarning(str);

    string sessionKey = "testKey1";

    HttpContext.Session.SetString(sessionKey, str);
}
1 Answers

With the current code, the sessions are stored locally so each time the load balancer redirect to a new pod, you will lose any context of the previous request.

You have to enable distributed caching that will allow to share sessions across multiple instances of your server application. See IDistributedCache interface

You must also ensure that all application instances shared the same keys for Data Protection related workflows.

Here is a working sample with Redis as DistributedCache:

public void ConfigureServices(IServiceCollection services)
{
    // ensure that several instances of the same application can share their session
    services.AddDataProtection()
        .SetApplicationName("myapp_session")
        .PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect("redis:6379,password=cE3nNEXHmvGCwdq7jgcxxxxxxxxxx"),
            "DataProtection-Keys");

    services.AddControllersWithViews();
    
    // add distributed caching based on Redis
    services.AddStackExchangeRedisCache(action => {
        action.InstanceName = "redis";
        action.Configuration = "redis:6379,password=cE3nNEXHmvGCwdq7jgcxxxxxxxxxx";
    });

    services.AddSession(options => {
        options.Cookie.Name = "myapp_session";
        options.IdleTimeout = TimeSpan.FromMinutes(60 * 24);
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseSession();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

Here is the corresponding csproj:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="5.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="5.0.3" />
  </ItemGroup>
</Project>

Full sample available here: https://github.com/Query-Interface/SO-Answers/tree/master/dotNET/AspWithSessionOnMultipleNodes

Another option, is to enable SessionAffinity in your Kubernetes Service. It allows the LoadBalancer to root traffic from one client always to the same Pod. It can help you but it is not recommended since as soon as a Pod will be deleted (failing pod or as a result of scaling operation) the LoadBalancer will not be able to route your request to this specific Pod. It is explained in this question: https://stackoverflow.com/questions/56323438

Related