Sliding sessions broken in 4.0.60+

I just recently updated from 4.0.56 to 4.5.6 only to find that it appears that my sliding session code is broken. So I then downgraded to 4.0.56 to confirm my code still worked and then updated to 4.0.60 and found it doesn’t work in 4.0.60.

I am using a CustomCredentialsAuthProvider. After I get logged in on subsequent page requests I then updated the session in cache. I am using Redis for the cache. What I’ve found is that after I update the session in cache a subsquent page refresh then returns a new session and the user is no longer authenticated.

See the code below:

Cache.Set(SessionFeature.GetSessionKey(), UserSession, ts);

I’ve also found that the issue only occurs in using Redis (if I switch to an in-memory cache then it is fine). Do you have any suggestions for what might be the issue?

Thanks,
Gerry

Can you provide more detailed info what’s wrong with code you provided? What are the values of SessionFeature.GetSessionKey(), UserSession and ts? Does redis contains the key SessionFeature.GetSessionKey() and what the TTL of this key?

You can check it by connecting to redis using redis-cli and typing GET key and TTL key where key is the value of SessionFeature.GetSessionKey())

Cache.Set(SessionFeature.GetSessionKey(), UserSession, ts);

SessionFeature.GetSessionKey returns something like this - “urn:iauthsession:VaVgdZfabfpPIQ…”
ts is just
var ts = new TimeSpan(12, 0, 0); // test 12 hour expiration
and UserSession is my custom session object that is subclassed from AuthUserSession

And so I can see the session in REDIS using the above value for KEY. And after saving it’s still there in REDIS and shows authenticated. Lastly TTL key shows a large value (e.g. 42443 for example)

However, the next time I refresh the webpage the session that gets returned is a new session and is not authenticated. I’m using the ASP.NET session helper stuff that you’ve posted.

e.g. return (TUserSession)(userSession ?? (userSession = SessionFeature.GetOrCreateSession(Cache)));

It would appear that SessionFeature.GetOrCreateSession is not returning the session even though it exists in REDIS. I also noted that the ss_id cookie matches the value in GetSessionKey.

One thing I do notice in REDIS on the session object that is stored before and after is that the values stored by ServiceStack vs the value I store doesn’t contain an __type value. Beyond that everything else seems to be the same.

If you’re going to save a Session you should be using the formal API:

IRequest.SaveSession(session, expiry)

Or if you’re going to use Redis directly you can modify the expiry of the session with:

var sessionKey = SessionFeature.GetSessionKey(session.Id);
redis.ExpireEntryIn(sessionKey, expireIn);

Which just changes the expiry time of the session key without needing to re-save the session.

Hi Demis–I have made the change you mentioned and it now works.

IRequest.SaveSession(session, expiry)

However, the approach I have been using for a number of years is documented as correct. See here:

// saving User Session
CacheClient.CacheSet(sessionKey , userSession);

And something changed between 4.0.56 to 4.0.60.

Thanks for the suggestion. I’d suggest noting the change in the documents as well.

Gerry

2 Likes

I am encountering the same issue now, using the 5.4.1 libraries, but have narrowed the issue down to a combination of SlidingSession code (from the sample here: SS Sliding Sessions) and the registration of OrmliteCacheClient.

