Skip to content
This repository has been archived by the owner on Sep 26, 2022. It is now read-only.

Can't get cookies on iOS #47

Closed
simonluebker opened this issue Aug 27, 2020 · 8 comments
Closed

Can't get cookies on iOS #47

simonluebker opened this issue Aug 27, 2020 · 8 comments
Labels
On Hold Not working on at this time

Comments

@simonluebker
Copy link

Question

I'm here because of the wkwebview issue of getting and setting cookies and hoped that this plugin will resolve this problems.
So i tried the Http.getCookie({url: ....}) method but the result is every time an empty array.

The first request to an IDP is done via an other framework so not with the Http Plugin. The IDP is setting the cookie. But it is not seen in Developer Console with Chrome when debugging on mobile. The cookie is seen when hosting the app in real browser but there also the plugins Http.GetCookie method returns an empty array.

So my Question is how to receive Cookies from other websites and send them with request to the same website?

I hope you can help me

@mlynch
Copy link
Contributor

mlynch commented Sep 17, 2020

Just making sure you're awaiting the promise?

It's possible the cookies are "sandboxed" somehow. Would need to dig in but if anyone else has ideas please chime in

@smoosh911
Copy link

smoosh911 commented Dec 9, 2020

I have this same issue.

Here are my dependencies:
"@capacitor-community/http": "^0.2.1",
"@capacitor/core": "^2.1.0",
"@capacitor/ios": "^2.4.4",

The code I'm using:

const cook1 = await Http.setCookie({
  url: '/',
  key: '_ga_F1R0Z1HBV5',
  value: 'GS1.1.1607469233.18.1.1607469258.0'
});

const cook2 = await Http.setCookie({
  url: '/',
  key: '_ga',
  value: 'GA1.1.172759051.1550597580'
});

const cook3 = await Http.setCookie({
  url: '/',
  key: 'dogy',
  value: 'dog world'
});

const getCookies = async () => {
  const ret = await Http.getCookies({
    url: '/'
  });
  console.log('Got cookies', ret);
  return JSON.stringify(ret.value);
};

const result = await getCookies();

console.log('getCookies :>> ', result);

From what I can tell there are no errors, it simply doesn't work on the iOS device and on the iOS simulator. This code does work on Web.

@imhoffd
Copy link
Contributor

imhoffd commented Dec 9, 2020

@tafelnl Is this an issue your implementation would fix?

@tafelnl
Copy link
Member

tafelnl commented Dec 10, 2020

Well, yes and no. It's complicated and I am not a native developer. But I will do my best to explain:

Android vs iOS

First of all we have to distinguish between Android and iOS.

On Android everything works just fine, with only one caveat:

Android cookies will work just fine as long as you have your webview server running on http://localhost and you have your backend hosted on a secure (SSL) endpoint. There is a little bug with the persisting of cookies when you close the app quickly after booting up. But there is a simple fix available. See: #3012

On iOS, cookies do not work at all or not correctly (dependent on your setup and use).

Therefore, the rest of this comment considers iOS.

Client vs Server side

Next we need to distinguish between client side and server side cookies.

In an ideal world cookies would work as follows:

Client side cookies can be set by: document.cookie = 'somecookie=yummy;'. And you can get them by const cookies = document.cookie.

Server side cookies will be set by making use of Headers:
E.g.: Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly

If you use the flag HttpOnly;, you will not be able to get and use them from JavaScript. But otherwise you will also be able to get these cookies by running const cookies = document.cookie.

