How to achieve cross-domain localStorage
LocalStorage is one way to store data on the WEB. It is stored in the browser on the client computer.
The data is saved per domain, it means that only pages with the same domain can access and modify it. Pages from other domains can’t access the data of each other. This is a browser storage security issue, and in fact none of LocalStorage/SessionStorage/IndexedDB can be shared across domains and subdomains. This is part of the “same origin policy”.
Why do we need to share localStorage?
A real example that I came across is when I needed to access to data from a marketing site that wasn’t developed by our company. The site was hosted on a different domain from our application site.
Let’s say that the marketing site is on the domain www.marketing.com
and our application on another domain www.app-site.com
. The use case was to save the whole user’s journey back and forth between these sites for analytics purposes. The user got a user Id which was saved in localStorage
and each event during that journey was recorded with that id. I needed a way to share the localStorage
across the domains.
How to achieve it?
Let’s assume we have domain A: www.aaa.com
and domain B: www.bbb.com
and we want that domain B will be able to read and write from localStorage
of domain A.
TL;DR
- Use an
Iframe
on domain B that will load tiny html from domain A - On each domain (domain B and the
Iframe
with domain A) set a listener and communicate throughpostMessage
- Domain A get the data from
localStorage
and send it in amessage
.
Write to another localStorage
First we create an Iframe
on domain B that will load a small html file from domain A. Iframe
style has no height, width and border. Also it is floating in absolute position. This is because we don't want it to take place in the natural flow of the DOM.
// www.bbb.com
<iframe id="iframe" src="www.aaa.com/external.html" onload="onMyFrameLoad()" style="width:0;height:0;border:none;position:absolute;"></iframe>
And we create a small html file hosted in domain A which sets a listener:
// www.aaa.com/external.html
<html>
<head>
<script>
window.addEventListener('message', (message) => {
if (message.origin === 'http://www.bbb.com') {
const data = JSON.parse(message.data);
const { userId } = data;
if (userId){
localStorage.setItem('userId', userId);
}
}
});
</script>
</head>
</html>
Now domain A is ready to get a message
from domain B with data such as userId
and set it to its localStorage
. Remember, the script is under www.aaa.com
, even if it is embedded in Iframe
it still uses the localStorage
of domain A.
When a user will move to domain A from domain B, we will already have the data because domain B sent a message
and domain A stored it in its localStorage
.
Don’t forget first to check message.origin
as it is an important security check! we don’t want to answer messages from an origin that we are not familiar with.
We just need to actually send a message
from domain B through postMessage
. To do so, we add the code of the function onMyFrameLoad
in the Iframe:
// www.bbb.com
<script>
function onMyFrameLoad() {
var userId = getUserId();
var data = {userId: userId};
var iframeEl = document.getElementById("myIframe");
iframeEl.contentWindow.postMessage(JSON.stringify(data), 'www.aaa.com');
};
</script>
<iframe id="iframe" src="www.aaa.com/external.html" onload="onMyFrameLoad()" style="width:0;height:0;border:none;position:absolute;"></iframe>
It’s important that the function onMyFrameLoad
will run after the Iframe
was loaded, which means you need to call it from onload
. We want to send the message only after we add the listener on the other side.
Ok so everything is set. When a user will reach domain B the Iframe
will be loaded, then external.html from domain A will be revoked and it will set a listener for a message
. Domain B will then fire a postMessage
that will send the userId
to domain A, then it will be saved in the localStorage
.
Opposite direction
Now consider the case that the user first visited domain A. We want to share the data, such as the user Id, which was stored in the localStorage
of domain A with domain B.
To do this, we need to add to our external.html an if statement
that checks if domain A already has the data. If it does, it will send a postMessage
to domain B. Otherwise, it will wait for domain B to send the data to it.
// www.aaa.com/external.html
<html>
<head>
<script>
const userId = localStorage.getItem('userId');
if (userId) {
const data = {userId: userId};
parent.postMessage(JSON.stringify(data), "http://www.bbb.com")
}
else {
window.addEventListener('message', (message) => {
if (message.origin === 'http://www.marketing.com') {
const data = JSON.parse(message.data);
const { userId } = data;
if (userId){
localStorage.setItem('userId', userId);
}
}
});
}
</script>
</head>
</html>
Note that now the postMessage
is a property of parent
and not the iframe
element. This is because when an iframe
is embedded, its window
is the parent
object which is responsible for loading it.
And similarly to domain A, on domain B we will add a script that sets a listener.
// www.bbb.com
<script>
window.addEventListener('message', function(message) {
if (message.origin === 'wwww.aaa.com') {
var data = JSON.parse(message.data);
var userId = data.userId;
if (userId){
doSomethingWithTheData()
}
}
});
</script>
<script>
function onMyFrameLoad() {
var userId = getUserId();
var data = {userId: userId};
var iframeEl = document.getElementById("myIframe");
iframeEl.contentWindow.postMessage(JSON.stringify(data), 'www.aaa.com');
};
</script>
<iframe id="myIframe" src="www.aaa.com/external.html" onload="onMyFrameLoad()" style="width:0;height:0;border:none;position:absolute;"></iframe>
Conclusion
We saw that we can share data that is saved in the localStorage
of one domain through postMessage
with another domain using an iframe
.
This is of course suitable for specific use cases with an agreement between the 2 domains on the exact params that we are willing to share. There are only a few use cases that we are willing to break the rule of none cross-domain storage, so you must carefully think if your case is a suitable use-case.