bgmulinari / B1SLayer

A lightweight SAP Business One Service Layer client for .NET
MIT License
136 stars 47 forks source link

Inconsistent Response Headers between Android and iOS in .NET MAUI with B1Slayer #49

Open aaron-yb opened 1 year ago

aaron-yb commented 1 year ago

Your project is great! It's saved me a ton of time. However when implementing B1Slayer in a .NET MAUI app, I've noticed an inconsistency in the response headers between Android and iOS. On Android, I receive authentication headers that are missing on iOS.

SLConnection.LoginAsync on Android gives the following output:

[DOTNET] Request URL: https://URL:50000/b1s/v1/Login [DOTNET] HTTP Method: POST [DOTNET] Response Status Code: OK [DOTNET] Request Headers: [DOTNET] Response Headers: [DOTNET] Connection: Keep-Alive [DOTNET] Date: Wed, 23 Aug 2023 10:05:18 GMT [DOTNET] Keep-Alive: timeout=5, max=100 [DOTNET] Server: Apache [DOTNET] Set-Cookie: B1SESSION=9056bec8-419c-11ee-c000-000c2980395a-3812-14596;HttpOnly;;Secure;SameSite=None, ROUTEID=.node1; path=/;Secure;SameSite=None [DOTNET] Vary: Accept-Encoding [DOTNET] X-Android-Received-Millis: 1692785120383 [DOTNET] X-Android-Response-Source: NETWORK 200 [DOTNET] X-Android-Selected-Protocol: http/1.1 [DOTNET] X-Android-Sent-Millis: 1692785117731 [DOTNET] Response Body: { [DOTNET] "odata.metadata" : "https://URL:50000/b1s/v1/$metadata#B1Sessions/@Element", [DOTNET] "SessionId" : "9056bec8-419c-11ee-c000-000c2980395a-3812-14596", [DOTNET] "Version" : "1000210", [DOTNET] "SessionTimeout" : 30 [DOTNET] }

The same request on iOS: 2023-08-23 11:06:57.445422+0100 SLTest[82914:1147716] Request URL: https://URL:50000/b1s/v1/Login 2023-08-23 11:06:57.445727+0100 SLTest[82914:1147716] HTTP Method: POST 2023-08-23 11:06:57.446298+0100 SLTest[82914:1147716] Response Status Code: OK 2023-08-23 11:06:57.446382+0100 SLTest[82914:1147716] Request Headers: 2023-08-23 11:06:57.446466+0100 SLTest[82914:1147716] Response Headers: 2023-08-23 11:06:57.446848+0100 SLTest[82914:1147716] Server: Apache 2023-08-23 11:06:57.446985+0100 SLTest[82914:1147716] Vary: Accept-Encoding 2023-08-23 11:06:57.448994+0100 SLTest[82914:1147716] Date: Wed, 23 Aug 2023 10:06:54 GMT 2023-08-23 11:06:57.449116+0100 SLTest[82914:1147716] Keep-Alive: timeout=5, max=100 2023-08-23 11:06:57.449216+0100 SLTest[82914:1147716] Connection: Keep-Alive 2023-08-23 11:06:57.453453+0100 SLTest[82914:1147716] Response Body: { "odata.metadata" : "https://URL:50000/b1s/v1/$metadata#B1Sessions/@Element", "SessionId" : "c9695270-419c-11ee-c000-000c2980395a-12492-1548", "Version" : "1000210", "SessionTimeout" : 30 }

Any iOS request afterwards is missing headers resulting in the following:

2023-08-23 11:27:14.436896+0100 SLTest[84984:1176478] Request URL: https://URL:50000/b1s/v1/Items?$select=ItemCode%2C ItemName 2023-08-23 11:27:14.437294+0100 SLTest[84984:1176478] HTTP Method: GET 2023-08-23 11:27:14.437526+0100 SLTest[84984:1176478] Response Status Code: Unauthorized 2023-08-23 11:27:14.437677+0100 SLTest[84984:1176478] Request Headers: 2023-08-23 11:27:14.437907+0100 SLTest[84984:1176478] Response Headers: 2023-08-23 11:27:14.438118+0100 SLTest[84984:1176478] Server: Apache 2023-08-23 11:27:14.438274+0100 SLTest[84984:1176478] Vary: Accept-Encoding 2023-08-23 11:27:14.438471+0100 SLTest[84984:1176478] Date: Wed, 23 Aug 2023 10:27:14 GMT 2023-08-23 11:27:14.438668+0100 SLTest[84984:1176478] WWW-Authenticate: Basic realm=/b1s/v1/Items 2023-08-23 11:27:14.438817+0100 SLTest[84984:1176478] Keep-Alive: timeout=5, max=100 2023-08-23 11:27:14.438967+0100 SLTest[84984:1176478] Connection: Keep-Alive 2023-08-23 11:27:14.439215+0100 SLTest[84984:1176478] Response Body: { "error" : { "code" : 301, "message" : { "lang" : "en-us", "value" : "Invalid session or session already timeout." } } }

Have tried adjusting iOS info.plist file with NSAppTransportSecurity headers.

