Support CustomCredentialsAuthProvider in Java JsonServiceClient

Add session cookies handling and automatic re-authentication on Java JsonServiceClient.

This should be similar to .NET client https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization#authenticating-with-net-service-clients

This is already recently implemented for BasicAuth in the latest 1.0.23 Java plugins following a similar API to the .NET Service Clients, you can view TestAuthTests.java for examples.

Yes, it is implemented for the BasicAuth but it is not implemented for the CustomCredentials.

For example this code in .NET is not possible in Java

var client = new JsonServiceClient(BaseUrl);

var authResponse = client.Post(new Authenticate {
    provider = CredentialsAuthProvider.Name, //= credentials
    UserName = "test@gmail.com",
    Password = "p@55w0rd",
    RememberMe = true,
});

Why isn’t it possible in Java? The Authenticate DTO should be included in the generated DTO’s. Also you don’t get auto re-authentication with custom credentials in .NET, that only works for Basic and Digest Auth.

Wait nvm, just noticed the Java JsonServiceClient doesn’t automatically retain cookies to keep the session alive, I’ll look at trying to add it.

I’ve added implicit support for automatically populating Session Cookies in this commit.

It will be enabled by default in the next 1.0.24 release of the Java plugins. To get it to work before then you can just assign a default Cookie manager, e.g:

CookieHandler.setDefault(new CookieManager());

Which will assign the response Session Cookies on subsequent requests as seen in this test:

public void test_can_authenticate_with_CredentialsAuth(){
    ServiceClient client = CreateClient();

    AuthenticateResponse authResponse = client.post(new Authenticate()
        .setProvider("credentials")
        .setUserName("test")
        .setPassword("test"));

    assertEquals("1", authResponse.getUserId());
    assertEquals("test", authResponse.getUserName());
    assertNotNull(authResponse.getSessionId());

    TestAuthResponse response = client.get(new TestAuth());

    assertEquals("1", response.getUserId());
    assertEquals("test", response.getUserName());
    assertEquals("test DisplayName", response.getDisplayName());
    assertNotNull(response.getSessionId());
}

I’m seeing some weird behavior with this using the 1.0.33 release. It seems to be somewhat randomly not retaining cookies (those are the symptoms). My test case is I kill off my app and then restart it and upon startup it authenticates using a singleton JsonServiceClient. On a later request (based upon user action) it will then fail a permission check because it can’t find the session. I can confirm that my custom credentials provider is getting called upon authenticate.

My app is written in kotlin (1.1.61) and using Android Studio 3.0.1. The API calls are made using co-routines. I mention this just for background as I do not think they should have any bearing on this.

I have an Xamarin.iOS app written that uses the same back-end services and it works fine, as does the web app using the same services. If you have suggestions on what might be happening it would be appreciated.

We’re going to need a real repro in order to be able to investigate. The JsonServiceClient uses Android Background Async tasks, which Kotlin’s coroutines may or may not interfere with. Can you verify this behavior happens with normal usage of the JsonServiceClient and provide a stand-alone integration test that shows the issue. Please also include the full HTTP Request and Response Headers of the integration test.

Although I am using both JsonServiceClient, & AndroidServiceClient, the authenticated calls are only using JsonServiceClient synchronously (e.g. post and get). Those are not using Android Background async tasks are they?

As far as a real repro I will see what I can do as it doesn’t happen consistently.

Only async APIs uses Async background tasks, but I’d still want to verify coroutines aren’t causing the issue and it’s happening with normal usage.

Given its inconsistent behavior you may be having multi threading issues, you can try using a new instance in each couroutine to see if that helps, if that fails see if it’s happening without coroutines.

I have confirmed that when it fails the response cookies (from auth) differ from the request cookies (get request) for ss-id & ss-pid. So it would seem to be a case where the JsonServiceClient cookies changed for some reason between the 2 API calls.

Is this through normal usage, i.e. without coroutines? If so please provide the integration test code + HTTP Request/Response headers.

It appears it is only occurring with coroutines. I will have to do more testing to confirm that the AndroidServiceClient is working 100% of the team.

The coroutine version looks something like this:

launch(UI) {
val result = async(CommonPool) {
myServiceStackService.SomeMethod(id)
}.await()
}

where someMethod is just declared as

suspend fun SomeMethod(id: Int) : Boolean {
  // this calls some JsonServiceClient.get or post method and returns the result
}

etc.

Anyhow, at this point I may just not use the coroutine version until I have more time to figure out what is happening.

I guess I spoke too soon–after more testing I am seeing the error in the non-coroutine version as well.

So after doing more testing on this and creating a sample project I believe it’s due to running multiple concurrent requests at the same time.

For example, upon startup I do an authentication request (/auth via a post) asynchronously and then also do a get asynchronously. These requests use a singleton object and depending on timing on how the requests run it makes me think that the cookies get updated after the get request runs but it is after the AUTH POST completes. And thus subsequent requests are no longer authenticated.

Further evidence of this is I occasionally am seeing a WebServiceException on the authenticate request–the message indicates that a POST or ANY method was not found, but the service mentioned in the error is for the second get. So it’s like the base url for the second request got super-imposed upon the first request.

I believe that java.net.CookieHandler introduces global state and thus even when I was using a singleton handler for authenticated requests and a different handler for non-authenticated requests the cookies could get overwritten by non-authenticated requests? I’m going to have to re-think how I’m making these requests. Let me know what you think of this.