Specifically, I have a big end-to-end test case set up where I seed data, inject a new user, log in as that user, then make subsequent requests using the same JsonServiceClient (all in C#, not via browser API calls). The code seeds my db, then logs in okay, makes one authed request successfully, which steps into the SlidingSession.Execute method and the documentation example code line:

 if (session != null) req.SaveSession(session, this.Expiry);

, if I set a breakpoint I can see the session expiry value change to 8 hours in the future (as requested), but an immediate call to another authed endpoint will fail with a 401.

I checked my AppHost config, comment out the line:

container.RegisterAs<OrmLiteCacheClient, ICacheClient>(); // cache to db

and the end to end test runs successfully, with the SlidingSession attribute on my secured endpoints. If I renable the OrmLiteCacheClient, problem re-occurs.Alternatively, I can leave the ormlitecacheclient enabled, but am forced to disable/comment out the SlidingSession attribute. Something about having both activated causes issues.

Let me know if you need a repro and I can put something together tomorrow.

Before I go to bed, I think I got it:

after auth, and just before the first SlidingSession.Execute() call runs the req.SaveSession() command, my db cache entry looks like this:

{__type:"api.services.infrastructure.CustomUserSession, api.services",activeStaffId:00000000000000000000000000000000,id:xveZlAopBDMEFufOXjSV,userAuthId:92,userAuthName:host,userName:host,firstName:HostCompany,lastName:Administrator,email:Host.Company@HostCompany.com,fullName:HostCompany Administrator,createdAt:"2019-06-24T20:44:34Z",lastModified:"2019-06-24T20:44:34Z",roles:[],permissions:[],isAuthenticated:True,fromToken:False,tag:0,authProvider:credentials,providerOAuthAccess:[]}

but AFTER the record saves in the db, it looks like this:

{__type:"api.services.infrastructure.CustomUserSession, api.services",activeStaffId:00000000000000000000000000000000,id:xveZlAopBDMEFufOXjSV,userAuthId:92,userAuthName:host,userName:host,firstName:HostCompany,lastName:Administrator,email:Host.Company@Host

Note that I’ve lost all the IsAuth etc properties from the serialised session. I use the out-of-the-box CredentialsAuthProvider but I DO have a CustomUserSession, which currently actually doesn’t add any functionality.

On closer inspection, it seems that the issue is possibly related somehow to an OrmLiteConfig setting that I use, whereby:

// Configure string handling defaults for ormlite
		var converter = OrmLiteConfig.DialectProvider.GetStringConverter();
		converter.UseUnicode = true; // We are multicultural
		// Override default string length to use 255 characters instead of default VARCHAR(MAX) setting
		converter.StringLength = 255;

seems to trunctate my saved session data at 255 characters, effectively logging me out. Not sure if thats by design, or a bug in the cache? I wasn’t expecting the converter.StringLength to prevent saving longer strings, I only meant to save myself from having to set all strings in the DB creation phase from being nvarchar(max) by default.

Why are you changing the size of VARCHAR()?

What version of ServiceStack are you using?

I changed that setting so that when the DB table creation extension methods generate “create table” scripts, instead of having to put StringLength attributes on every string property in all my DB table classes so that they weren’t all created as varchar(max), I could set them by default to 255 then only occasionally have to explicitly declare those few DB columns that I want to be longer.

Is that misusing the concept of the converter.stringlength feature? If so, how/what should it be used for, and is it recommended that I DO explicitly set each string column length using a property attribute?

Using newest libs from MyGet.

Is this SQL Server? By default it creates normal string properties as VARCHAR(8000) and VARCHAR(MAX) for complex properties stored as blobs, e.g:

public class Customer
{
    [AutoIncrement] 
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    [Index(Unique = true)] 
    public string Email { get; set; }
    public Dictionary<PhoneType, string> PhoneNumbers { get; set; }
    public Dictionary<AddressType, Address> Addresses { get; set; }
    public DateTime CreatedAt { get; set; }
}

CREATE Statement

CREATE TABLE "Customer" 
(
  "Id" INTEGER PRIMARY KEY IDENTITY(1,1), 
  "FirstName" VARCHAR(8000) NULL, 
  "LastName" VARCHAR(8000) NULL, 
  "Email" VARCHAR(8000) NULL, 
  "PhoneNumbers" VARCHAR(MAX) NULL, 
  "Addresses" VARCHAR(MAX) NULL, 
  "CreatedAt" DATETIME NOT NULL 
); 

Changing the string converter definition is a global setting that will affect how other features with built-in table types are stored which would assume the default size, i.e. upper VARCHAR size per RDBMS. However the CacheEntry does use MaxText for its length which will be unaffected by the reduced limit.

There was an issue with updating the string param size up UpdateOnly() APIs when it exceeds the configured string length which should now be resolved from this commit. This change is available from v5.5.1 that’s now available on MyGet.

1 Like

Sorry for the delay in responding, I have been installing a new PC, but your latest change resolves the issue, thanks!

1 Like