0

I'm using the awesome ServiceStack to implement my REST backend which serves two iPhone apps written in Xamarin. Everything works great but i'm struggling in getting sessions to work correctly when the two apps are installed on the same device !

The issue is that if I login in one of the apps the second app gets authenticated and doesn't require me to login as a result of 'isCurrentUserAuthenticated()' method below.

I pass cookies with my requests to mimic the browser and to make sure user doesn't have to pass his credentials every time but I guess the problem is that maybe ServiceStack sees two authentication requests from the same IP so it authenticated them both using the first authentication requests succeeds.

Note : The two apps accesses the same database and UserAuth table but every app supports a user role different than the other.

The only way to fix it is to logout from the second app so the user can login again with his credentials to make everything work.

Can you please help with this ?

Here is the code so far :

public static class BLL
{
    public static JsonServiceClient ServiceClient { get; set; }

    public static string HostUri = "http://test.elasticbeanstalk.com";
    public static string HostDomain = "test.elasticbeanstalk.com";

    static BLL ()
    {
        string ss_id = ConfigRepository.GetConfigString ("ss-id");
        string ss_pid = ConfigRepository.GetConfigString ("ss-pid");

        ServiceClient = new  JsonServiceClient (HostUri);

        ServiceClient.CookieContainer.Add (new Cookie ("ss-id", ss_id, "/", HostDomain));
        ServiceClient.CookieContainer.Add (new Cookie ("ss-pid", ss_pid, "/", HostDomain));
    }


    public static async Task<bool> isCurrentUserAuthenticated ()
    {
        bool result = false;

        try {

            Authenticate authRequest = new Authenticate ();

            // Restore the cookie
            var response = await ServiceClient.PostAsync<AuthenticateResponse> (authRequest);

            NSUserDefaults.StandardUserDefaults.SetString (response.UserId, "UserId");
            NSUserDefaults.StandardUserDefaults.Synchronize ();

            result = true;

        } catch (Exception Ex) {
            result = false;
        }

        return result;
    }

    public static async Task<AuthenticateResponse> Login (string userName, string password)
    {
        Authenticate authRequest = new Authenticate () {
            provider = "credentials",
            UserName = userName,
            Password = password,
            RememberMe = true,
        };

        var response = await ServiceClient.PostAsync<AuthenticateResponse> (authRequest);

        var cookies = ServiceClient.CookieContainer.GetCookies (new Uri (HostUri));

        if (cookies != null) {
            var ss_id = cookies ["ss-id"].Value;
            var ss_pid = cookies ["ss-pid"].Value;

            if (!ss_id.IsNullOrEmpty ()) {
                int r = ConfigRepository.AddConfigKey ("ss-id", ss_id);
                System.Diagnostics.Debug.WriteLine ("ss-id " + ss_id.ToString ());
            }
            if (!ss_pid.IsNullOrEmpty ()) {
                int r = ConfigRepository.AddConfigKey ("ss-pid", ss_pid);
                System.Diagnostics.Debug.WriteLine ("ss-pid " + ss_pid.ToString ());
            }
        }

        NSUserDefaults.StandardUserDefaults.SetString (response.UserId, "UserId");
        NSUserDefaults.StandardUserDefaults.Synchronize ();


        return response;
    }

    public static async Task<AuthenticateResponse> Logout ()
    {
        Authenticate authRequest = new Authenticate () {
            provider = "logout"
        };

        var response = await ServiceClient.PostAsync<AuthenticateResponse> (authRequest);
        return response;
    }
}
  • how does the service know which app is requesting auth? You are not passing any sort of app identifier in the request. – Jason Jun 22 '15 at 00:11
  • @Jason I know but even if I pass some app identifier how will ServiceStack handle that out of the box ? i have no control over authenticate request.. So would i need to override the authentication provider ? – Mohammad Zekrallah Jun 22 '15 at 11:16
  • @Jason I think you are on the right track but if I append an app identifier with the cookies values so they are unique on the app level how will service stack match the cookies I send with the one it cached ? it will never match .. does that mean i need to override TryAuthenticate() to handle that logic myself ? thx – Mohammad Zekrallah Jun 22 '15 at 11:56

1 Answers1

1

The issue is because you're using the same Session Cookies with a shared ServiceClient instance which ends up referencing the same Authenticated Users Session.

ServiceStack Sessions are only based on the session identifiers (ss-id/ss-pid) specified by the clients cookies, if you use the same cookies you will be referencing the same Authenticated Users Session, they're not affected by IP Address or anything else.

If you want to authenticate as another user, use a new instance of the ServiceClient (so it's not using an existing Sessions Cookies).

mythz
  • 141,670
  • 29
  • 246
  • 390
  • Thanks Mythz for your answer .. it does make sense but actually how is the instance shared if everyone executes in a separate app ? the code above is indeed in a separate shared project but when execution happens, every app has their own instance of the ServiceStack Client .. So what should I do exactly ? Please elaborate .. thankt a lot – Mohammad Zekrallah Jun 22 '15 at 10:21
  • @MohammadZekrallah the instance isn't shared, but by the looks of your code you're likely sharing the same cookies - which is what determines what session the clients use. – mythz Jun 22 '15 at 11:25
  • I see .. so should I append maybe an app identifier with the cookies values ? so they are unique on the app level ? but again if I do that how will service stack match the cookies I send with the one he cached ? it will never match .. I don't know if i'm being extra dumb here but I don't see how that would work .. – Mohammad Zekrallah Jun 22 '15 at 11:34
  • and as you see from the code, every app stores in its own local sqlite database so they are not mixed up .. I think the problem is because after all, the userauth table is shared for both user roles so if i login in one app, i'm authenticated in the other .. maybe the question should be how to authenticate based on the role instead of only the credentials .. or maybe by the client app id or something like that .. but that's authorization not authentication .. I'm lost here .. any guidance ? – Mohammad Zekrallah Jun 22 '15 at 11:43
  • You shouldn't have to specify the Cookies manually, new Cookies are automatically assigned to the ServiceClient after a user successfully authenticates. The Cookies then just reference that authenticated Users Session. When you authenticate, the authenticated Session is for that user - there's no authentication for a single user role. It sounds like you may want to look at [extending the UserAuth table and Session](http://stackoverflow.com/a/11118747/85785) where you could store metadata about what App the User has access to. – mythz Jun 22 '15 at 12:42
  • thanks @mythz I get your point .. I had to send the cookies because iOS apps are not like the browser and they don't maintain the cookies when you close the app and reopen it .. otherwise, you will have to enter your credentials every time you open the app which is not feasible .. I got the cookies code from another Stackoverflow question trying to do the same actually .. – Mohammad Zekrallah Jun 22 '15 at 13:09
  • sorry to bother you .. i'm just trying to override TryAuthenticate but I don't have access to anything other than username and password .. I need a dictionary or something like that to pass the appID or user role the login is coming from so I can inspect it in TryAuthenticate and return proper responses .. how can I do something like that ? – Mohammad Zekrallah Jun 23 '15 at 18:41
  • @MohammadZekrallah The `Authenticate` DTO has a `Dictionary Meta` that you can assign custom data to, e.g: `?meta={appId:1}`. Also you can access any Custom QueryString inside TryAuthenticate with `authService.Request.QueryString["appId"]`. Please open up a new question for any remaining questions. – mythz Jun 23 '15 at 23:28
  • 1
    appreciated man .. thanks a lot .. by the way servicestack is just a brilliant piece of work .. it's the most clean oop REST framework i've seen so far .. hats to you and the rest of the team :+1 – Mohammad Zekrallah Jun 24 '15 at 09:24