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
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.cookieExpected:
"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?
Most helpful comment
Are there any updates or workarounds? Thx in advance.