How to achieve cross-domain localStorage

Adi Achituve
5 min readJan 27, 2023

--

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 through postMessage
  • Domain A get the data from localStorage and send it in a message.

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.

--

--