Session not filled with logged On user

I am using Servicestack 5.13.3 and am having a strange issue where base.GetSession() does not fill the UserAuthId and show the session as Unauthenticated. I have checked the ss-id and ss-pid and it is set when I log in. I can also see it being passed back to my service, but inspecting the session shows it as not being authenticated. I also looked at the session Id to make sure that the session exist in the redis cache and it does and show in the cache as authenticated, although the roles are not filled.

{
“__type”: “ServiceStack.AuthUserSession, ServiceStack”,
“id”: “v0Ir0YpFCITBim4yk2AN”,
“user_auth_id”: “1”,
“user_auth_name”: “andy.fensham@scadsoftware.com”,
“display_name”: “Admin User”,
“email”: “andy.fensham@scadsoftware.com”,
“created_at”: “/Date(-62135596800000-0000)/”,
“last_modified”: “/Date(1638380490522)/”,
“roles”: ,
“permissions”: ,
“is_authenticated”: true,
“from_token”: false,
“profile_url”: “data:image/svg+xml,%3Csvg width=‘100’ height=‘100’ viewBox=‘0 0 100 100’ xmlns=‘SVG namespace’%3E %3Cstyle%3E .path%7B%7D %3C/style%3E %3Cg id=‘male-svg’%3E%3Cpath fill=‘%23556080’ d=‘M1 92.84V84.14C1 84.14 2.38 78.81 8.81 77.16C8.81 77.16 19.16 73.37 27.26 69.85C31.46 68.02 32.36 66.93 36.59 65.06C36.59 65.06 37.03 62.9 36.87 61.6H40.18C40.18 61.6 40.93 62.05 40.18 56.94C40.18 56.94 35.63 55.78 35.45 47.66C35.45 47.66 32.41 48.68 32.22 43.76C32.1 40.42 29.52 37.52 33.23 35.12L31.35 30.02C31.35 30.02 28.08 9.51 38.95 12.54C34.36 7.06 64.93 1.59 66.91 18.96C66.91 18.96 68.33 28.35 66.91 34.77C66.91 34.77 71.38 34.25 68.39 42.84C68.39 42.84 66.75 49.01 64.23 47.62C64.23 47.62 64.65 55.43 60.68 56.76C60.68 56.76 60.96 60.92 60.96 61.2L64.74 61.76C64.74 61.76 64.17 65.16 64.84 65.54C64.84 65.54 69.32 68.61 74.66 69.98C84.96 72.62 97.96 77.16 97.96 81.13C97.96 81.13 99 86.42 99 92.85L1 92.84Z’/%3E%3C/g%3E%3C/svg%3E”,
“tag”: 0,
“auth_provider”: “credentials”,
“provider_o_auth_access”:
}

However in session = base.GetSession(), it shows isAuthenticated as false and UserAuthId is not filled.

I have a standard Configure.Auth.cs I pulled in with mix that looks like.

using Microsoft.Extensions.DependencyInjection;
using ServiceStack;
using ServiceStack.Auth;
using ServiceStack.FluentValidation;

namespace digitaluapi
{
    // Add any additional metadata properties you want to store in the Users Typed Session
    //public class CustomUserSession : AuthUserSession
    //{
    //    public int TenantsId { get; set; }
    //}

    // Custom Validator to add custom validators to built-in /register Service requiring DisplayName and ConfirmPassword
    public class CustomRegistrationValidator : RegistrationValidator
    {
        public CustomRegistrationValidator()
        {
            RuleSet(ApplyTo.Post, () =>
            {
                RuleFor(x => x.DisplayName).NotEmpty();
                RuleFor(x => x.ConfirmPassword).NotEmpty();
            });
        }
    }

    public class ConfigureAuth : IConfigureAppHost, IConfigureServices
    {
        public void Configure(IServiceCollection services)
        {
            //services.AddSingleton<ICacheClient>(new MemoryCacheClient()); //Store User Sessions in Memory Cache (default)
        }

        public void Configure(IAppHost appHost)
        {
            //var AppSettings = appHost.AppSettings;
            //appHost.Plugins.Add(new AuthFeature(() => new CustomUserSession(),
            //    new IAuthProvider[] {
            //        new CredentialsAuthProvider(AppSettings),     /* Sign In with Username / Password credentials */
            //        new FacebookAuthProvider(AppSettings),        /* Create App https://developers.facebook.com/apps */
            //        new GoogleAuthProvider(AppSettings),          /* Create App https://console.developers.google.com/apis/credentials */
            //        new MicrosoftGraphAuthProvider(AppSettings),  /* Create App https://apps.dev.microsoft.com */
            //    }));

            var AppSettings = appHost.AppSettings;
            appHost.Plugins.Add(new AuthFeature(
                new IAuthProvider[] {
                    new CredentialsAuthProvider(AppSettings),     /* Sign In with Username / Password credentials */
                    new FacebookAuthProvider(AppSettings),        /* Create App https://developers.facebook.com/apps */
                    new GoogleAuthProvider(AppSettings),          /* Create App https://console.developers.google.com/apis/credentials */
                    new MicrosoftGraphAuthProvider(AppSettings),  /* Create App https://apps.dev.microsoft.com */
                }));

            appHost.Plugins.Add(new RegistrationFeature()); //Enable /register Service

            //override the default registration validation with your own custom implementation
            appHost.RegisterAs<CustomRegistrationValidator, IValidator<Register>>();
        }
    }
}

And a Configure.AuthRepository.cs that looks like

using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using ServiceStack;
using ServiceStack.Web;
using ServiceStack.Data;
using ServiceStack.Auth;
using ServiceStack.Configuration;
using ServiceStack.OrmLite;
using digitaluapi.ServiceModel.Types;

namespace digitaluapi
{
    // Custom User Table with extended Metadata properties
   

    public class AppUserAuthEvents : AuthEvents
    {
        public override void OnAuthenticated(IRequest req, IAuthSession session, IServiceBase authService, 
            IAuthTokens tokens, Dictionary<string, string> authInfo)
        {
            var authRepo = HostContext.AppHost.GetAuthRepository(req);
            using (authRepo as IDisposable)
            {
                var userAuth = (AppUser)authRepo.GetUserAuth(session.UserAuthId);
                userAuth.ProfileUrl = session.GetProfileUrl();
                userAuth.LastLoginIp = req.UserHostAddress;
                userAuth.LastLoginDate = DateTime.UtcNow;
                authRepo.SaveUserAuth(userAuth);
            }
        }
    }

    public class ConfigureAuthRepository : IConfigureAppHost, IConfigureServices, IPreInitPlugin
    {
        public void Configure(IServiceCollection services)
        {
            services.AddSingleton<IAuthRepository>(c =>
                new OrmLiteAuthRepository<AppUser, UserAuthDetails>(c.Resolve<IDbConnectionFactory>()) {
                    UseDistinctRoleTables = true
                });            
        }

        public void Configure(IAppHost appHost)
        {
            var authRepo = appHost.Resolve<IAuthRepository>();
            authRepo.InitSchema();

            CreateUser(authRepo, "andy.fensham@scadsoftware.com", "Admin User", "xxxxxxxxxxx", roles: new[] { RoleNames.Admin });
            CreateUser(authRepo, "alwyn.pelzer@scadsoftware.com", "Admin User", "xxxxxxxxxxx", roles: new[] { RoleNames.Admin });
            CreateUser(authRepo, "jmaguranye@gmail.com", "Admin User", "xxxxxxxxxxxx", roles: new[] { RoleNames.Admin });
            CreateUser(authRepo, "jzheke@gmail.com", "Admin User", "xxxxxxxxxxx", roles: new[] { RoleNames.Admin });
            CreateUser(authRepo, "dawie.martins@scadsoftware.com", "Admin User", "xxxxxxxxxxxx", roles: new[] { RoleNames.Admin });
        }

