2

The problem: it seems to me, like the normal and best way to look at sessions is: One session per device, per user.

So, you can have one session alive per device, meaning one session per web browser, per phone, tablet etc. You should not be allowed have two or more valid sessions for the same phone, for example.

When I implemented my own session cache and auth structure, I implemented it as above, since my apps are sending a "DeviceUUID" that is unique for each device. In that way, I could detect that "there is a valid session for this DeviceUUID and User already" and act accordingly (replace the old session with a new one).

Now, when I am evaluating ServiceStack, I'd like some input on how to do this using the IAuthSession etc.

Currently, I have custom code to authenticate to the backend, so I get a IAuthSession that I populate with some data, like:

                session.FirstName = alrReply.Actor.Firstname;
                session.IsAuthenticated = true;
                session.UserAuthName = alrReply.Actor.Username;
                session.UserAuthId = alrReply.AuthToken;
                session.Roles.Add("alfaconnect");
                base.Request.SaveSession(session);

I also have access to the "DeviceUUID", but I'm not sure how to make sure ServiceStack behaves as described above. Is it doable?

I have read similar posts here in SO, but either they didn't address the same issue, of I didn't understand the answer.

halfer
  • 19,824
  • 17
  • 99
  • 186
Ted
  • 19,727
  • 35
  • 96
  • 154

2 Answers2

2

This feature isn't supported explicitly, but you should be able to implement it with a Custom AuthProvider and store the Session Id of the current device in an extended UserAuth table. Basically you want to store the current SessionId and its DeviceUUID and if it's different, remove the old Session.

You can send additional metadata during Authentication in the Authenticate.Meta dictionary used for Authentication or if preferred add additional info on the QueryString or HTTP Headers which can be accessed from the IRequest context inside your Custom AuthProvider.

Sessions are just plain AuthUserSession DTOs persisted in the registered ICacheClient in the following format:

urn:iauthsession:{sessionId}

Where an existing Session can be removed using IRequest.RemoveSession(), e.g:

req.RemoveSession(sessionId);

Which will remove the previous Session where they'll no longer be able to make Authenticated Requests.

If you wanted to notify existing devices that they've been logged out you can use Server Events to send a Custom Notification using the NotifySession() IServerEvents API using the previous SessionId. Alternatively they can periodically poll /auth (or send an empty client.Get(new Authenticate()) to check if they're still Authenticated.

mythz
  • 141,670
  • 29
  • 246
  • 390
  • Thanks for the input! I will see what I can do with this, and I'll get back to you here when I run into problems, or if it works, set it as the correct answer. – Ted Feb 13 '18 at 12:20
  • Q1: The `Meta string dictionaries` mentioned in https://stackoverflow.com/questions/11117469/how-can-i-extend-servicestack-authentication/11118747#11118747, where is that? I tried to find some meta-data-dictionary in the IAuthSession, but there was none. The easiest way, I think, would to simply add the `DeviceUUID` to the `IAuthSession`. But how? Id like to avoid implemening my own IAuthSession, for now. – Ted Feb 13 '18 at 12:48
  • (Currently, i just add the `DeviceUUID` to the `AuthProvider` string) – Ted Feb 13 '18 at 13:05
  • Also, I read this post: https://stackoverflow.com/a/32461871/178143 Is that still the preferred way to find sessions? Cause In my case, I need to check all Sessions that has the same DeviceUUID, and throw all old ones if this new login is a success. As I see it, the ICacheClientExtended searched for session id, not something else, like AuthProvider (that I have placed the DeviceUUID in). – Ted Feb 13 '18 at 13:26
  • You can store additional info in the [Meta Dictionary](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack/Auth/UserAuth.cs#L54) of the `UserAuth` table. The reason why I said to store the previous Session Id is so you don't have to Search existing Sessions. The bottom of the [Sessions docs](http://docs.servicestack.net/sessions#inspecting-persisted-user-sessions) shows an example of scanning through existing sessions, but deserializing all existing Sessions does not scale well in Apps maintaining many active User Sessions. – mythz Feb 13 '18 at 18:19
1

The way I solved the question can be seen below, even though a much more efficient way to check for duplicate sessions for the same DeviceUUID should be implemented.

  1. I am using my own custom login stuff
  2. When I get a successful login, I go through all IAuthSessions and check for other sessions that has the same DeviceUUID and UserId (my custom userId)
  3. I use the IAuthSession AuthProvider to save the DeviceUUID

if (alrReply.Success) // when my custom code decided login was OK
{
    ICacheClient cacheClient = TryResolve<ICacheClient>();
    IDictionary<string, IAuthSession> sessionList = cacheClient?.GetAll<IAuthSession>(cacheClient?.GetAllKeys());

    foreach (KeyValuePair<string, IAuthSession> kvp in sessionList)
    {
        if (kvp.Value.AuthProvider == deviceUUID && // Found a session from the same Device
            kvp.Value.UserAuthId == alrReply.Actor.Id.ToString()) // for the same user
        {
            // We only allow one session per device and user
            Request.RemoveSession(kvp.Value.Id);
            Console.WriteLine($"Removed duplicate session ({kvp.Value.Id}) for Device {kvp.Value.AuthProvider} for user {kvp.Value.DisplayName}, nbr of sessions was {sessionList.Count}");
        }
    }

    session.IsAuthenticated = true;
    session.UserAuthId = alrReply.Actor.Id.ToString();
    session.UserAuthName = alrReply.Actor.Username;
    session.DisplayName = alrReply.Actor.ToString();
    session.AuthProvider = deviceUUID;

    session.FirstName = alrReply.Actor.Firstname;
    session.LastName = alrReply.Actor.Lastname;

    session.Roles = new List<string>();
    session.Roles.Add("alfapro");

    base.Request.SaveSession(session);
    // ... etc
Ted
  • 19,727
  • 35
  • 96
  • 154