Reputation: 20115
I'm replacing cookies with localStorage on browsers that can support it (anyone but IE). The problem is site.example
and www.site.example
store their own separate localStorage objects. I believe www
is considered a subdomain (a stupid decision if you ask me). If a user was originally on site.example
and decides to type in www.site.example
on her next visit, all her personal data will be inaccessible. How do I get all my "subdomains" to share the same localStorage as the main domain?
Upvotes: 169
Views: 208572
Reputation: 71
If there is not a huge amount of data you can try leverage this npm page: subdomain-storage It is still based on cookies but from code perspective is uses same interface as localStorage.
Here is example:
First set parent domain:
import { subdomainStorage } from 'subdomain-storage';
subdomainStorage.setConfig({domain: '.example'});
or
import { subdomainStorage } from 'subdomain-storage';
subdomainStorage.setConfig({domain: 'site.example'});
and then just use in app just as localStorage to set, get or delete items:
import { subdomainStorage } from 'subdomain-storage';
subdomainStorage.setItem('some-key', 'some value');
const value = subdomainStorage.getItem('some-key');
subdomainStorage.removeItem('some-key');
It can help in this case and also keep same code style as if you were using LS, just don't forget about size limitations
Upvotes: 0
Reputation: 8174
[November 2020 Update: This solution relies on being able to set document.domain
. The ability to do that has now been deprecated, unfortunately. NOTE ALSO that doing so removes the "firewall" between domains and subdomains for vulnerability to XSS attacks or other malicious script, and has further security implications for shared hosting, as described on the MDN page. September 2022 Update: From Chrome v109, setiing document.domain
will only be possible on pages that also send an Origin-Agent-Cluster: ?0
header.]
For sharing between subdomains of a given superdomain (e.g. 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.
(1) Pick one "main" domain to for the data to belong to: i.e. either https://example.com or https://www.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 https://www.example.com pages (the other domain), use javascript to set document.domain = "example.com";
. 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). @Mayank Jain 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.
Upvotes: 13
Reputation: 183
This is how I solved it for my website. I redirected all the pages without www to www.site.example
. This way, it will always take localstorage of www.site.example
Add the following to your .htaccess
, (create one if you already don't have it) in root directory
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]
Upvotes: -5
Reputation: 11012
Set to cookie in the main domain:
document.cookie = "key=value;domain=.mydomain.example"
and then take the data from any main domain or sub domain and set it on the localStorage
Upvotes: 21
Reputation: 86805
I suggest making site.example
redirect to www.site.example
for both consistency and for avoiding issues like this.
Also, consider using a cross-browser solution like PersistJS that can use each browser native storage.
Upvotes: 42
Reputation: 3195
This is how I use it across domains...
parent.example
child.example
domain, just do a postMessage to your parent.example
iframeparent.example
iframe.Upvotes: 144
Reputation: 3649
this kind of solution causes many problems like this. for consistency and SEO considerations redirect on the main domain is the best solution.
do it redirection at the server level
How To Redirect www to Non-www with Nginx
or any other level like route 53 if are using
Upvotes: 0
Reputation: 3569
I'm using xdLocalStorage, this is a lightweight js library which implements LocalStorage interface and support cross domain storage by using iframe post message communication.( angularJS support )
https://github.com/ofirdagan/cross-domain-local-storage
Upvotes: 4
Reputation: 2821
If you're using the iframe and postMessage solution just for this particular problem, I think it might be less work (both code-wise and computation-wise) to just store the data in a subdomain-less cookie and, if it's not already in localStorage on load, grab it from the cookie.
I agree with other commenters though, this seems like it should be a specifiable option for localStorage so work-arounds aren't required.
Upvotes: 62