Problem with HttpClient after updating Xamarin.iOS

JordiCorominasJordiCorominas ESMember
edited June 2015 in Xamarin.iOS

Hi,

I'm currently investigating some strange behavior I see when using the System.Net.Http.HttpClient.

We have a REST API with NTLM Authentication enabled running on our server and our apps (iOS, Android, WP) get and POST data to this API. This solutions has been working fine for many months but last week we updated Xamarin Studio and Xamarion.iOS to the latest version and since then the POST (only the POSTs, GETs are working perfectly) stopped to work and the server returns 401 UNAUTHORIZED. This error is only happening on iOS, the Android and Windows Phone versions are still work perfectly with the same code... All of them use the same Service to communicate with the API.

It's super weird and it's driving us crazy. We tried to force old versions of the Microsoft.Net.Http, to use a package called ModernHttpClient... and nothing worked. We don't remember the old version of Xamarion.iOS but it was quite old... around 8.6, and now we are using the latest.

In the following lines you'll find the functions we use to perform the communications. First the GET function, that works perfectly and then the POST function that only doesn't work on iOS and was working perfectly on the previous version.

public async Task Get<T>(NetworkCredential credential, string url, Action<T> onSuccess, Action<PostErrorMessage> onError)
    {
        var httpClientHandler = new HttpClientHandler { Credentials = credential.GetCredential(new Uri(url), "NTLM") };

        using (var client = new HttpClient(httpClientHandler))
        {
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            client.DefaultRequestHeaders.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");

            try
            {
                var httpResponseMessage = await client.GetAsync(url);

                if (httpResponseMessage.StatusCode != HttpStatusCode.OK)
                {
                    onError(new PostErrorMessage(this, httpResponseMessage.ReasonPhrase, httpResponseMessage.StatusCode));
                }
                else
                {
                    var responseString = await httpResponseMessage.Content.ReadAsStringAsync();
                    var serializer = new JsonSerializer();
                    try
                    {
                        var json = serializer.Deserialize<T>(new JsonTextReader(new StringReader(responseString)));
                        onSuccess(json);
                    }
                    catch (Exception ex)
                    {
                        onError(new PostErrorMessage(this, ex.ToString(), httpResponseMessage.StatusCode));
                    }
                }
            }
            catch (Exception ex)
            {
                onError(new PostErrorMessage(this, ex != null ? ex.ToString() : string.Empty, HttpStatusCode.NotFound));
            }
        }
        System.Diagnostics.Debug.WriteLine(string.Format("DEBUG--------------------  seconds from {0}:[END Get]", DateTime.Now));
    }

public async Task Post<T>(NetworkCredential credential, string url, object data, Action<T> onSuccess, Action<PostErrorMessage> onError)
    {
        var httpClientHandler = new HttpClientHandler { Credentials = credential.GetCredential(new Uri(url), "NTLM") };

        using (var client = new HttpClient(httpClientHandler))
        {
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            client.DefaultRequestHeaders.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");

            var httpResponseMessage = await client.PostAsync(
                    new Uri(url),
                    new StringContent(
                        JsonConvert.SerializeObject(data, new JsonSerializerSettings()),
                        System.Text.Encoding.UTF8,
                        "application/json"));

            if (httpResponseMessage.StatusCode != HttpStatusCode.Created &&
                httpResponseMessage.StatusCode != HttpStatusCode.OK)
            {
                onError(new PostErrorMessage(this, httpResponseMessage.ReasonPhrase, httpResponseMessage.StatusCode));
            }
            else
            {
                var responseString = await httpResponseMessage.Content.ReadAsStringAsync();
                var serializer = new JsonSerializer();
                var json = serializer.Deserialize<T>(new JsonTextReader(new StringReader(responseString)));
                onSuccess(json);
            }
        }
    }

Any help or any clue will be more than welcome :)

Many thanks in advance!

Jordi

