As you may know, LocalStorage is domain based. You can’t read or write from localstorage that’s on different domain, even if that’s subdomain. But there is iframe trick that you can use to store data from domain to it’s subdomain.
Basically to have Cross-Domain LocalStorage, you create an iframe that’s hosted on your other domain, then you send PostMessage to that iframe and inside iframe you set that value of localStorage.
Here is the code:
iFrame
window.onmessage = function(e) { if (e.origin !== "http://example.com") { return; } var payload = JSON.parse(e.data); localStorage.setItem(payload.key, JSON.stringify(payload.data)); };
main file
window.onload = function() { var win = document.getElementsByTagName('iframe')[0].contentWindow; var obj = { name: "Jack" }; win.postMessage(JSON.stringify({key: 'storage', data: obj}), "*"); }; <a href="http://other.example.com/iframe.html">http://other.example.com/iframe.html</a>
If you want to save and load value from LocalStorage, you can PostMessage with method key that will indicate what action it need to take. You can also send message back from iframe to it’s parent with data read from localStorage.
iframe
document.domain = "example.com"; window.onmessage = function(e) { if (e.origin !== "http://example.com") { return; } var payload = JSON.parse(e.data); switch(payload.method) { case 'set': localStorage.setItem(payload.key, JSON.stringify(payload.data)); break; case 'get': var parent = window.parent; var data = localStorage.getItem(payload.key); parent.postMessage(data, "*"); break; case 'remove': localStorage.removeItem(payload.key); break; } };
Your main page on example.com
window.onload = function() { var iframe = document.getElementsByTagName('iframe')[0]; var win; // some browser (don't remember which one) throw exception when you try to access // contentWindow for the first time, it work when you do that second time try { win = iframe.contentWindow; } catch(e) { win = iframe.contentWindow; } var obj = { name: "Jack" }; // save obj in subdomain localStorage win.postMessage(JSON.stringify({key: 'storage', method: "set", data: obj}), "*"); window.onmessage = function(e) { if (e.origin != "http://other.example.com") { return; } // this will log "Jack" console.log(JSON.parse(e.data).name); }; // load previously saved data win.postMessage(JSON.stringify({key: 'storage', method: "get"}), "*"); };
Here is a demo that use my sysend library to send messages to other tab in same browser (the library use localStorage for this). It use iframe proxy to send message from jcubic.pl to terminal.jcubic.pl and vice versa. Iframe is hosted on both domains. (everything is done by the library, you only need to include the file from repo on other.example.com domain and call sysend.proxy('http://other.example.com')
).
The only caveat is that in Firefox, it seems that you need to enable CORS on Iframe. If you use apache server you can enable it in .htaccess
file with this code:
Header set Access-Control-Allow-Origin "*" Header set Access-Control-Allow-Methods: "GET"
it will enable CORS for all files, if you want to enable CORS for a single file, this should work:
<Files "iframe.html"> Header set Access-Control-Allow-Origin "*" Header set Access-Control-Allow-Methods: "GET" </Files>
And that’s it you now can have Cross-Domain LocalStorage.
Hi Jakub: thanks for the tutorial… it’s not very clear how the iframes should be set up. You have two code snippets labeled “iframe”. Are you using two iframes or just one?
Second iframe is extension of the first one to allow to set, get and remove data from localStorage.
This is by far the best and most useful tutorial I’ve seen regarding HTML5 local storage usage. Thank you so much for sharing this knowledge with us!
meh
This isn’t that clear to be honest. You need to identify which is you parent and which is your sub-domain
You don’t you can host iframe on both domains, on domain A you include iframe from domain B and on domain B you include iframe from domain A. That way you can have two directional connection, you send data from A to B and from B to A.
If you have one iframe you always send message from host page to domain inside iframe.
I’m setting localstorage from a webpage, and then trying to access the same from an iframe but it shows me null. Iframe and parent window are not from the same domain. Is there any cross domain solution for this?
You need to save data to localStorage inside iframe that’s on sub domain. That’s the only way to have cross-domain localStorage, you simlpy send message to the iframe to save data.
hey,
this seems great but in this case sub domain url is limited to just one. What if i have dynamic sub domains. How can i set dynamic sub domain url inside main file’s iframe’s src.
On note – meanwhile this is a smart trick to set the cross-domain local storage between domain-subdomain, keep in mind that due to the security policy on iOS devices the local storage will be associated to the compound of (host site, iframe site). It means that when you open on iOS the iframe content (domain.com), the local storage will be empty for it – but when you open it within an iframe like you set it, the local storage will be there.
Anyway to solve this problem on ios ?
If you realy need to make it work on iOS, you can use webscokets or firebase (that also use websockets) on both domain and subdomation and send data that way. But if you use websockets/firebase you will not need to use iframe anymore even for non iOS.
Nice job thanks.
Thank you so much! That was exactly what I needed at the moment
For iOS when you talk about firebase. How exactly? storing the token in firebase? rather than the local storage and sharing it from there?
Not sure if I’ve understood correctly but you should not store login tokens in Firebase since AFAIK you make db public (or part of it) to make it work from browser, I’m not sure if you can store token that can be accessed only by single user.
Firebase can be used to have cross-domain storage, or if you want cross-domain events, just create item and delete it immediately.
For login you should use Firebase auth. Not sure how this will work if you want to authenticate Cross-Domain.
Thank you so much. If I need to get data from domain, do I have to wait for event in window.onmessage, or is there any way to get return value through callback?
This solution is is one way communication, but it should not be hard to create double channel and and use promises to get the result back.
The way it should be implement is like RPC, you send generated id and wait for same id back in other message.
I may write POC with this when I have time. Using sysend library.
Looking forward to that as well 🙂 Since it is an asynchronic call I am having problem picking up the ‘return value’. After the postmessage in domain a I having the script on domain b, where it actually reads the local storage put the value in that div. But by the time domain b is done with that, the code in domain a is already ahead and did not find the value since it was not there yet. Even a time out did not solve it. So I tried an even listener on domain a and have domain b do a postmessage to send the value. But no luck sofar in getting that synchronic, so still my code in domain a is already a few steps further
Nice article, thanks. Does this only work on subdomains or does it work on unrelated domains as well?
It works with any domain if you can upload iframe to that domain.
If you’re only trying to share storage between subdomains (as opposed to entirely different domains), and you’re willing to risk setting the now-deprecated document.domain, then you don’t even need postMessage() and the infrastructure around it: Set document.domain = in all windows & iframes, and then you can access the window variables of the iframe from the main window… including its localStorage. This is even more useful if you want to use indexedDB instead, which has an API that’d be much harder to interact with via postMessage(). (Also SharedWorker, BroadcastChannel, etc – you can share all of them between subdomains this way). (See: https://stackoverflow.com/a/63602446/999120)
Will this work on IOS devices
No, This doesn’t work on Safari. Because Apple developers decided that this is a security problem.