3

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;
    }
}
  • I don't think this is related to your issue, but you shouldn't be filling the session in the `TryAuthenticate` method. The preferred practise is in the `OnAuthenticated` method. [See here](https://github.com/ServiceStackV3/ServiceStackV3/wiki/Authentication-and-authorization). It seems there is a problem retrieving the session back from Redis in the `AuthenticationAttribute`. To debug this, I would start by removing the attribute from one of your action handlers and try adding `base.Request.GetSession()` within it, and see if your session is restored properly in your service. – Scott Jan 08 '14 at 13:51
  • @Scott I can retrieve the session in the action handler when i remove the `[Authenticate]` Attribute. So it seems this is a bug in the `AuthenticationAttribute`? –  Jan 08 '14 at 14:16
  • 2
    It's not a bug there is something wrong in your implementation. I think you need to set `session.UserAuthName` and `session.UserAuthId` unless your override `CredentialProviders` `IsAuthorized` method. – Scott Jan 08 '14 at 14:40
  • Thank you @Scott, but I didn't get it to work with the CredentialsProvider. See my updated question. –  Jan 10 '14 at 11:19
  • Just looking over your code. Remove `public ICacheClient cache { get; set; }` The ICacheClient is already setup in `Service` from which you inherit. You can just call `base.Cache`. Still looking to find other issues. – Scott Jan 10 '14 at 11:24
  • Your code looks functional. Are you getting errors with your new code? – Scott Jan 10 '14 at 11:30
  • no, i wasn't clear in my updated question. the code does work. and I can add/delete sessions from redis. also the [SessionAuth] Attributes works. It's just that it seems odd that I have to implement my own SessionAuth Attribute just to get redis working. Anyway thanks for your help! –  Jan 10 '14 at 11:41
  • You should post your updated code as the answer then and remove it from the question. You can just copy and paste. – Scott Jan 10 '14 at 11:43

2 Answers2

3

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;
}
Andrei Schneider
  • 3,618
  • 1
  • 33
  • 41
1

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.