Can reproduce by making new Maui (dotnet 7) project with B1SLayer 1.3.2, creating a new SLConnection, awaiting LoginAsync and examining the response using SLConnection.AfterCall

SLTest.zip

bgmulinari commented 1 year ago

Hi, @aaron-yb.

Sorry for my ignorance, but I've never developed a MAUI/Xamarin app targeting iOS. Can I still reproduce the issue with your sample project if I don't own a Mac nor an iPhone?

aaron-yb commented 1 year ago

Hi @bgmulinari - Just want to say again: Thank you, your project has saved me so much time!

I've come across an issue specifically with Mac Catalyst / iOS Maui builds. This persists on both the simulator and actual devices. As far as I know, iOS debugging does require a Mac - (Apple, right?)

Inspecting the SLConnection.cs class, specifically within the ExecuteLoginAsync method, I noticed that cookieJar has a count of 0 when making a request from Apple devices. Which would explain the issue with authentication as there isn't a cookie.

I've submitted a pull request which is a bit hacky.... that adjusts the ExecuteLoginAsync method to manually generate a cookie if one isn't present. It might come across as a bit of a workaround, but it resolves the issue I faced for now

bgmulinari commented 1 year ago

@aaron-yb, I took the liberty to modify your sample project to perform the login request using just Flurl, like B1SLayer performs in the background. Can you test it on iOS and report back, please?

From what you're describing, it sounds like it might be an issue with Flurl.

bgmulinari commented 1 year ago

SLTest2.zip

aaron-yb commented 1 year ago

Hi @bgmulinari here you go

On iOS / Mac I get the following output:

2023-08-23 14:56:09.536898+0100 SLTest[10595:1499318] Request URL: https://URL:50000/b1s/v1/Login 2023-08-23 14:56:09.537186+0100 SLTest[10595:1499318] HTTP Method: POST 2023-08-23 14:56:09.537798+0100 SLTest[10595:1499318] Response Status Code: OK 2023-08-23 14:56:09.537904+0100 SLTest[10595:1499318] Request Headers: 2023-08-23 14:56:09.538140+0100 SLTest[10595:1499318] Response Headers: 2023-08-23 14:56:09.538552+0100 SLTest[10595:1499318] Server: Apache 2023-08-23 14:56:09.538721+0100 SLTest[10595:1499318] Vary: Accept-Encoding 2023-08-23 14:56:09.540703+0100 SLTest[10595:1499318] Date: Wed, 23 Aug 2023 13:56:06 GMT 2023-08-23 14:56:09.540808+0100 SLTest[10595:1499318] Keep-Alive: timeout=5, max=100 2023-08-23 14:56:09.540898+0100 SLTest[10595:1499318] Connection: Keep-Alive 2023-08-23 14:56:09.542724+0100 SLTest[10595:1499318] Request Body: { "odata.metadata" : "https://URL:50000/b1s/v1/$metadata#B1Sessions/@Element", "SessionId" : "ce6f595c-41bc-11ee-c000-000c2980395a-14548-6632", "Version" : "1000210", "SessionTimeout" : 30 } 2023-08-23 14:56:09.558334+0100 SLTest[10595:1498480] Cookies received:

On Android I get the following:

[DOTNET] Request URL: https://URL:50000/b1s/v1/Login [DOTNET] HTTP Method: POST [DOTNET] Response Status Code: OK [DOTNET] Request Headers: [DOTNET] Response Headers: [DOTNET] Connection: Keep-Alive [DOTNET] Date: Wed, 23 Aug 2023 14:02:19 GMT [DOTNET] Keep-Alive: timeout=5, max=100 [DOTNET] Server: Apache [DOTNET] Set-Cookie: B1SESSION=ac58e080-41bd-11ee-c000-000c2980395a-14752-8204;HttpOnly;;Secure;SameSite=None, ROUTEID=.node8; path=/;Secure;SameSite=None [DOTNET] Vary: Accept-Encoding [DOTNET] X-Android-Received-Millis: 1692799340723 [DOTNET] X-Android-Response-Source: NETWORK 200 [DOTNET] X-Android-Selected-Protocol: http/1.1 [DOTNET] X-Android-Sent-Millis: 1692799338028 [DOTNET] Request Body: [DOTNET] { [DOTNET] "odata.metadata" : "https://URL:50000/b1s/v1/$metadata#B1Sessions/@Element", [DOTNET] "SessionId" : "ac58e080-41bd-11ee-c000-000c2980395a-14752-8204", [DOTNET] "Version" : "1000210", [DOTNET] "SessionTimeout" : 30 [DOTNET] } [DOTNET] Cookies received: [DOTNET] ROUTEID: .node8 [DOTNET] B1SESSION: ac58e080-41bd-11ee-c000-000c2980395a-14752-8204 [DOTNET] Request URL: https://URL:50000/b1s/v1/Login [DOTNET] HTTP Method: POST [DOTNET] Response Status Code: OK [DOTNET] Request Headers: [DOTNET] Response Headers: [DOTNET] Connection: Keep-Alive [DOTNET] Date: Wed, 23 Aug 2023 14:02:19 GMT [DOTNET] Keep-Alive: timeout=5, max=100 [DOTNET] Server: Apache [DOTNET] Set-Cookie: B1SESSION=ac567d90-41bd-11ee-c000-000c2980395a-8440-7940;HttpOnly;;Secure;SameSite=None, ROUTEID=.node7; path=/;Secure;SameSite=None [DOTNET] Vary: Accept-Encoding [DOTNET] X-Android-Received-Millis: 1692799340837 [DOTNET] X-Android-Response-Source: NETWORK 200 [DOTNET] X-Android-Selected-Protocol: http/1.1 [DOTNET] X-Android-Sent-Millis: 1692799338028 [DOTNET] Request Body: [DOTNET] { [DOTNET] "odata.metadata" : "https://URL:50000/b1s/v1/$metadata#B1Sessions/@Element", [DOTNET] "SessionId" : "ac567d90-41bd-11ee-c000-000c2980395a-8440-7940", [DOTNET] "Version" : "1000210", [DOTNET] "SessionTimeout" : 30 [DOTNET] } [DOTNET] Cookies received: [DOTNET] ROUTEID: .node7 [DOTNET] B1SESSION: ac567d90-41bd-11ee-c000-000c2980395a-8440-7940

