Issue with ASP.NET Core Session Cookies Not Being Set (Intermittent)
I’m using ASP.NET Core sessions, and for some reason, the middleware is not consistently sending the Set-Cookie
header for the .AspNetCore.Session
cookie. This issue becomes even stranger because sometimes, it works just fine, but other times, it doesn’t.
For example, on DevMachine1, it doesn’t work currently, but if I go to DevMachine2 and run the exact same repo with the same middleware configuration, it works as expected.
Problem Context:
Program.cs
:
builder.Services.AddHttpClient();
builder.Services.AddHttpContextAccessor();
builder.Services.AddControllersWithViews();
builder.Services.AddScoped<AutomateAPIService>();
builder.Services.AddScoped<AutomateDataService>();
// builder.Services.AddSingleton<PersistService>(); // For Future Persist Integration, removing to test sessions.
builder.Services.AddScoped<AutomateLoginService>();
builder.Services.AddTransient<JsonParserService>();
builder.Services.AddSingleton<IMongoClient, MongoClient>(sp => new MongoClient(connectionUri));
builder.Services.AddTransient<PersistService>();
builder.Services.AddTransient<SessionValidationService>();
builder.Services.AddScoped<SessionAuthorizationFilter>();
builder.Services.AddScoped<DeviceJsonParser>();
builder.Services.AddDistributedMemoryCache();
// MongoSessionStorage
builder.Services.AddSingleton(sp =>
{
var client = sp.GetRequiredService<IMongoClient>();
var database = client.GetDatabase("SessionDatabase");
return database.GetCollection<BsonDocument>("Sessions");
});
// Add Session
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(60);
options.Cookie.SecurePolicy = CookieSecurePolicy.None;
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.Cookie.SameSite = SameSiteMode.Lax;
});
builder.Services.AddSingleton<ISessionStore, MongoDbSessionStore>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseSession();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
As you may notice, sessions are being stored in a MongoDB database. This setup has worked before and, on some machines, still works intermittently. Rolling back to the In-Memory cache did not resolve the issue either.
Logs:
We log out of the session store and see the following:
Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware[3]
Failed to determine the https port for redirect.
info: WebAppApiTest.Services.MongoDbSessionStore[0]
Setting SessionKey, System.Byte[] for session
info: WebAppApiTest.Controllers.HomeController[0]
SessionKey value: SessionValue
info: WebAppApiTest.Services.MongoDbSessionStore[0]
Attempting to commit session ca2084a4-23c1-f13f-4bb2-661251e14d38
info: WebAppApiTest.Services.MongoDbSessionStore[0]
Session ca2084a4-23c1-f13f-4bb2-661251e14d38 committed successfully. Matched: 0, modified 0, Upserted: True
We can see session IDs, so we know the middleware is working to a degree, but no session cookie is being set, meaning our session store never has the correct or persistent session ID to refer to.
Controller Test for Forcing Session Interaction:
I tried forcing session interaction at my LoginController
to see if I could get ASP.NET to send the session cookie:
public IActionResult AutomateLogin()
{
HttpContext.Session.SetString("SessionKey", "SessionValue");
var sessionValue = HttpContext.Session.GetString("SessionKey");
_logger.LogInformation($"SessionKey value: {sessionValue}");
return View(new AutomateLoginModel());
}
Logs confirm that session interaction is happening:
info: WebAppApiTest.Services.MongoDbSessionStore[0]
Setting SessionKey, System.Byte[] for session
info: WebAppApiTest.Controllers.AutomateLoginController[0]
SessionKey value: SessionValue
info: WebAppApiTest.Services.MongoDbSessionStore[0]
Attempting to commit session 6207e6cb-6eeb-688e-0e37-c60e32f4a004
info: WebAppApiTest.Services.MongoDbSessionStore[0]
Session 6207e6cb-6eeb-688e-0e37-c60e32f4a004 committed successfully. Matched: 0, modified 0, Upserted: True
Still, no session cookie is being set.
Summary of Issue:
- The session cookie is not being set intermittently, across different machines and at different times.
- Session IDs are being generated and committed to the store (MongoDB or In-Memory, the issue persists).
- The middleware configuration is identical across working and non-working environments.
- I expect the session cookie to be set with each new session.
My Question:
What could cause ASP.NET Core sessions to fail intermittently when setting the session cookie (.AspNetCore.Session
)? Why would the session cookie be set on some machines but not on others, even with identical configurations?
I did verify this across several browsers and confirmed that the Set-Cookie header is not present. I did find out why:
So in the SessionMiddleware class we evaluate whether or not we should include the header here:
IMPORTANT UPDATE:
In SessionMiddleware.cs (An ASP.NET native class)
The Set-Cookie header is conditionally evaluated on whether or not the response is started. If the response is already started we can no longer use the Set-Cookie header.
However, after running through the debug for awhile, it looks like even though my response HasStarted value = false we do not set _shouldEstablishSession = true.
SessionMiddleware.cs
internal bool TryEstablishSession()
{
return (_shouldEstablishSession |= !_context.Response.HasStarted);
}
And when I set a break point at the return line it is not being hit at all, so even though my Response.HasStarted is false and therefore _shouldestablishSession should be true
private static Task OnStartingCallback(object state)
{
var establisher = (SessionEstablisher)state;
if (establisher._shouldEstablishSession)
{
establisher.SetCookie();
}
return Task.CompletedTask;
}
And therefore executing in this task, we have a false value for _shouldEstablishSession.
Not sure why it’s not evaluating, but this is for sure the issue for why the Set-Cookie header is not being included. Could be a middleware pipeline issue but not sure why it’s manifesting as an intermittent issue.
6
After reviewing the underlying class responsible for creating and sending session cookies in ASP.NET Core, I realized that the method TryEstablishSession
was not being invoked. This method is crucial for setting session cookies and should be passed as a delegate when creating a session.
Here’s how the Create
method in the custom session store should look:
public ISession Create(string sessionKey, TimeSpan idleTimeout, TimeSpan ioTimeout, Func<bool> tryEstablishSession, bool isNewSession)
{
return new MongoDbSession(sessionKey, _sessionCollection, idleTimeout, _logger, tryEstablishSession);
}
Once the tryEstablishSession
delegate was passed to session operations like LoadAsync
and CommitAsync
, the session cookie started being set properly. The breakpoint in the SessionMiddleware
class was finally being hit, and the session cookie was included in the response.
The Fix:
The tryEstablishSession
delegate should be passed when creating a session and used throughout the session lifecycle. This ensures that the underlying middleware can invoke it, allowing session cookies to be set.
While the issue appeared intermittent at first, this solution fixed the problem, and the session cookie is now consistently set.