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

1.5k views Asked by At

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;
    }
}
2

There are 2 answers

0
AudioBubble On BEST ANSWER

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.

0
Andrei Schneider On

The Authenticate attribute calls the IsAuthorized of the AuthUserSession class. In my case to make it work with the Redis cache client, I've done the following

public override bool IsAuthorized(string provider)
{
    string sessionKey = SessionFeature.GetSessionKey(this.Id);
    ICacheClient cacheClient = AppHostBase.Resolve<ICacheClient>();

    CustomUserSession session = cacheClient.Get<CustomUserSession>(sessionKey);

    if (session == null)
    {
        return false;
    }

    return session.IsAuthenticated;
}