Skip to content

Cookies are not added to cookie container when running on iOS #2168

Description

@h-arshad

When using rest sharp in NET MAUI application development, for the same API call that is supposed to inject a cookie, in Android you will see the cookie but it is missing when running in iOS device.

Code Sample:

async Task TestCookiesAsync(){
var restClient = new RestSharp.RestClient(new RestSharp.RestClientOptions()
{
  BaseUrl = new Uri("https://google.com"),
  CookieContainer = new System.Net.CookieContainer()
});
var request = new RestSharp.RestRequest("", RestSharp.Method.Get);
var response = await restClient.ExecuteAsync(request);
Console.WriteLine(response.Cookies.Count);
}

Expected behaviour:
The response cookie should be added inside the cookie container.

  • OS: iOS 17.2.1 (iPhone 11 )
  • NET MAUI .NET 8
  • Rest Sharp V 110.2.0

Activity

  1. rassilon commented on Mar 14, 2024

    @rassilon

    Based on what I see at https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.InvokeNativeHandler.cs

    This probably has something to do with the differences between the native handlers on iOS vs Android.
    i.e. Xamarin.Android.Net.AndroidMessageHandler, Mono.Android vs System.Net.Http.NSUrlSessionHandler, Microsoft.iOS

    If a redirect is involved, it might be related to #2059, although I wonder why the behavior would differ between mobile platforms. :(

  2. h-arshad commented on Mar 14, 2024

    @h-arshad
    Author

    Thank you for your response and looking into the post.
    In fact I do not suspect the issue comes from those handlers because eventually due to the cookie issue I had with Restsharp, I replaced it with the native HttpClient and it works fine on both platforms.
    I put an example of the working code below.

    private static readonly System.Net.Http.HttpClientHandler HttpClientMessageHandler = new()
     {
         AllowAutoRedirect = false,
         UseCookies = true,
         CookieContainer = new(),
     };
     private static readonly System.Net.Http.HttpClient RestClient = new(HttpClientMessageHandler)
     {
         BaseAddress = new("https://google.com"),
         Timeout = TimeSpan.FromSeconds(60),
         DefaultRequestHeaders =
         {
             { "User-Agent", "mobile"},
             { "Accept", "application/json" }
         }
     };
    

    The one can simply use var response = await RestClient.GetAsync();

  3. alexeyzimarev commented on May 26, 2024

    @alexeyzimarev
    Member

    @h-arshad RestSharp handles cookies without using an external CookieContainer. It might have worked if you have just removed the cookie container from the client setup.

    It could also be that .NET on iOS has some differences concerning getting cookies from the container based on the URL.

    Basically, what RestSharp does: it creates a new cookie container for each request, unless the request itself has a cookie container. But, the container is not used for making a call. Instead, RestSharp adds cookie headers to the request.
    When RestSharp receives a response with SetCookie headers, it adds those values to the cookie container.

    The code looks like this:

    public static void AddCookies(this CookieContainer cookieContainer, Uri uri, IEnumerable<string> cookiesHeader) {
        foreach (var header in cookiesHeader) {
            try {
                cookieContainer.SetCookies(uri, header);
            }
            catch (CookieException) {
                // Do not fail request if we cannot parse a cookie
            }
        }
    }

    You see there that sometimes adding a cookie to the container fails. I am not sure if that's what happens on iOS.

  4. newky2k commented on Jun 4, 2024

    @newky2k

    @h-arshad I have the same issue with RestClient not returning the cookies on MAUI for iOS.

    I was able to resolve it by passing an instance of HttpClient which had the CookieContainer set on its HttpClientHandler (similar to the working code you posted above).

    I think setting it on the handler via RestClientOptions.ConfigureMessageHandler should probably work too, though i haven't tested it.

    The below code would probably also work with IHttpClientFactory, by using that to create the HttpClient instance(and configuring the CookieContainer), but again not tested.

     var handler = new HttpClientHandler { CookieContainer = _cookieManager };
     var client = new HttpClient(handler);
    
     var options = new RestClientOptions()
     {
         BaseUrl = new Uri(Connection.Url),
         Timeout = TimeSpan.FromSeconds(60),
         CookieContainer = _cookieManager,
     };
    
     return new RestClient(client, options);
    

    @alexeyzimarev its feels like the ConfigureHttpMessageHandler method should be applying Options.CookieContainer to the handler, even though you say that it doesn't.

    Setting the CookieContainer on the request didn't fix the problem on iOS MAUI, neither did not setting Options.CookieContainer

        static void ConfigureHttpMessageHandler(HttpClientHandler handler, RestClientOptions options) {
    #if NET
            if (!OperatingSystem.IsBrowser()) {
    #endif
            handler.UseCookies             = false;
            handler.Credentials            = options.Credentials;
            handler.UseDefaultCredentials  = options.UseDefaultCredentials;
            handler.AutomaticDecompression = options.AutomaticDecompression;
            handler.PreAuthenticate        = options.PreAuthenticate;
            if (options.MaxRedirects.HasValue) handler.MaxAutomaticRedirections = options.MaxRedirects.Value;
    
            if (options.RemoteCertificateValidationCallback != null)
                handler.ServerCertificateCustomValidationCallback =
                    (request, cert, chain, errors) => options.RemoteCertificateValidationCallback(request, cert, chain, errors);
    
            if (options.ClientCertificates != null) {
                handler.ClientCertificates.AddRange(options.ClientCertificates);
                handler.ClientCertificateOptions = ClientCertificateOption.Manual;
            }
    
  5. alexeyzimarev commented on Jun 4, 2024

    @alexeyzimarev
    Member

    It's weird that the issue only affects one particular platform. RestSharp doesn't do any kind of magic with cookies, and the reason why it doesn't set the cookie container property of the handler is because otherwise handling cookies will deviate from what's already there when the container isn't used.

    Basically, after making a call, RestSharp tries to find cookie headers, collect the values, and add cookies to the response and to the Options.CookieContainer. It's really strange that it doesn't work on iOS.

                // Parse all the cookies from the response and update the cookie jar with cookies
                if (responseMessage.Headers.TryGetValues(KnownHeaders.SetCookie, out var cookiesHeader)) {
                    // ReSharper disable once PossibleMultipleEnumeration
                    cookieContainer.AddCookies(url, cookiesHeader);
                    // ReSharper disable once PossibleMultipleEnumeration
                    Options.CookieContainer?.AddCookies(url, cookiesHeader);
                }
  6. h-arshad commented on Jul 30, 2024

    @h-arshad
    Author

    @alexeyzimarev Thank you for the suggestion. I tried to make the request on sample code without initializing the cookie container, but it did not work. In fact, after looking into the response object and comparing the Android and iOS, I realized that on iOS the ResstSharp.RestResponse object has 9 headers and Android has 17 headers after the call completes. Strangely enough, on iOS there are no headers for Set-Cookie, it seems the cookie container is not the main problem after all.

  7. alexeyzimarev commented on Jun 2, 2026

    @alexeyzimarev
    Member

    Reviving this with a definitive root-cause analysis after #2385 came in with the cleanest reproducer yet (side-by-side HttpClient vs RestClient diagnostic dump, exact same endpoint, only the iOS RestSharp response is missing Set-Cookie).

    What's actually happening

    On iOS / Mac Catalyst, HttpClient is backed by NSUrlSessionHandler, which wraps Apple's NSURLSession. By default:

    • NSURLSession.HTTPCookieStorage = NSHTTPCookieStorage.SharedStorage (process-wide singleton)
    • NSURLSession.HTTPCookieAcceptPolicy = Always

    When a response with Set-Cookie arrives, NSURLSession consumes the cookie into NSHTTPCookieStorage and strips the header before passing the response up to .NET. By the time RestSharp's ParseResponseCookies does responseMessage.Headers.TryGetValues("Set-Cookie", ...), the header is gone. HttpClientHandler.UseCookies = false (RestSharp's default) does not prevent this on iOS — that flag only controls the managed HttpClientHandler's cookie integration, not NSURLSession's native cookie handling underneath.

    Android doesn't have this problem because AndroidMessageHandler doesn't intercept Set-Cookie the same way. Same code, different native behaviour.

    The reason @h-arshad's direct-HttpClient workaround appears to work is misleading: setting UseCookies = true + a managed CookieContainer on HttpClientHandler re-routes cookies through a path that exposes them via the container. The original header is still stripped — the cookies are just visible somewhere else.

    Why the "obvious" fix is wrong for RestSharp

    The straightforward fix would be: have RestSharp set handler.UseCookies = true + give it a shared CookieContainer, then read cookies from the container after each call. We can't do this. RestSharp deliberately sets UseCookies = false and avoids handing the handler a shared container, because in multi-tenant scenarios (one RestClient instance serving requests for different users/tenants) a shared container pools cookies across every call. Tenant A's authentication cookie ends up on tenant B's request. This was a deliberate design decision and is not negotiable.

    The actual fix

    Suppress NSURLSession's native cookie handling entirely, so the Set-Cookie header passes through to .NET intact. RestSharp's existing per-request ParseResponseCookies then runs as designed, with cookies stored only in the per-request CookieContainer. No shared state anywhere.

    The recipe (iOS / MAUI consumer applies it via ConfigureMessageHandler):

    #if IOS || MACCATALYST
    using Foundation;
    
    var options = new RestClientOptions(baseUrl) {
        ConfigureMessageHandler = _ => {
            var config = NSUrlSessionConfiguration.DefaultSessionConfiguration;
            config.HttpCookieStorage      = null;
            config.HttpCookieAcceptPolicy = NSHttpCookieAcceptPolicy.Never;
            return new NSUrlSessionHandler(config);
        }
    };
    #endif

    What this does:

    • HttpCookieStorage = null → NSURLSession has nowhere to put cookies.
    • HttpCookieAcceptPolicy = Never → NSURLSession doesn't even try to interpret Set-Cookie.
    • Header reaches responseMessage.Headers intact, RestSharp's parser handles it normally.
    • Multi-tenant safety preserved: no shared cookie state at any layer.

    Why this can't go into core RestSharp

    The recipe needs to call NSUrlSessionConfiguration and NSUrlSessionHandler, which only exist in the iOS workload (net9.0-ios etc.). Core RestSharp targets netstandard2.0 and the standard .NET TFMs — pulling iOS workloads in would penalise every non-iOS consumer.

    Follow-ups

    Keeping this issue open as the canonical thread; the actionable work has moved to the two follow-ups. Closing #2385 as duplicate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions