63

I am attempting to share data across subdomains using Safari. I would like to use an HTML5 database (specifically localStorage as my data is nothing but key-value pairs). However, it seems as though data stored to domain.com can not be accessed from sub.domain.com (or vice versa). Is there any way to share a single database in this situation?

Sebastian Celis
  • 12,052
  • 6
  • 33
  • 44
  • Possible duplicate of [Persist javascript variables across pages?](https://stackoverflow.com/questions/1981673/persist-javascript-variables-across-pages) –  Apr 16 '18 at 04:10

4 Answers4

13

Update 2016

This library from Zendesk worked for me.

Sample:

Hub

// Config s.t. subdomains can get, but only the root domain can set and del
CrossStorageHub.init([
  {origin: /\.example.com$/,            allow: ['get']},
  {origin: /:\/\/(www\.)?example.com$/, allow: ['get', 'set', 'del']}
]);

Note the $ for matching the end of the string. The regular expression in the above example will match origins such as valid.example.com, but not invalid.example.com.malicious.com.

Client

var storage = new CrossStorageClient('https://store.example.com/hub.html');

storage.onConnect().then(function() {
  return storage.set('newKey', 'foobar');
}).then(function() {
  return storage.get('existingKey', 'newKey');
}).then(function(res) {
  console.log(res.length); // 2
}).catch(function(err) {
  // Handle error
});

Check https://stackoverflow.com/a/39788742/5064633

Community
  • 1
  • 1
super1ha1
  • 549
  • 1
  • 7
  • 15
11

There is simple way to use cross-domain anything, just create simple page that will be included as proxy iframe hosted on domain you try to access, send PostMessage to that iframe and inside iframe you do your LocalStorage database manipulation. Here is a link to article that do this with lcoalStorage. And here is demo that send message to different page in subdomain check the source code, it use iframe and PostMessage.

EDIT: New version of sysend.js library (used by above demo) use BroadcastChannel if browser support it, but still it require Iframe. Recent version also simplify using of Cross-Origin messages, you have html of the iframe in repo, that you can use (or you can use simple html file with single script tag with the lib) and in parent you just need to call one function sysend.proxy('https://example.com'); where example.com need to have proxy.html file (you can also use your own filename and different path).

jcubic
  • 51,975
  • 42
  • 183
  • 323
  • 11
    Is it just me or is that kind of a smelly solution? Sounds kludgey. (No downvote ... just saying). – Rap Sep 16 '14 at 18:36
  • It's not really kludgey, as PostMessage was created for the very purpose of communicating across domains. The issue is keeping/maintaining the IFRAME content on the different hosts. Say you're using a 3rd-party shopping cart solution… It's not always possible to create a target page on their domain for your PostMessage receiving IFRAME. Also, look into the new Channel Messaging, which is bidirectional ([support starts at IE10](http://caniuse.com/#feat=x-doc-messaging,channel-messaging)). – thirdender Dec 16 '15 at 01:10
  • Probably the best solution of this topic! Native and efficient. – Julien Moulin Dec 27 '17 at 19:24
  • This is the best general solution for dissimilar domains. BUT, when you only have subdomains as the OP does, there's a MUCH simpler solution: You still use the proxy iframe to hold the right origin for data access, but rather than postMessage(), you just set `document.domain = superdomain` on both pages, then they can interact *directly*, and on your main page you then just use `iframe.contentWindow.localStorage` instead of `window.localStorage`! I guess it's different enough that I'll post it as a seperate answer... – Doin Aug 26 '20 at 16:55
  • This solution won't work if "Block third party cookies" setting is on in the browser. It is enabled by default in safari – vishesh Apr 17 '21 at 16:45
  • @vishesh do you know any other way to send Cross-Domain message on Safari, it seems that nothing will work, unless you're proxy through the server. – jcubic Apr 17 '21 at 18:06
  • There is no issue in cross domain messaging, issue is getting the message data from local storage in iframes. op wants to specifically read data from local storage. `window.open` might work, but that would require opening and immediately closing the window which might be pretty visible. It also runs the risk of popup blocker – vishesh Apr 18 '21 at 20:17
  • @vishesh note that when the answer was created, the solution worked in Iframe on Safari, and not don't the same as any other answer. It seems that Safari block any way to sending data out inside iframe, maybe just in case if Advertisers will want to create workaround. I think this should only work with cookies and new FLoC. – jcubic Apr 19 '21 at 06:46
5

Google Chrome blocks localStoage access from an iFrame in another domain by default,unless 3rd party cookie is enabled and so does Safari on iPhone...the only solution seems to be opening the parent domain on a different domain and then sending to to the Child via window.postMessage but looks ugly and shifty on phones...

Mad Scientist
  • 320
  • 4
  • 13
0

Yes. This is how:

For sharing between subdomains of a given superdomain (e.g. foo.example.com vs bar.example.com vs example.com), there's a technique you can use in that situation. It can be applied to localStorage, IndexedDB, SharedWorker, BroadcastChannel, etc, all of which offer shared functionality between same-origin pages, but for some reason don't respect any modification to document.domain that would let them use the superdomain as their origin directly.

NOTE: This technique depends on setting document.domain to allow direct communication between iframes on different subdomains. That functionality has now been deprecated. (Although as of April 2021 it continues to work in all major browsers, and will possibly be retained for backwards compatibility).

(1) Pick one "main" domain to for the data to belong to: i.e. either https://foo.example.com or https://bar.example.com or https://example.com will hold your localStorage data. Let's say you pick https://example.com.

(2) Use localStorage normally for that chosen domain's pages.

(3) On all other https://*.example.com pages (the other domains), use javascript to set document.domain = "example.com"; (always the superdomain). Then also create a hidden <iframe>, and navigate it to some page on the chosen https://example.com domain (It doesn't matter what page, as long as you can insert a very little snippet of javascript on there. If you're creating the site, just make an empty page specifically for this purpose. If you're writing an extension or a Greasemonkey-style userscript and so don't have any control over pages on the example.com server, just pick the most lightweight page you can find and insert your script into it. Some kind of "not found" page would probably be fine).

(4) The script on the hidden iframe page need only (a) set document.domain = "example.com";, and (b) notify the parent window when this is done. After that, the parent window can access the iframe window and all its objects without restriction! So the minimal iframe page is something like:

<!doctype html>
<html>
<head>
  <script>
    document.domain = "example.com";
    window.parent.iframeReady();  // function defined & called on parent window
  </script>
</head>
<body></body>
</html>

If writing a userscript, you might not want to add externally-accessible functions such as iframeReady() to your unsafeWindow, so instead a better way to notify the main window userscript might be to use a custom event:

    window.parent.dispatchEvent(new CustomEvent("iframeReady"));

Which you'd detect by adding a listener for the custom "iframeReady" event to your main page's window.

(NOTE: You need to set document.domain = "example.com" even if the iframe's domain is already example.com: Assigning a value to document.domain implicitly sets the origin's port to null, and both ports must match for the iframe and its parent to be considered same-origin. See the note here: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Changing_origin)

(5) Once the hidden iframe has informed its parent window that it's ready, script in the parent window can just use iframe.contentWindow.localStorage, iframe.contentWindow.indexedDB, iframe.contentWindow.BroadcastChannel, iframe.contentWindow.SharedWorker instead of window.localStorage, window.indexedDB, etc. ...and all these objects will be scoped to the chosen https://example.com origin - so they'll have the this same shared origin for all of your pages!

The most awkward part of this technique is that you have to wait for the iframe to load before proceeding. So you can't just blithely start using localStorage in your DOMContentLoaded handler, for example. Also you might want to add some error handling to detect if the hidden iframe fails to load correctly.

Obviously, you should also make sure the hidden iframe is not removed or navigated during the lifetime of your page... OTOH I don't know what the result of that would be, but very likely bad things would happen.

And, a caveat: setting/changing document.domain can be blocked using the Feature-Policy header, in which case this technique will not be usable as described.


However, there is a significantly more-complicated generalization of this technique, that can't be blocked by Feature-Policy, and that also allows entirely unrelated domains to share data, communications, and shared workers (i.e. not just subdomains off a common superdomain). @jcubic already described it in their answer, namely:

The general idea is that, just as above, you create a hidden iframe to provide the correct origin for access; but instead of then just grabbing the iframe window's properties directly, you use script inside the iframe to do all of the work, and you communicate between the iframe and your main window only using postMessage() and addEventListener("message",...).

This works because postMessage() can be used even between different-origin windows. But it's also significantly more complicated because you have to pass everything through some kind of messaging infrastructure that you create between the iframe and the main window, rather than just using the localStorage, IndexedDB, etc. APIs directly in your main window's code.

Doin
  • 6,230
  • 3
  • 31
  • 31
  • This solution won't work if "Block third party cookies" setting is on in the browser. It is enabled by default in safari – vishesh Apr 17 '21 at 16:47
  • @vishesh This answer doesn't involve cookies, as such, however it is likely that the Safari setting is blocking other things as well. It'd be nice to have some more information. **What doesn't work, precisely?** Does setting `document.domain` throw an error? Is access to the iframe from the parent window blocked? Or does the problem only arise when you try to use localStorage, for example? – Doin Apr 18 '21 at 18:57
  • Yes, browsers disable access to local storage just as they do for cookies. This is controlled by the same "Block third party cookies" setting. `document.domain` is deprecated, it might not work in some browsers already. Problem is only when iframe wants to access it's own local storage. – vishesh Apr 18 '21 at 20:21
  • @vihesh Well if the browser is blocking an iframe from accessing even *its own* local storage, there's not much point asking if you can share access to it across subdomains. ...Or is it only iframes, and not top-level windows? Or does it only happen if you change `document.domain`? In any case, I'll readily concede there are a number of things that can interfere with this solution. However, when it does work, it can be a lot simpler then the other `postMessage`-based solutions, since uisng it you get direct access to the localStorage/indexedDB/etc APIs. – Doin Apr 19 '21 at 23:42
  • @vihesh I'm using it successfully in a Greasemonkey script running on Firefox, BTW. – Doin Apr 19 '21 at 23:45
  • @vihesh If the security settings you're talking about are specific to embedded iframes, might you get around it by opening a new window, (instead of using an iframe)? It's ugly in that the new window would be visible to the user, and you'd have to trust them not to close or navigate it away (or you could detect this and re-open it, provided the pop-up blocker doesn't get you)... but it might be a feasible solution in some cases...? – Doin Apr 20 '21 at 00:10