bgmulinari commented 1 year ago

Thanks for the prompt response, @aaron-yb.

From what I could understand, this issue it not related to B1SLayer. It appears that for some reason, on iOS, the response headers are not received by Flurl and that results in an empty CookieJar in B1SLayer.

I appreciate your contribution, but although the workaround you provided in the PR #50 might solve this issue, it's generally not a good idea to omit the ROUTEID cookie because of Service Layer's load balancer, so I don't think it would be a good addition to B1SLayer.

I encourage you to open an issue in the Flurl repository so they can have a proper look on what's really happening in this case. You could even link this issue there.

If this proves to be an issue with Flurl and they release a fix, rest assured that I will update B1SLayer with the new Flurl version.

I will keep this issue open for now.

bgmulinari commented 1 year ago

@aaron-yb, could you do one more test, please?

Add the method bellow to the sample project an call it, then report back the console output.

This will perform the login request using just the native HttpClient instead of Flurl.

private async Task HttpClientLoginAsync()
{
    var handler = new HttpClientHandler();
    handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;

    using (var client = new HttpClient(handler))
    {
        var requestUri = new Uri(new Uri(_serviceLayerUrl.EndsWith('/') ? _serviceLayerUrl : _serviceLayerUrl + "/"), "Login");
        var loginData = new { CompanyDB = _companyDB, UserName = _username, Password = _password };
        var content = new StringContent(JsonConvert.SerializeObject(loginData), Encoding.UTF8, "application/json");

        var response = await client.PostAsync(requestUri, content);
        var loginResponse = await response.Content.ReadAsStringAsync();

        Console.WriteLine("Response body:");
        Console.WriteLine(loginResponse);

        Console.WriteLine("Response Headers:");
        response.Headers?.ToList().ForEach(header => Console.WriteLine($"{header.Key}: {string.Join(",", header.Value)}"));
    }
}
aaron-yb commented 1 year ago

Hi @bgmulinari , here you go:

2023-08-23 20:40:45.050977+0100 SLTest[31284:1761367] Response body: 2023-08-23 20:40:45.051188+0100 SLTest[31284:1761367] { "odata.metadata" : "https://URL:50000/b1s/v1/$metadata#B1Sessions/@Element", "SessionId" : "f1e3d0ae-41ec-11ee-c000-000c2980395a-12492-1548", "Version" : "1000210", "SessionTimeout" : 30 } 2023-08-23 20:40:45.051290+0100 SLTest[31284:1761367] Response Headers: 2023-08-23 20:40:45.054745+0100 SLTest[31284:1761367] Server: Apache 2023-08-23 20:40:45.054894+0100 SLTest[31284:1761367] Vary: Accept-Encoding 2023-08-23 20:40:45.054981+0100 SLTest[31284:1761367] Date: Wed, 23 Aug 2023 19:40:42 GMT 2023-08-23 20:40:45.055156+0100 SLTest[31284:1761367] Keep-Alive: timeout=5, max=100 2023-08-23 20:40:45.055258+0100 SLTest[31284:1761367] Connection: Keep-Alive 2023-08-23 20:40:45.055339+0100 SLTest[31284:1761367] Set-Cookie: B1SESSION=f1e3d0ae-41ec-11ee-c000-000c2980395a-12492-1548; Path=/b1s/v1; Domain=URL; Version=0; Discard; Secure; httponly,ROUTEID=.node2; Path=/; Domain=URL; Version=0; Discard; Secure

Guessing it's Flurl issue then

bgmulinari commented 1 year ago

Hi, @aaron-yb.

I see that in issue you opened in Flurl's repository, you were asked to perform another test, but didn't respond. Since this problem only seems to happen on iOS, it would be nice if you could help troubleshooting it there.

Are you still facing this problem?

aaron-yb commented 1 year ago

Hi @bgmulinari , apologies. I have now updated the issue with the results.

The problem does still persist