Reputation: 478
I have configured an application to use sessions but I have a concern and I am not sure what else I might need to do.
When the user logs in, I set a Session variable and that works fine. If I choose to 'Remember Me' at login, close the browser and then reopen the website, it remembers the logged in user, but not the other session variable that I set.
The configuration and supporting code is as follows:
Startup.cs
//Configure Services
...
services.AddHttpContextAccessor();
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(60);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
...
//Configure()
app.UseSession();
I set the session at login like this:
var result = await _signInManager.PasswordSignInAsync(Input.UserName, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
HttpContext.Session.SetInt32("SessionVariable", 20);
return LocalRedirect(returnUrl);
}
As stated before, this works fine. If I close the browser and reopen the app, the value is no longer available in the application.
Any guidance is appreciated.
Upvotes: 1
Views: 2045
Reputation: 63327
Firstly I don't recommend what you're trying to do. The session cookie has its standard behavior (as by design) so you should follow it (I'm not so sure about the detailed reason because I'm not very good at security aspect).
I believe that you can find some alternative solution such as using local storage on the client or use persistent cookie explicitly.
However because it's possible, I would like to show the way you can achieve it. As long as you explicitly set the expiration time for it, it should still be OK (so instead of being cleared after the browsers closing, it will be cleared on a specific time).
Session has 2 parts to be stored:
What you configure (e.g: IdleTimeout
) is just for session data. The session key is set as a response cookie using a separate CookieOptions
. This options is built from the SessionOptions.Cookie
(a CookieBuilder
) which is initialized with a default implementation in which the CookieOptions.Expires
will be left as default(DateTimeOffset)
.
So to override it, you can provide your own implementation of CookieBuilder
, like this:
public class ConfigurableExpirationCookieBuilder : CookieBuilder
{
readonly CookieBuilder _defaultSessionCookieBuilder;
public ConfigurableExpirationCookieBuilder(CookieBuilder defaultSessionCookieBuilder)
{
_defaultSessionCookieBuilder = defaultSessionCookieBuilder;
}
public override string Domain { get => _defaultSessionCookieBuilder.Domain; set => _defaultSessionCookieBuilder.Domain = value; }
public override bool HttpOnly { get => _defaultSessionCookieBuilder.HttpOnly; set => _defaultSessionCookieBuilder.HttpOnly = value; }
public override bool IsEssential { get => _defaultSessionCookieBuilder.IsEssential; set => _defaultSessionCookieBuilder.IsEssential = value; }
public override TimeSpan? MaxAge { get => _defaultSessionCookieBuilder.MaxAge; set => _defaultSessionCookieBuilder.MaxAge = value; }
public override string Name { get => _defaultSessionCookieBuilder.Name; set => _defaultSessionCookieBuilder.Name = value; }
public override SameSiteMode SameSite { get => _defaultSessionCookieBuilder.SameSite; set => _defaultSessionCookieBuilder.SameSite = value; }
public override string Path { get => _defaultSessionCookieBuilder.Path; set => _defaultSessionCookieBuilder.Path = value; }
public override CookieSecurePolicy SecurePolicy { get => _defaultSessionCookieBuilder.SecurePolicy; set => _defaultSessionCookieBuilder.SecurePolicy = value; }
}
The default cookie builder is private and init all the default (standard) values in its constructor. So we override all the properties to keep those default values in the custom bookie builder. Of course the Expiration
is not overridden, it can be set by your code freely. The default cookie builder lets it as null
and protect against changing it like this:
public override TimeSpan? Expiration {
get => null;
set => throw new InvalidOperationException(nameof(Expiration) + " cannot be set for the cookie defined by " + nameof(SessionOptions));
}
Without Expiration
being set, the built cookie will be treated as session cookie with standard behaviors.
Now you can configure that Cookie
along with your IdleTimeout
to achieve your desired behavior:
services.AddSession(options =>
{
var sessionTimeout = TimeSpan.FromMinutes(60);
options.Cookie = new ConfigurableExpirationCookieBuilder(options.Cookie);
//for session key
//NOTE: this is not a sliding timeout like set by IdleTimeout
//the actual expire time will be this added with DateTimeOffset.Now
//at the time the session is established.
options.Cookie.Expiration = sessionTimeout;
//for session data
options.IdleTimeout = sessionTimeout;
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
Finally, I would not recommend this, so please don't try to leave some comments like: if it's not recommended, just don't provide any solutions. I love technical aspects more than trying to follow rules & standards. When it's possible, we can show how it's possible instead of trying to hide it. And actually in some real scenarios, some things cannot always be kept sticking to some strict rule.
Upvotes: 2