[Authenticate]'d endpoint returns null from SessionAs and GetSessionTimeToLive

We’re trying to return to clients essentially an expiration / TTL value of the session, so we have an endpoint for this which grabs a couple of things:

[Authenticate]
public async Task<CustomUserSession> Get(GetTtl dto) {

  var ttl = await Request.GetSessionTimeToLiveAsync();
//...
  var userSession = SessionAs<CustomAuthUserSession>();
//... do things
}

But sometimes ttl / userSession are null, generating 500 errors. Shouldn’t the [Authenticate] attribute block against entering into this method if the session is expired / non-existent?

If you’re using IdentityAuth you should be using [ValidateIsAuthenticated] on the Request DTO (e.g. GetTtl) instead which is also the preferred method to annotate an API requires Authentication for all ServiceStack Hosts.

We are using SS authentication, is that the same?

I’d recommend it for all ServiceStack Apps, as it’s declarative, decoupled from a specific implementation and lets client Applications know which APIs are authenticated.

Action Filters (i.e. Request Filters on method implementations) are rare, it executes the same implementation but it differs on where it’s executed in the Request Pipeline. I’d recommend using [ValidateIsAuthenticated] for APIs requiring Auth instead.

Can you try to switch to [ValidateIsAuthenticated] on GetTtl DTO to see if it makes a difference? if it doesn’t please provide the full StackTrace. Also what Memory Cache implementation are you using?

Making the updates now - we are using Valkey / Redis in AWS in the cloud, with SQL localhost.

Is this a rare occurrence? If you’re using a distributed caching provider than a race condition is more possible where the session exists when the Request is validated but was removed before rechecking the session TTL.

I’ve updated to use the [ValidateIsAuthenticated] attribute.

It happens more than I’d think:

Stack trace:

System.InvalidOperationException: Nullable object must have a value.
   at System.Nullable`1.get_Value()
   at LENSSX.ServiceInterface.UserService.Post(UpdateCurrentUserSession req) in /app/LENSSX.ServiceInterface/UserService.cs:line 221
   at ServiceStack.Host.ServiceRunner`1.ExecuteAsync(IRequest req, Object instance, TRequest requestDto) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/Host/ServiceRunner.cs:line 131

The DTO names were different in my original post, this is the actual DTO. I’ve put [ValidateIsAuthenticated] on it.
Code:

public CustomAuthUserSession Post(UpdateCurrentUserSession req)
        {
            var l = LogManager.GetLogger(typeof(UserService));
            l.Debug("UpdateCurrentUserSession: Start");
            var sessionService = TryResolve<SessionService>();
            
            var currentSession = SessionAs<CustomAuthUserSession>();
            l.DebugFormat("UpdateCurrentUserSession: Current Session = {@currentSession}", currentSession);
            
            var newSession = sessionService.PopulateSession(currentSession.UserId) as CustomAuthUserSession;
            
            l.DebugFormat("UpdateCurrentUserSession: New Session = {@newSession}", newSession);

            var ttl = Request.GetSessionTimeToLive();
            
            l.DebugFormat("UpdateCurrentUserSession: TTL = {@ttl}", ttl);
            
            newSession.Expiration = (DateTime.Now + ttl.Value).ToUnixTime(); // failing because ttl is null - line 221
            
            Request.SaveSessionAsync(newSession);
            
            l.DebugFormat("UpdateCurrentUserSession: End");
            return newSession;
        }

This is the code to get the Session Cache Key and call the Registered Cache Client to get the time remaining before expiration:

var sessionKey = SessionFeature.GetSessionKey(sessionId);
return cache.GetTimeToLive(sessionKey);

If you’re using Redis GetTimeToLive() would return null if the Session Key does not exist in Redis.

I can’t tell you why it doesn’t exist, it’s only going to be populated for Server Sessions, i.e. it wouldn’t exist if this Request was authenticated with an IAuthWithRequest provider.

Hopefully information about the Session will provide some information on the type of Session that’s authenticated.

We no longer use any JWTs / stateless authentication. We recently moved everything to sessions as we needed to support TTLs for our requirements.