Answers

  • ArturMalendowiczArturMalendowicz USMember ✭✭

    Same problem, check what happens if you move this code inside your iOS project. For me it started working...

  • EidandEidand GBMember, University ✭✭✭

    I take it that the code is inside a PCL, right?

    If only iOS is not working, I would look into what details are getting passed to the PCL. 401 unauthorized means you're missing the authentication details or they are wrong. I am not convinced it's a Xamarin or iOS problem. Just check that someone didn't change the iOS integration with your PCL.

    This may or may not help you, I am using Microsoft HTTP Client Libraries PCl package for my rest stuff and I didn't have an issue with the types of authentication used. You're welcome to have a look here to see an example : http://eidand.com/2015/04/22/xamarin-ios-authentication-using-owin-web-api2-json-web-tokens/

  • MatthewNoelMatthewNoel USMember ✭✭

    I have a similar issue. Previously working POST commands are no longer working with NTLM authentication. I'm the only developer working on this app and have not changed anything about the PCL integration in my iOS app. In my case the only change made was getting the latest version of Xamarin.iOS. I happen to have an older MBP that I just replaced and on there everything still works fine, but it's not yet updated.

  • JordiCorominasJordiCorominas ESMember

    We are exactly in the same situation MatthewNoel... we installed Xamarin 5.8 and Xamarin.iOS 8.8.0.2 and it's working again!

    We have investigated a little bit more using Fiddler: the working version negotiates the NTLM in the proper way: 3 calls to the same URL and the third gets a 200. However, compiled using the latest version of Xamarin.iOS the first call is done to the host, ignoring the rest of the URL. I can't understand why this changed...

  • ta.speot.ista.speot.is AUMember

    (This might not be related, please disregard if it's not.) I am currently seeing the "Authorization" header on System.Net.HttpWebRequests being discarded. This happens with webRequest.PreAuthenticate = true or false, as soon as I change the header from "Authorization" to "ZAuthorization" it sends it straight down.

    Version: 8.10.1.64 (Business Edition)

  • MatthewNoelMatthewNoel USMember ✭✭

    I can confirm that downgrading Xamarin.iOS to 8.9.1.3 fixes this issue. I kept the latest version of Studio without any problem.

  • WileyPageWileyPage USMember ✭✭

    OMG, thank you for this. I've been beating my head against this all day. I can also confirm that dropping back to 8.9.1.3 works.

  • MatthewNoelMatthewNoel USMember ✭✭

    I submitted a bug here: https://bugzilla.xamarin.com/show_bug.cgi?id=30883. Help add detail to my poor description so they can fix it! :)

  • MatthewNoelMatthewNoel USMember ✭✭
    edited June 2015

    Nevermind, mine was a duplicate of https://bugzilla.xamarin.com/show_bug.cgi?id=30869. I guess you can't edit or delete messages on this forum after 1 hour.

  • GuidoKerstenGuidoKersten NLUniversity ✭✭

    I can also confirm that downgrading to Xamarin.IOS 8.9.1.3 fixed the problem.. Just wasted a day trying to find the problem.. So thanks for this thread! Hopefully we will get a quick fix for this...

  • fontanka16fontanka16 USMember

    We have the same issue using Xamarin Android. When i read the mentioned Bugzilla bug report (https://bugzilla.xamarin.com/show_bug.cgi?id=30869) they say that it is fixed, but how do i get this fix installed? Is it in some release yet?

  • MatthewNoelMatthewNoel USMember ✭✭

    They said this would be fixed in SR3 of the latest Xamarin.iOS, so I assume that corresponds with 8.10.3? I don't do any Android dev, so I'm not sure what version you're on there, but it was apparently fixed in Mono 4.2, whenever they roll that in, you should get the fix, assuming it's the same issue+fix.

    I'm eagerly awaiting this, I can tell you!

  • fontanka16fontanka16 USMember

    Since GET is working (not PUT it seems), we can manage with a temporary workaround for a while, but i sure want to have it fixed..

  • UrmilSetiaUrmilSetia USUniversity ✭✭

    Hi All,

    Can you please tell us, how to set credentials for accessing the web service using ModernHttpClient.
    i am set the credentials like below, but getting 401-unautherized error.

        var _nativehandler = new NativeMessageHandler ();
        _nativehandler.AllowAutoRedirect = false;
        _nativehandler.UseProxy = false;
        _nativehandler.AutomaticDecompression = DecompressionMethods.None;
        _nativehandler.Credentials = new NetworkCredential (_UserName,_Password,_Domain);
    
        var httpClient = new HttpClient (_nativehandler);
        string answer = await  httpClient.GetStringAsync (_URL).ConfigureAwait (true);
    

    Thanks in advance for your support.

    Thanks
    Urmil

  • WileyPageWileyPage USMember ✭✭

    @UrmilSetia ModernHttpClient is also broken for NTLM, even after rolling back to Xamarin.iOS 8.9.1.3. I tried rolling back to a previous version of ModernHttpClient as well, but couldn't find one that worked.

  • GuidoKerstenGuidoKersten NLUniversity ✭✭

    I updated my environment to Windows 10 + VS2015 and the latest Xamarin tooling, after using the old Xamarin.IOS version for some time.. And now the problem returned.. Any ideas on when this bugfix is going to be released?

  • GuidoKerstenGuidoKersten NLUniversity ✭✭

    I didn't want to do an other downgrade, so I took a deepdive into this.. For me the problem was related to using Basic authentication with HttpClient, I was using a HttpHandler and set the Credentials on it like this:

    _handler = new HttpClientHandler() { Credentials = new System.Net.NetworkCredential(_account.Username, _account.Password) }; _client = new HttpClient(_handler);

    This gave me an 401 error on IOS, so I managed to work around this by setting the authorization header by myself:
    byte[] authBytes = Encoding.UTF8.GetBytes(_account.Username + ":" + _account.Password); httpRequestMessage.Headers.Add("Authorization", "BASIC " + Convert.ToBase64String(authBytes));

  • JimBennettJimBennett GBXamarin Team, Insider, University, Developer Group Leader ✭✭✭✭

    @Guido_Kersten - thanks for the workaround, I've just made the change to my app and it now works. Only change I made to your code was to use Basic instead of BASIC. Not sure all the scenarios but for what I was using it failed if it was all in uppercase.

    In other good news though, whilst I was uploading my fixed binary to iTunesConnect, Xamarin have released a hotfix: https://releases.xamarin.com/ios-hotfix-for-watch-app-submission-technical-bulletin/

  • AlighttestAlighttest USMember

    Hello guys,
    I am experiencing the same problem with xamarin.forms and NTLM authentication. the Get methods works perfectly but the Post methods fails to pass the Credentials. it works also on windows phone: both Get and Post Methods. But not on iOS nor Android. Did someone came up with a work around?? Is this bug fixed Yet??
    My hold project is on hold because of this bug :/

  • OllieQOllieQ USMember
    edited August 2015

    Hi guys,
    I am also having exactly the same problem with latest Xamarin.iOS 8.10.4.46. Below is my code. The same code on Visual Studio works fine, But in Xamarin its doesn't . Its giving 401 Unauthorised error. Here my issue is bit weird. With same credentials I am calling another GetAsync which is giving correct response. At this point Authentication settings in server for my virtual directory is "Windows Authentication Enabled and Anonymous Authentication Disabled."
    When I change the settings to Anonymous Authentication Enabled and Windows Authentication also Enabled(as previous). GetAsync doesn't work and PostAsync works fine on Xamarin.

    Please find below both my getAsync and postAsync code.

    GetAsync:

    NetworkCredential obj = new NetworkCredential ();
    obj.Domain = “Domain”;

    obj.UserName = “Username”;
    obj.Password = “Password”;
    HttpClientHandler handler = new HttpClientHandler () {
    AllowAutoRedirect = false,
    UseProxy = false,
    Credentials = obj
    };
    System.Net.ServicePointManager.ServerCertificateValidationCallback += (s, ce, ca, p) => true;
    HttpClient httpClient = new HttpClient (handler, true);
    httpClient.Timeout = new TimeSpan (0, 10, 30);
    httpClient.BaseAddress = new Uri ("BaseAddress");
    httpClient.DefaultRequestHeaders.Accept.Add (new MediaTypeWithQualityHeaderValue ("application/json"));
    HttpResponseMessage response = httpClient.GetAsync ("LoginUrl").Result;
    if (response.IsSuccessStatusCode) {
    }

    PostAsync:

    NetworkCredential obj = new NetworkCredential();

    obj.Domain = “Domain”;

    obj.UserName = “Username”;
    obj.Password = “Password”;
    HttpClientHandler handler = new HttpClientHandler()

    {
    AllowAutoRedirect = false,
    UseProxy = false,
    Credentials = obj
    } ;
    string serverDateTime = "null";
    Int32 startRecordCount = 0;
    HttpClient httpClient = new HttpClient(handler, true); 
 System.Net.ServicePointManager.ServerCertificateValidationCallback += (s, ce, ca, p) => true;
 httpClient.Timeout = new TimeSpan(0, 6, 30);
    httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
 HttpResponseMessage response = new HttpResponseMessage();
    StringContent emptyString = new StringContent("");

    response =await httpClient.PostAsync(“https url string”, emptyString).Result;
    

 if (response.IsSuccessStatusCode)

    {
    
 Console.WriteLine(response.IsSuccessStatusCode.ToString());
 
 }

    Error response:

    response {StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.StreamContent, Headers: { Server: Microsoft-IIS/8.5 WWW-Authenticate: Negotiate, NTLM X-Powered-By: ASP.NET Date:

    Also I don't want to downgrade my Xamarin.iOS version.

    Thanks in advance for your support.

  • CraigBowesCraigBowes AUMember

    Hi All
    I am having the same issue with Android / iOS apps.

    I have a REST web service that uses TSL 1.2 and NTLM so I cannot downgrade my Xamarin implementations.

    I have implemented the following and am using ModernHttpClient (so I can consume the TSL 1.2)

    var d = new HttpClient (new NativeMessageHandler () {Credentials= new NetworkCredential ("USERNAME", "PASSWORD", "DOMAIN") });

    Task myTask2 = Task.Run (async() => await d.GetAsync("REST_URL"));
    myTask2.Wait ();

    On iOS I get unauthorised and on Android I get the following error:-

    Javax.Net.Ssl.SSLHandshakeException: Exception of type 'Javax.Net.Ssl.SSLHandshakeException' was thrown.

    If I do the following directly in .NET it all works fine so I know the REST_URL works fine.

    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

    ServicePointManager.Expect100Continue = true;

    ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(AllwaysGoodCertificate);

    var request = (HttpWebRequest)WebRequest.Create("REST_URL");

    request.Method = "GET";
    request.Credentials = new NetworkCredential("USERNAME", "PASSWORD", "DOMAIN");

    using (var response = (HttpWebResponse)request.GetResponse())

    Anybody have any ideas? Xamarin any word on a fix for this? I tried the ServicePointManager settings in IOS and Android but no joy.

    Cheers

    Craig

  • HowardBaylissHowardBayliss USMember ✭✭

    RE: GETs working but POSTs returning 401 Unauthorised when using NTLM authentication, I had the same issue with Xamarin Android 7.0.2.42. After some experimentation I found the following code works for both GET and POST. Hope it helps someone:

    const string scheme = "NTLM";
    
    NetworkCredential credentials = new NetworkCredential(userSettingsService.ADUserName, userSettingsService.ADPassword);
    
    CredentialCache credentialCache = new CredentialCache {{host, port, scheme, credentials}};
    
    var httpHandler = new HttpClientHandler()
     {
        CookieContainer = new CookieContainer(),
        Credentials = credentialCache.GetCredential(host, port, scheme)
    };
    
    _httpClient = new HttpClient(httpHandler)
    {
        Timeout = new TimeSpan(0, 0, 0, 30)
    };
    
  • We use Xamarin Forms PCL and when we POST an image to an aspx page using windows authentication and not tls1.2 its works fine but when the android or ios app is set to tls1.2 we only can upload a small image, <100kb. When the image is larger the handshake fails.
    Does anyone have an idea whats wrong?

Sign In or Register to comment.