Now, as we know, cookies unfortunately do not behave like this on iOS. This is probably because of the fact that a custom scheme is used (capacitor://).

So the next step is to discover how they exactly do behave.

When do Client side cookies work on iOS?

The answer is quite simple: never.

It's easy to reproduce this. Try running document.cookie = 'somecookie=yummy;'; const cookies = document.cookie;. And you will see that cookies has a value of "". (see: ionic-team/capacitor#1373 (comment))

When do Server side cookies work on iOS?

When you have your app configured as follows:

  "server": {
    "url": "http://example.com"
  }

It will work exactly the same as if you would visit http://example.com in Safari on your Mac or iPhone.

But since such a configuration is only for development purposes you config would probably look like this:

  "server": {}

This configuration makes your app launch from capacitor://localhost. But the URL's you will be calling from for example the fetch API, will probably be just a 'normal' URL (e.g.: https://example.com). Now, normally (like on Android with their http://localhost), this wouldn't be a problem. Just as long as you set the right flags (Secure and SameSite=None) (see also: ionic-team/capacitor#3418 (comment)).

But (understandably) iOS does not consider capacitor://localhost to be a Secure Context. And it is not likely that they will accept this sometime in the future. So therefore all URLs called from within that capacitor://localhost will not be able to set cookies.

What is remarkable is that iframes that are secure (e.g.: https://tr.snapchat.com for tracking) within the webview also seem to be considered insecure by iOS. Because they are also not able to retrieve cookies through a fetch request.

So how to fix all of this?

How to fix the Client side cookies on iOS?

This is pretty straight forward. As explained in my other comment (ionic-team/capacitor#1373 (comment)) we will have to proxy the document.cookie. This way we can override the normal behaviour of the setter and getter. The only thing that is needed for that fix to be implemented is a synchronous way to call native API's. See my feature request here: ionic-team/capacitor#3675.

So not to worry about client side cookies, they can be fixed pretty easily.

How to fix the Server side cookies on iOS?

On to Server side cookies.

Again I am not a native developer. But I did some digging and came up with a few ideas.

The most straight forward (and also the worst) solution is by using a config similar to the following:

  "server": {
    "hostname": "subdomain.example.com"
  }

With subdomain.example.com being your main domain used for API calls and Authentication. Somehow this tricks the WKWebView into thinking that it is running on https://subdomain.example.com (as far as I understand at least). This way cookies from https://subdomain.example.com are accepted. But all other cookies from other domains are still ignored.

This is of course not a viable solution in any way. It has many defects and use cases where this is not a working fix.

So what to do now?

I've got some (still vague) ideas about how to fix this:

A possible solution would be to add afterhooks to both the XMLHttpRequest and fetch API's. In those afterhooks you then read out all the cookie headers and finally set them manually by calling (the fixed) document.cookie = . This is almost perfect (besides the fact that it is hacky in my opinion). Only cookies with the HttpOnly; flag set will not be fixed with this (as JavaScript does not have access to them).

Another solution would be to add beforehooks to both the XMLHttpRequest and fetch API's. In those beforehooks you redirect all them requests to the Capacitor.Plugins.Http API. This would be very nice, but also very tricky. There are a lot of variables to be considered. And the Plugins.Http API might not be able to support all of them.

Last solution I had in mind, was to intercept all XMLHttpRequest and fetch calls within the WKWebView. But I'm not sure if that would be possible. If it's possible, I think this would by far be the best solution; no weird hacky proxies for the JavaScript API's; just one simple interceptor in the native Swift code to read out all the cookie headers and set them in a way that they can be read out again by JavaScript.

Also worth noting is that the Cordova community is bothered by the same issues. A lot of information can be found there. See for example this library: https://github.com/CWBudde/cordova-plugin-wkwebview-inject-cookie. Although I do not understand what problem this library is trying to solve in what way exactly. It might be worth looking at.

@thomasvidas thomasvidas changed the title Can't get cookies Can't get cookies on iOS Jan 19, 2021
@maartenmensink
Copy link

Is there any update on this topic. Or is Set-Cookie a lost cause for iOs?

@thomasvidas
Copy link
Contributor

I'm closing this issue for now because this is mainly an issue with Capacitor Core. The team is aware and actively working on solutions, but for now it is outside the scope of this plugin due to limitations set by Apple with Third Party cookies

@thomasvidas thomasvidas added the On Hold Not working on at this time label Feb 12, 2021
@thomasvidas
Copy link
Contributor

On second thought, I'll leave it open, but label it appropriately

@thomasvidas thomasvidas reopened this Feb 12, 2021
@thomasvidas
Copy link
Contributor

This is fixed as best as it can be in this plugin. The issue is open in core, but with the recent 1.0 release of this plugin you can specify the domains you need cookies from and it may fit your use case.

Alternatively you can change the hostname in your capacitor config to allow your cookies to become first party cookies, but as we state in the Capacitor config dogs, it is recommended to keep that value as localhost as it allows the use of Web APIs that would otherwise require a secure context such as navigator.geolocation and MediaDevices.getUserMedia.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
On Hold Not working on at this time
Projects
None yet
Development

No branches or pull requests

7 participants