        public void BeforePluginsLoaded(IAppHost appHost)
        {
            appHost.AssertPlugin<AuthFeature>().AuthEvents.Add(new AppUserAuthEvents());
        }

        // Add initial Users to the configured Auth Repository
        public void CreateUser(IAuthRepository authRepo, string email, string name, string password, string[] roles)
        {
            if (authRepo.GetUserAuthByUserName(email) == null)
            {
                var newAdmin = new AppUser { Email = email, DisplayName = name };
                var user = authRepo.CreateUserAuth(newAdmin, password);
                authRepo.AssignRoles(user, roles);
            }
        }
    }
}

Please help.

Hi @AndyF,

How are you inspecting the session? The roles not being populated is due to using the UseDistinctRoleTables = true so they are not on the session but persisted in the database.

I have made an example project with an a similar setup to what you have provided and added a HelloAuth service + integration test showing the authentication and use of UserAuthId from session.

Hopefully this will help identify what might be different since I can’t see the whole context of you project. Feel free to create a GitHub repository with a reproduction of the issue you are seeing.

Hope that helps.

The strange thing is my authentication worked before on the same project. What I did now was created a x new web project and added auth, authRepo, Validation, Cors etc one by on and tested after each one. What I found was the moment I added Redis wit x mix redis, it stopped working. Here is the repo. https://github.com/andyfensham/SSAuthTest2. To reproduce, remove Configure.Redis.cs from the project and run authenticated Hello which works. Put back Configure.Redis.cs and it stops working. When I however ook at the session id in redis, it exists, so it did persist the session details to redis and show it as authenticated = true.

FYI an easy way to change whether a module is run or not is to comment out its HostingStartup regsitration, e.g:

//[assembly: HostingStartup(typeof(SSAuthTest2.ConfigureRedis))]

When enabled it will Register IRedisClientsManager which will if not registered, ServiceStack will register to use Redis as the ICacheClient caching provider since that’s the primary use-case for Redis.

You can prevent this and revert to using an In Memory Cache client by uncommenting this line:

Which resolves the issue because User Sessions persisted in Memory Cache Clients don’t get serialized and wont be affected by your custom global configuration which changes how everything is serialized from .NET’s default of camelCase to snake_case:

JsConfig.Init(new ServiceStack.Text.Config {
    TextCase = TextCase.SnakeCase,
});

If you don’t use change global configuration to serialize using snake_case it will work as expected since the serializers support both deserialization of PascalCase and camelCase properties by default, to opt-in to support deserializing snake_case properties into serializers not configured to use snake_case by changing it to lenient deserialization:

JsConfig.Init(new ServiceStack.Text.Config {
    TextCase = TextCase.SnakeCase,
    PropertyConvention = PropertyConvention.Lenient,
});

I’ll need to investigate why the AuthUserSession isn’t being deserialized with the global configuration, I’m assuming it’s due to it being a late bound serialized type (to support custom sub classes) which is what differentiates it from normal DTOs.

I need to use Redis for Caching and for session management as it will be a big system with load balancing and many instances of the Servicestack Services. Something in what you said above is not clear to me, so let me repeat what I think you say.

What would I do to make it use Redis Caching of Sessions? What I think you are saying is to use Redis Sessions, comment out

//services.AddSingleton<ICacheClient>(new MemoryCacheClient()); //Store User Sessions in Memory

And make sure that

[assembly: HostingStartup(typeof(SSAuthTest2.ConfigureRedis))]

is not commented out.

The biggest reason for using snake case, is that I have to do custom sql in my services which uses Postgres, so it becomes a problem as when I use native sql, it is snake case, and when I call it through normal ormlite with my POCOS, it is PascalCase. So I get very inconsistent ways of accessing the raw data vs through Ormlite select with POCOS being filled. So I am using dapper with raw sql statements for everything. We also have to make use of stored procedures a lot for performance and staying on the db instead of bringing huge amounts of data from db to services.

Any suggestions on how to handle that best. Awaiting your feedback on the AuthUserSession deserialization.

