Open Dadoeo opened 5 years ago
Any information about how to reproduce the issue?
It's very simple, you can try to open console and do:
document.cookie = "username=John Doe"; document.cookie
Expected: "username=John Doe" But result is: ""
Relevant PR where that was introduced https://github.com/apache/cordova-plugin-wkwebview-engine/pull/27
thank you jcesarmobile.
Why was it not introduced even in Capacitor? How I do you put it manually?
It's very simple, you can try to open console and do:
document.cookie = "username=John Doe"; document.cookie
Expected: "username=John Doe" But result is: ""
I still get the same using WKWebView. On UIWebView this simple javascript works. Is document.cookie
readonly on WKWebView? Any news on this?
Are there any updates or workarounds? Thx in advance.
Hi, same problem here. @EiyuuZack Do you have some workaround?
Hi,
We have the same problem. We are using a third party JS framework which is performing the cookie test as mentioned below.
addTest("cookies", function() { try { t.cookie = "cookietest=1"; var e = t.cookie.indexOf("cookietest=") != -1; return t.cookie = "cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT", e } catch (e) { return !1 }
As document.cookie is always returning "" . We are unable to use the third party JS framework.
I have created a singleton instance of WKProcessPool process and I have set it to webViewConfiguration.processPool = WKProcessPool().This didn't solve my issue as document.cookie is returning "".
Please let me know if there is any workaround for the problem.
Many Thanks
@shivatangirala were you ever able to find an answer for this? Currently experiencing the same problem
Currently the WKWebView does not accept/store/send XHR cookies. I believe this is currently an unsolved issue over in Cordova land as well.
I ported over my Cordova app which was using https://github.com/oracle/cordova-plugin-wkwebview-file-xhr and it works here in Capacitor as well.
Is this tagged as low priority because it's out of scope for capacitor? This bug makes user sessions with cookies impossible, so the app I'm working on is unable to support persistent authentication, which is essentially the foundation of the whole app.
The wkwebview file xhr plugin is what I’m using for the time being until some one figures this out.
@alexsasharegan for now it seems like this approach is the easiest if your app is working against one host: https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/22#issuecomment-579332332.
I also do not understand how this issue could possibly be low priority. Is it because wkwebview is outside the scope/control of Capacitor? This problem forces to to either adopt the solution linked above, limiting you to one XHR host, or re-architecting your app to use authorization
headers. I didn't have luck with the oracle plugin mentioned by @steel, but I would give it another shot if necessary given that some people report success.
Hi all, will take a look at this as part of #2495
@mlynch, the original issue is about document.cookie
not working (or returning ""
).
I don't think the HTTP plugin will be able to solve that.
I tested in the past (and again today) and the common process pool doesn't fix the issue on Capacitor, nor setting the cookies in the native side, they get set, but still not accessible on document.cookie
.
I think it's related to the apps being loaded from a custom scheme (capacitor://
). I've tested on cordova-ios 6 (dev version) and it has the same problem too since it also uses a custom scheme now instead of loading from file://
document.cookie
works fine if I load an external website.
@jcesarmobile makes sense. This won't solve that problem but provides an easy way to get system wide cookies using the native Cookie management APIs which I think solves this
Any solutions ? It is working in android without any issues.
I have do have some ideas and code on how to fix this annoying issue.
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: https://github.com/ionic-team/capacitor/issues/3012
iOS cookies however... Well, they are something special.
Let's assume your main backend is running on https://subdomain.example.com
First of all you will have to add the following to your capacitor.config.json
"server": {
"hostname": "subdomain.example.com"
}
mind you that this config should only be applied for ios
Then you should create a file which will handle the cookies for you. Let's call it CookiesPlugin.js
import '@capacitor-community/http';
import { Capacitor, Plugins } from '@capacitor/core';
const url = () => {
return window.location.origin;
};
export const setCookie = (key, value) => {
const { Http, CookieManagerPlugin } = Plugins;
try {
const parsedValue = JSON.stringify(value);
value = parsedValue;
} catch (e) {
console.error(e);
}
if (Capacitor.getPlatform() === 'ios') {
return CookieManagerPlugin.setCookie({
url: window.location.host,
key,
value,
});
}
return Http.setCookie({
url: url(),
key,
value,
});
};
export const hasCookie = async (key) => {
const { Http } = Plugins;
const ret = await Http.getCookies({
url: url(),
key,
});
return ret?.value?.some((cookie) => key == cookie.key);
};
export const getCookie = async (key) => {
const { Http } = Plugins;
const ret = await Http.getCookies({
url: url(),
});
const value = ret?.value?.find((cookie) => key == cookie.key)?.value;
try {
const parsedValue = JSON.parse(value);
return parsedValue;
} catch (e) {
console.error(e);
return value;
}
};
export const deleteCookie = (key) => {
const { Http } = Plugins;
return Http.deleteCookie({
url: url(),
key,
});
};
As you can see, it mainly utilizes the https://github.com/capacitor-community/http plugin.
However their setCookie
method is not fully working on iOS. So I made my own:
Add CookieManagerPlugin.swift
:
import Capacitor
import Branch
@objc(CookieManagerPlugin)
public class CookieManagerPlugin: CAPPlugin {
@objc public func setCookie(_ call: CAPPluginCall) {
guard let key = call.getString("key") else {
return call.reject("Must provide key")
}
guard let value = call.getString("value") else {
return call.reject("Must provide value")
}
guard let urlString = call.getString("url") else {
return call.reject("Must provide URL")
}
guard let url = URL(string: urlString) else {
return call.reject("Invalid URL")
}
let jar = HTTPCookieStorage.shared
let cookieProperties: [HTTPCookiePropertyKey : Any] = [.name : key,
.value : value,
.domain : urlString,
.originURL : urlString,
.path : "/",
.version : "0",
.expires : Date().addingTimeInterval(2629743)
]
if let cookie = HTTPCookie(properties: cookieProperties) {
jar.setCookie(cookie)
}
call.resolve()
}
}
together with CookieManagerPlugin.m
:
#import <Capacitor/Capacitor.h>
CAP_PLUGIN(CookieManagerPlugin, "CookieManagerPlugin",
CAP_PLUGIN_METHOD(setCookie, CAPPluginReturnPromise);
)
Both client and server side cookies will now fully work.
BUT, you will have to get and set them through the just created CookiesPlugin.js
. For example:
import { setCookie, getCookie } from 'CookiesPlugin.js'
async () => {
await setCookie('foo', 'bar');
const result = await getCookie('foo');
console.log(result); // outputs 'bar'
}
This means, that some third-party stuff will probably not work. See for example https://github.com/ionic-team/capacitor/issues/1433 where Google Tag Manager (GTM) is not working. This is because GTM tries to set and get cookies by utilizing the document.cookies
. But as said before it can only be done (on iOS at least) by using our custom CookiesPlugin.js
. Of course all third-party plugins (like GTM) will not use this.
So another problem we need to tackle. I came up with an idea. It is partly working already, but Capacitor its core needs to be changed for this as well.
So what's the solution?
Apparently it's possible to proxy the document.cookie
. See this answer on Stack Overflow: https://stackoverflow.com/a/33064438/8634342
This makes it possible to do something like this:
Object.defineProperty(document, 'cookie', {
set: function (val) {
const values = val.split(/=(.+)/);
if (values && values.length > 1) {
setCookie(values[0], values[1]);
}
},
});
What this does is the following: it replaces the browsers native setter for document.cookie
with a custom one that utilizes the CookiesPlugin.js
. So now every plugin ever that uses document.cookie
to set cookies, will just work fine.
However, as I said before, there is still one catch: document.cookie
will still return ""
. So writing cookies is possible for third parties, but reading them not. That can still cause problems. This can be solved, but capacitor does not (yet) allow for synchronous calls from JavaScript to Native code. That leaves us with the following issue:
It would be possible to do something like this:
Object.defineProperty(document, 'cookie', {
get: function (val) {
const cookies = await getCookies();
// parse the cookies, so they conform with the native document.cookie format
return cookies;
},
});
But that await
is not possible there. Hence, we need a synchronous function. So if and when Capacitor allows for synchronous features, as far as I am aware, every issue is tackled and we can have fully working cookies on both Android and iOS
As it was said above, if you have issues that cookies are not attached to requests for ios because they are made to some other domain (not localhost), just use this advice:
https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/22#issuecomment-579332332
But it works only for one domain, probably that one which you own, not third-party ones
And you also can get errors with some service like Stripe which require https to be used
@jcesarmobile
Can you please take a look at this issue and update us about the priority and effort you guys are willing to put into this?
@tafelnl I'm confused how this can work for server-side httponly cookies. I have no way of accessing the cookies from JS' side so it's impossible to manually get and set them.
Or am I misunderstanding what this is for?
@creativiii The long part about hacking into the client code to make the cookies work in the client does indeed not solve server side cookie problems in iOS. But that is where the next code part comes in:
"server": {
"hostname": "subdomain.example.com"
}
This takes care of at least accepting cookies from subdomain.example.com
. For other domains your are out of luck, so choose wisely.
@creativiii The long part about hacking into the client code to make the cookies work in the client does indeed not solve server side cookie problems in iOS. But that is where the next code part comes in:
"server": { "hostname": "subdomain.example.com" }
This takes care of at least accepting cookies from
subdomain.example.com
. For other domains your are out of luck, so choose wisely.
See that worked just fine for me, however as soon as I close the app all my cookies are deleted.
And this is on iOS I suppose?
iOS only so far, mostly because I haven't had the chance to test Android yet.
It seems like Cordova users are experiencing similar issues. Check this plugin: https://github.com/CWBudde/cordova-plugin-wkwebview-inject-cookie. It can be that it overlaps with my client side solution mentioned above. That would also explain why I am not experiencing your problem in my app.
It seems like Cordova users are experiencing similar issues. Check this plugin: https://github.com/CWBudde/cordova-plugin-wkwebview-inject-cookie. It can be that it overlaps with my client side solution mentioned above. That would also explain why I am not experiencing your problem in my app.
This seems to be the what I need, unfortunately it just crashes my application as soon as it's loaded. Thank you for the help anyways, I'll just revert back to non-httponly for the apps.
By the way, if the issue is WebKit related then it probably coming soon for the Chromium as well as Google also is going to disable third party cookies
@loburets Good point! Client side cookies will definitely still work in Chrome, but server side cookies will be tricky indeed. I am not sure how Google intents to handle server side cookies on http://localhost
. But yes, this is something to keep in mind indeed!
@tafelnl Just wondering, do you have any idea how to install that plugin on Capacitor? I found this explanation for Cordova, but I'm really unclear how to:
plugins.xml
(which I think is a Cordova-only option)Plugins made for Cordova usually have a wrapper to make it work on Capacitor really easily. This is a list of already supported Cordova plugins: https://ionicframework.com/docs/native/ . It looks like that plugin is not in there. So you should find a way to use the plugin yourself. I do not have enough time on my hands to help you with that, I am sorry.
@tafelnl Would you be able to send a PR to the HTTP plugin with your fix for iOS cookies?
@thomasvidas Is there any update on this issue? My app's authentication is based on session cookies and it was working on an old version of Xcode (11.7) but after new updates even on old version when the app first loads the cookies are not being set but closing and opening the app fix it. On XCode 12 it does not set the cookies at all. We've tried the capacitor http plugin and it does not work. Is there any version of ionic & Capacitor that works fine in iOS? Also, what versions of ionic cli and Capacitor do you suggest to use? This issue is preventing me from publishing my app and it's very urgent. Any help is apricated.
I am also experiencing a similar problem. In my case I have an iframe in which I use a web address that needs to save cookies on the page itself, but they are not being saved
Same here. I'm using the capacitor and my iframe is pointing to mydomain.com and I can't get access to my api's cookies api.mydomain.com. Any updates how to solve this problem?
Failed attempts:
"server": {"hostname": "api.mydomain.com"}
in capacitor.config.json.cordova-plugin-wkwebview-inject-cookie
plugin. Third party cookies are not supported by Safari anymore. Chrome will follow as of 2022. (see here and here). So iframes from a third party (different url than the url of the frontend) and server side cookies from at third party are expected to not work.
For first party cookies, check out my answer above.
However, I think it is important that Capacitor takes a stance in how developers should handle authentication (@dwieeb @mlynch). Because on a 'normal' website/webapp, this would not form a problem, since you could just use a first party cookie. But on capacitor this is not possible. So I think the Capacitor team should formulate a preferred method to authenticate users. This way everyone uses the same prescribed best practice.
Especially now that cookies that still work on chromium (http://localhost
can get cookies from https://example.com
when the right flags are set) are being phased out by Google (!) this is a very important issue.
I think the preferred method is Ionic Identity Vault and deep pockets to pay for ionic enterprise.
As tafelnl said earlier, setting hostname to be the same as the server works for saving cookies on IOS. I haven't tried this on android yet since chrome still supports 3rd party cookies. I could probably do the same thing but I found that when the api server and app hostname matched android would try and route api calls to itself. I'm guessing there is a config to sort that out. I've been deferring figuring that out for a bit.
Third party cookies are not supported by Safari anymore. Chrome will follow as of 2022. (see here and here). So iframes from a third party (different url than the url of the frontend) and server side cookies from at third party are expected to not work.
For first party cookies, check out my answer above.
However, I think it is important that Capacitor takes a stance in how developers should handle authentication (@dwieeb @mlynch). Because on a 'normal' website/webapp, this would not form a problem, since you could just use a first party cookie. But on capacitor this is not possible. So I think the Capacitor team should formulate a preferred method to authenticate users. This way everyone uses the same prescribed best practice.
Especially now that cookies that still work on chromium (
http://localhost
can get cookies fromhttps://example.com
when the right flags are set) are being phased out by Google (!) this is a very important issue.
I totally agree on you with the stance on how this should be handled. The lack of information is terrible. This is so fundamental for application development.
I tested with setting the host name and it works fine. But I have a variable in my subdomain which I can't set in capacitor.config json file.
@tafelnl @maartenmensink so do we conclude that authentication with session cookies won't work on iOS period and they should advise developers to use token based authentication? Because I read in an Ionic doc that setting host name introduces some limitations:
@mlynch what do you suggest?
I've been struggling with cookies on iOS for days in the past... I don't get why this is so difficult for Apple to maintain something working across their versions (https://github.com/oauth2-proxy/oauth2-proxy/issues/830#issuecomment-706068990).
As mentioned in my post, I inject a cookie in swift (coming from a deeplink) and for no reason it was not working recently... After seeing some Capacitor issues I tried to set thehostname
as my top domain level and it works well.
"hostname": "subdomain.domain.com",
Note: I tried at first to use the "wildcard" as for cookie (.subdomain.domain.com
) but it was not working. Just removing the starting dot works.
At least I'm now able to use it again... even if I'm not totally sure what has changed since the last build of my app.
I tested with setting the host name and it works fine. But I have a variable in my subdomain which I can't set in capacitor.config json file.
@tafelnl @maartenmensink so do we conclude that authentication with session cookies won't work on iOS period and they should advise developers to use token based authentication? Because I read in an Ionic doc that setting host name introduces some limitations:
@mlynch what do you suggest?
We only do native http calls. That was the quickest way to get everything up and running in our usecase. We wrote a CookieManager that stores incoming cookies from the native http call in a secure storage. You will need to build some kind of way to invalidate the cookies.
Ofcourse this isn't a solution for OAuth web client but i think you can handle oauth login as a native client.
@maartenmensink I think your way of authentication is exactly what I had in mind when trying to think of a solution. Correct me if I a wrong:
fetch
or XMLHttpRequest
When you want to authenticate someone (by means of a cookie), you would do the following:
@maartenmensink I think your way of authentication is exactly what I had in mind when trying to think of a solution. Correct me if I a wrong:
- First of all: all 'regular' API calls just go through
fetch
orXMLHttpRequest
When you want to authenticate someone (by means of a cookie), you would do the following:
- Make your API call through a native HTTP library (this one for example)
- Intercept the incoming cookie headers
- Ideally store those cookies in a way, so that they become available in the WebView
- From now on you can go ahead with your regular API calls and attach the authentication cookie to it.
I think the biggest issue here is making the cookies available to the WebView. I think that there is only one option to get that working is by sharing the CookieJar between the http lib and WebView instance but I dont know if that is even possible.
As passing cookies through in any other way would violate the http only cookies issue.
@maartenmensink Well, with my solution the cookies are actually being shared between Jar and WebView. But the problem is that when you set a cookie for example.com
but your client is running on http://localhost
, the cookie is being treated as a third-party one (and it is of course).
Although the cookie is now really present (when you would navigate to example.com
in the WebView you would see the cookie being present) , it will not be sent when making a XHR request from localhost to example.com (see also this issue on chromium with an explanation).
So we should store the cookie somewhere else (secure vault for example) and attach it to every request. But I think it is important that someone with a lot of security knowledge, states some guidelines on how to handle this. Otherwise every Capacitor developer ends up writing their own (maybe insecure) implementation.
I opened up an issue at chromium: https://bugs.chromium.org/p/chromium/issues/detail?id=1178836
I hope to achieve that they make an exception for localhost
, so the cookies will keep on working on chrome. When we have achieved that, we could maybe pursue webkit to do the same.
Please give it a star, so the dev team are able to see the urgency behind this.
I tested with setting the host name and it works fine. But I have a variable in my subdomain which I can't set in capacitor.config json file.
@tafelnl @maartenmensink so do we conclude that authentication with session cookies won't work on iOS period and they should advise developers to use token based authentication? Because I read in an Ionic doc that setting host name introduces some limitations:
@mlynch what do you suggest?
An update on my issues: I was not setting the hostname because I have a variable sub domain and I couldn't use the variable in the capacitor config json file. Then I tried the last part of my domain only and it worked fine. My entire URL is like this xxx.yyy.zzz.com and only setting hostname to zzz.com, resolved my issue. However, I'm still not sure what limitation this can introduce as opposed to not setting host name and use localhost. I can't find any document on this.
@dwieeb Would you (and your team) be willing to stress out the importance of excluding localhost
from third-party cookie policy to the Chromium (and in the near future WebKit) team? I think that would help a lot, since you guys are a serious party in the development world and therefore definitely have some lobbying power.
If Chromium and WebKit decide to make this exception, that would solve almost all (if not all) of our problems regarding cookies and authentication.
As mentioned above I already filed an issue for Chromium here: https://bugs.chromium.org/p/chromium/issues/detail?id=1178836
As we have pointed here https://github.com/ionic-team/cordova-plugin-ionic-webview/issues/22
Setting hostname
to a real domain value seems to cause errors on android for ajax requests to that domain (we initially thought that was related to using allowNavigation
key too but it doesn't seem to be the problem).
They fail because shouldInterceptRequest
is involved and remote request is not sent.
We're still aware of this issue and working on coming up with a long term solution. The core issue is that document.cookie
never works on iOS. We would need to manually patch it in which unfortunately isn't possible at the moment since communication between the web and native layers are asynchronous. We are exploring options to make the code synchronous in which case we could manually patch document.cookie
but nothing concrete yet.
In the mean time, there are workarounds that solve the majority of problems people are facing!
The @capacitor-community/http
plugin 0.x versions work with Capacitor 2.x and the new 1.x versions (under @capacitor-community/http@next
on npm) supports the current Capacitor 3 RC. It doesn't completely solve the issues with cookies, but if you need to send cookies via HTTP request, should be able to handle most use cases. You can use it as follows
import { Http } from '@capacitor-community/http'
await Http.setCookie({
url: 'http://www.example.com',
key: 'key',
value: 'value',
});
await Http.get({ url: 'http://example.com' }); // <-- cookies attached to request
You may need to add the server variable to your capacitor.config
file as follows to ensure your cookies are treated as first-party cookies.
"server": {
"hostname": "subdomain.example.com"
}
Safari does not support them, and Chromium will not support them in 2022. Third party cookies are going the way of the dodo and since the underlying webviews will auto-block third party cookies, Capacitor is somewhat limited in how it could potentially support third party cookies anyways.
Both LocalStorage and IndexedDB are available on all of the platforms that Capacitor supports. If you are using cookies to store values and not using them for http requests, this is an excellent alternative! There's also @capacitor/storage and @capacitor/filesystem for longer term solutions if you need to write a file or save user data longer than 7 days. I'd recommend @capacitor/storage for most use cases! Remember, while you're using web tech, you're still making an app! Take advantage of it!
You can use @capacitor/storage
in Cap 3 as follows, it's easy!
import { Storage } from '@capacitor/storage';
await Storage.set({
key: 'name',
value: 'Thomas',
});
Rather than using session cookies, if you have the ability to use token based authentication; do that! And then use one of the storage options I talked about above 😄
Tl;dr: This issue isn't dead, its a hard one. In the mean time...
Hi,
in Ios platform, Capacitor use the vkWebView, which has the well known problem of cookies shared between the domains, but no solution has been implemented.
In "cordova-plugin-wkwebview-engine", the ploblem is solved using "WKProcessPool":
"This plugin creates a shared WKProcessPool which ensures the cookie sharing happens correctly across WKWebView instances. CDVWKProcessPoolFactory class can be used to obtain the shared WKProcessPool instance if app creates WKWebView outside of this plugin."
Is possible add this on Capacitor? Or are there any workaround for do this manually?
Regards