Related
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?
This is how I use it across domains...
Use an iframe from your parent domain - say parent.example
Then on each child.example domain, just do a postMessage to your parent.example iframe
All you need to do is setup a protocol of how to interpret your postMessage messages to talk to the parent.example iframe.
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.
Pros:
Doesn't need the extra iframe and postMessage set up.
Cons:
Will make the data available across all subdomains (not just www) so if you don't trust all the subdomains it may not work for you.
Will send the data to the server on each request. Not great, but depending on your scenario, maybe still less work than the iframe/postMessage solution.
If you're doing this, why not just use the cookies directly? Depends on your context.
4K max cookie size, total across all cookies for the domain (Thanks to Blake for pointing this out in comments)
I agree with other commenters though, this seems like it should be a specifiable option for localStorage so work-arounds aren't required.
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.
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
This is how:
[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.
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
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
https://www.digitalocean.com/community/tutorials/how-to-redirect-www-to-non-www-with-nginx-on-centos-7
or
any other level like route 53 if are using
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]
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?
This is how I use it across domains...
Use an iframe from your parent domain - say parent.example
Then on each child.example domain, just do a postMessage to your parent.example iframe
All you need to do is setup a protocol of how to interpret your postMessage messages to talk to the parent.example iframe.
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.
Pros:
Doesn't need the extra iframe and postMessage set up.
Cons:
Will make the data available across all subdomains (not just www) so if you don't trust all the subdomains it may not work for you.
Will send the data to the server on each request. Not great, but depending on your scenario, maybe still less work than the iframe/postMessage solution.
If you're doing this, why not just use the cookies directly? Depends on your context.
4K max cookie size, total across all cookies for the domain (Thanks to Blake for pointing this out in comments)
I agree with other commenters though, this seems like it should be a specifiable option for localStorage so work-arounds aren't required.
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.
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
This is how:
[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.
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
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
https://www.digitalocean.com/community/tutorials/how-to-redirect-www-to-non-www-with-nginx-on-centos-7
or
any other level like route 53 if are using
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]
Background
We have two web applications hosted on different sub-domains. Application 1 is an internal admin system. Application 2 is a helpdesk system.
We can modify the source code of Application 1 but we have no access to modify Application 2.
The Goal
To display a link against an order in Application 1 that will open a new window, the URL of which is that of a ticket in Application 2.
The idea being that our staff can see that an order has a helpdesk ticket raised against it and simply needs to click a link on the order to view the ticket and reply to it.
The problem
Regardless of how I open the new window (window.open, target="_blank", etc.) the ticket in the new window is unable to make any ajax requests back to the helpdesk system where it is hosted.
The URL of the new window is part of Application 2.
In Google dev tools it tells me "The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "https". Protocols must match." even when I open it using _blank.
If I go to the exact same URL manually everything works... but this doesn't help when I need it to work from the link.
Is there any way to achieve the above?
If not, is there any way I can open a new window that is "detached" from the window that opened it so that same origin policy no longer applies?
Edit 2014-03-28 10:23
I have no access to App2's code at all. I cannot make any changes to App2. Any answer must take this into account.
I am trying to open a new window from my application (App1) where the target URL of that window is a page in App2. That page inside App2 then needs to be able to use ajax to communicate with other areas of App2. This is where the problem lies. Because App1 opened the window the same origin policy is preventing that window from making it's ajax requests.
I suspect that JavaScript on the second (helpdesk) app is trying to access the first app via window.opener (which could lead to the cross-origin error you're seeing) and subsequent JavaScript (fetching stuff via AJAX) is then not getting executed. You can probably narrow things down by setting appropriate breakpoints in the second app.
If this is the cause and you can't modify the source for the helpdesk app, how about going to a URL in the internal domain that would then redirect to the help desk? The redirect should cause the window.opener property to become null (same as manually typing in the URL).
Assuming https://admin.mydomain.co.uk and http://helpdesk.mydomain.co.uk, clicking on the "Help Ticket" link would go to a URL in the internal app, e.g. https://admin.mydomain.co.uk/getHelp?ticketId, which would respond with a 301 response and an appropriate Location: http://helpdesk.domain.uk/help/ticketId header taking the user to the actual helpdesk URL.
You could use a proxy server or iframe proxying.
Use the following url //app2.mydomain.co.uk without the http or https.
It's not only a cross domain problem but a protocol issue :
You can't embed https into http page without this warning.
Consider using iframe inside your App1 :
<iframe src="https://app2.mydomain.co.uk" ></iframe>
Or maybe you can use CORS to access data between your two domains ( but i think it's not the point, you want the whole App2 page, isn't it ? )
Edit : By re-reading your question, i'm pretty sure of two thing :
You're not looking at the right direction. You say App2 don't use SSL, and that obviously false when Chrome say "Protocols must match"
It's not a "attach" or "detached" problem. If you put a link (blank or not) in a page, it can be load the new page without any problem, nor link with the referal page.
So my guess is : Your are calling App2 without SSL ( no https), BUT inside the App2, there is some https involved ( certainly some ajax query). So here is the problem : When you open the page without https, it's seem to load, but when the first https Ajax fires, it fail.
Try using https when calling your App2 url, and give us the result
My solution is this: in Application 1 you create a method your method that calling Application 2 on the server side, then you can use AJAX calling your method which will return result of Application 2.
Is it possible, from within the "http-on-modify-request" event, to identify which requests are coming from a PageWorker object, as opposed to those coming from visible tabs/windows?
Note: Because of redirects and subresources, the URL here is NOT the same URL as the pageWorkers contentURL property.
require("sdk/system/events").on("http-on-modify-request", function(e) {
var httpChannel = e.subject.QueryInterface(Ci.nsIHttpChannel),
url = httpChannel.URI.spec,
origUrl = httpChannel.originalURI.spec;
...
});
I don't know of any way to actually distinguish page-worker requests from "regular" ones.
Current, page workers are implemented like this:
The SDK essentially creates an <iframe> in the hiddenWindow (technically, in sdk/addon/window, which creates a hidden window in the hiddenWindow). The hiddenWindow in mozilla applications is more or less an always-present top-level XUL or HTML window that is simply hidden.
The worker page is loaded into that iframe.
The page-worker will then operate on the DOM on that iframe.
It is possible to identify requests originating from the hidden window and the document within the hidden window.
But identifying if the request or associated document belongs to a page-worker, let alone which page-worker instance, doesn't seem possible, judging from the code. The SDK itself could map the document associated with a request back to a page-worker, as it keeps some WeakMaps around to do so, but that is internal stuff you cannot access.
You only can say that a request is not coming from a page-worker when it is not coming from the hiddenWindow.
Also, keep in mind that there are tons of requests originating neither from a tab nor page-worker: Other (XUL) windows, add-ons, js modules and components, etc...
If it a page-worker created by your add-on that you're interested in: The contentURL property should reflect the final URI once the page is loaded.
I wrote a content script that injects an iframe to any website (therefore different domain).
I need the parent website to send some information to the child iframe, however I couldn't find a way to do it.
The code
var targetFrame = $('#myIframe')[0];
targetFrame.contentWindow.postMessage('the message', '*');
Doesn't work somehow and i get a Cannot call method 'postMessage' of undefined error.
But then when I tried the same code directly in Chrome's console, it worked.
I had no trouble sending a postMessage from the child to the parent though but just need a way for the parent to send messages to the child iframe.
I recently wrote code that did postMessage to an iframe and I encountered quite a similar issue where it said contentWindow is undefined.
In my case, my iframe was not yet part of the DOM tree, it was a variable created by document.createElement('iframe').
Once I put it hidden (width and height 0px, visibility hidden) into the body of the page, contentWindow was no longer undefined and everything worked as expected.
I found the Mozilla Developer Network page for postMessage extremely useful when I was working on my project.
I've had success using the following library:
http://easyxdm.net/wp/
It doesn't require any flash/silverlight, only javascript. And it is compatible as far back as as IE6.
It took a little doing to get it up and running, but once it was things ran very smoothly.
Keep in mind that if the iFrame you're opening on the other domain uses a different protocol (HTTP vs. HTTPS) the browser will kick out a warning which prevents your script from running (unless the user says they will accept the risk). If you have access to both protocols it may be wise to host the contents of the iFrame on both HTTP and HTTPS and load the appropriate script accordingly.
Good luck!
You don't need to target contentWindow. Try this:
var targetFrame = $('#myIframe')[0];
targetFrame.postMessage('the message', '*');