Simply to enable Redis for caching, ConfigureRedis needs to enabled and no other ICacheClient is registered.

I don’t understand how the serialization case used in your DTOs is relevant to querying DB tables? They shouldn’t be related, can you provide a small example that demonstrates the issue?

I have dto’s for my database that I auto generate from the database schemas I have. We work with different databases e.g. Postgres, SQL Server, SQLite, MySQL. My DTO’s whether generated from Postgres or SQL Server etc. look like

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int? Age { get; set; }
}

now in postgres, the sql you generate when I do a db.Select will be something like (not exact but just sample)

select id, first_name, last_name, age from person

, where in sql server it will be something like (not your exact sql but just sample)

select Id, FirstName, LastName, Age from Person

Now in some cases, I am getting dynamic sql that is stored in a database. The database also have an exact representation of the Person dto.

So I will have a Postgres version of the dynamic query and an SQL version of the dynamic query, which is built up with a template engine based on dynamic data being passed through e.g. Table Name, fields to bring back, where clause etc. When I call the dynamic query, I know the DTO Name but because I am calling it dynamically I can not do :

var person = db.Select<Person>("select * from person").,

I have a single function called RunQuery(QueryId) for my dynamic queries. I normally call these in dapper as I don’t know how to get the type back. But when I call it in dapper, the case of the field names differ depending on whether I use sql server or postgres

The result of that Query bring me back the dynamic sql query to run and the DTO Name.

By using OrmLite and the DTO’s I automatically get the correct snake_case PascalCase etc. conversion. But when I do it dynamically, I get the native case that comes back from the db.

I could either convert them dynamically in the results, but that will hard code it and the JSConfig settings for seriazation will then be bypassed.

Is not not possible to cast the results to a type by Type String Name. E.g.

Type personType = System.GetType("Person")
var person = db.Select<personType>("select id, first_name, last_name, age from person")

or

var person = db.Select<personType>("select Id, FirstName, LastName, Age from Person")

Alternatively, can I use your conversion methods you use to convert from database results to DTO’s to match what is set in the JSConfig.

Or can I use List<Dictionary<string,object>> and will the serialization conversion between snake_case, PascalCase etc. work as expected ?

Did you manage to find out why the above is not working ?

In the latest v5.13.3 release it’s now configured to use the Property Names during serialization. But I still wouldn’t recommend having any of your internal implementation concerns affecting your Services public API contract where convention mismatch issues are propagated down to your API consumers.

You can fetch the dynamic results as an Object Dictionary, remove ‘_’ from keys so they become a case insensitive match to your DTO properties, then use it populate the DTO, something like:

var results = db.Select<Dictionary<string,object>>(sql)
    .Map(row => row.Map(x => KeyValuePairs.Create(x.Key.Replace("_",""),x.Value))
        .ToObjectDictionary()
        .FromObjectDictionary<Person>());

Hi Mythz, I updated my repo and included a couple of versions of queries in my Hello Service. I also took the settings in Configure.AppHost.cs out so TextCase in JsConfig is default. There seem to be an inconsistent JSON serialization between the following queries.

var result = db.Select<MyTable>();

var resultdynamic = db.Select<dynamic>("select Id, FirstName from MyTable");

var dapperresult = db.Query("select Id, FirstName from MyTable", null);

var dappertyped = db.Query<MyTable>("select Id, FirstName from MyTable", null);

var dapperdict = Utils.DapperDynamicToIDictionaryList(dapperresult);

Where all the dynamic query results does not have the same serialization case as the typed results in json.

What’s an example of an actual issue? Serialization conventions only changes typed property names, it doesn’t change dictionary string keys if that’s the issue you’re referring to. If it is convert it into a typed DTO then serialize that.

If there is no strong type to convert into you’ll need to rewrite the Dictionary Key names with your own preferred convention.

Thanks, I thought that setting the json type conversion globally will affect all serialization, dynamic or strongly typed. I will do the conversion myself to align with the settings in the jsconfig. Thanks

It should never effect any payload data like string dictionary keys, no convention should.