redisservicestackservicestack-bsdservicestack-auth

How do I use my custom ServiceStack authentication provider with Redis?


I have implemented a custom CredentialsAuthProvider for my authentication and used it with the default in memory session storage.

Now I tried to change the session storage to Redis and added this to my Configure() method in the AppHost:

container.Register<IRedisClientsManager>(c => 
    new PooledRedisClientManager("localhost:6379"));

container.Register<ICacheClient>(c => (ICacheClient)c
    .Resolve<IRedisClientsManager>()
    .GetCacheClient()).ReusedWithin(Funq.ReuseScope.None);

Now when I authenticate, I can see that a key with urn:iauthsession:... is added to my Redis server. But all routes with the [Authenticate] attribute give a 401 Unauthorized error.

The CustomCredentialsAuthProvider is implemented like this:

public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
    public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
    {
        if (userName != string.Empty && password != string.Empty)
        {
            // Database call ...
            var session = (CustomSession)authService.GetSession();
            session.ClientId = login.ClientId;
            // Fill session...
            authService.SaveSession(session, SessionExpiry);
            return true;
        }
        return false;
    }
}

ServiceStack Version: 3.9.71

EDIT :

I tried to override the CredentialsAuthProvider IsAuthorized method but without success.

But I'm inheriting my session object from AuthUserSession, which also has a IsAuthorized method. When I return true from this method the Redis session does work with the Authenticate Attribute.

public class CustomSession : AuthUserSession
{
    public int ClientId { get; set; }
    ...

    public override bool IsAuthorized(string provider)
    {
        return true;
    }
}

Solution

  • I couldn't figure out a way to get the [Authenticate] Attribute to work with Redis storage.

    I had to write a custom [SessionAuth] Attribute

    public class SessionAuthAttribute : RequestFilterAttribute
    {
        public ICacheClient cache { get; set; }
        public string HtmlRedirect { get; set; }
    
        public SessionAuthAttribute()
        {
        }
    
        public override void Execute(IHttpRequest req, IHttpResponse res, object requestDto)
        {
            string sessionId = req.GetSessionId();
            if (string.IsNullOrEmpty(sessionId))
            {
                HandleNoSession(req, res);
    
            }
            else
            {
                var session = cache.Get<CustomSession>("urn:iauthsession:" + sessionId);
                if (session == null || !session.IsAuthenticated)
                {
    
                    HandleNoSession(req, res);
                }
            }
        }
    
        private void HandleNoSession(IHttpRequest req, IHttpResponse res)
        {
    
            if (req.ResponseContentType.MatchesContentType(MimeTypes.Html))
            {
    
                res.RedirectToUrl(HtmlRedirect);
                res.End();
    
            }
            res.StatusCode = (int)HttpStatusCode.Unauthorized;
            res.Write("not authorized");
            res.Close();
        }
    }
    

    In my AppHost Configure() method I just register the SessionFeature and the IRedisClientsManager/ICacheClient:

    Plugins.Add(new SessionFeature());
    
    container.Register<IRedisClientsManager>(c => new PooledRedisClientManager("localhost:6379"));
    
    container.Register<ICacheClient>(c => (ICacheClient)c.Resolve<IRedisClientsManager>()
            .GetCacheClient()).ReusedWithin(Funq.ReuseScope.None);
    

    The CustomSession class inherits from AuthUserSession:

    public class CustomSession : AuthUserSession
    {
        public int ClientId { get; set; }
        ...
    }
    

    And I have a normal service route on /login/auth for the authentication part and a /login/logout route to remove the session:

    public class LoginService : Service
    {
        public ICacheClient cache { get; set; }
    
        public object Post(AuthRequest request)
        {
            string userName = request.UserName;
            string password = request.Password;
    
            // check login allowed
    
            if (IsAllowed)
            {
    
                var session = SessionFeature.GetOrCreateSession<CustomSession>(cache);
    
                session.ClientId = login.ClientId;
                ...
                session.IsAuthenticated = true;
                session.Id = SessionFeature.GetSessionId();
    
                this.SaveSession(session, TimeSpan.FromSeconds(30 * 60));
    
    
                return true;
            }
    
            return false;
        }
    
    
        [SessionAuth]
        public object Any(LogoutRequest request)
        {
            this.RemoveSession();
            return true;
        }
    }
    

    }

    I'm still interested in a solution that works with the normal [Authenticate] Attribute.