68

I have to call domain A.com (which sets the cookies with http) from domain B.com. All I do on domain B.com is (javascript):

var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.src = "A.com/setCookie?cache=1231213123";
head.appendChild(script);

This sets the cookie on A.com on every browser I've tested, except Safari. Amazingly this works in IE6, even without the P3P headers.

Is there any way to make this work in Safari?

Luca Matteis
  • 29,161
  • 19
  • 114
  • 169
  • Does Safari send a request at all? – Gumbo Jan 24 '09 at 10:51
  • Yes, it sends the request, just doesn't set the cookie. – Luca Matteis Jan 27 '09 at 22:07
  • 1
    I would take this issue to their mailing list: http://developer.apple.com/internet/webdevelopmentlist.html – Zach Feb 03 '09 at 05:22
  • Haha, I don't want to give out that feeling, I thank you for pointing me to the Safari Developer FAQ and giving me your experience on the matter, but this doesn't really answer my point, that's all I'm trying to say. – Luca Matteis Feb 04 '09 at 15:48
  • 1
    http://evernote.com/about/download/#a-webclipper drag the "Clip to Evernote" icon on your toolbar. Go to someone elses site, click on the boomarklet, try logging in / signing up, it sets cookies. – Luca Matteis Feb 04 '09 at 15:50
  • I have posted a working solution. See my answer below. – Alec Smart Feb 19 '10 at 11:42
  • 2
    Evernote's web clipper is an extension, which means that you explicitly give it permissions when you install it.. The install dialog states "Install extensions only from sources you trust. Extensions can modify the appearance and behavior of Safari, and access your private information at websites." – ForOhFor Dec 04 '12 at 10:37

18 Answers18

64

From the Safari Developer FAQ:

Safari ships with a conservative cookie policy which limits cookie writes to only the pages chosen ("navigated to") by the user. This default conservative policy may confuse frame based sites that attempt to write cookies and fail.

I have found no way to get around this.

If it's worth anything, Chrome doesn't set the cookies either if you use the <script> appending method, but if you have a hidden <img> with the same source, Chrome works in addition to the rest of the browsers (except, again, Safari)

Paolo Bergantino
  • 480,997
  • 81
  • 517
  • 436
  • Any idea how evernote.com does this? They use an Iframe for their bookmarklet thingy, which is able to set the cookies, somehow. – Luca Matteis Jan 28 '09 at 05:31
  • Are you sure evernote does it, and not that you have changed your default Safari settings? – Paolo Bergantino Jan 28 '09 at 05:33
  • Well I've dug into this issue plenty of times before and maybe they're giving the illusion it works but I'm 99.9% that Safari will not set the cookies unless that security setting is changed. – Paolo Bergantino Jan 28 '09 at 05:41
  • If I knew I'd tell you... the point is it might seem like its working but they're obviously doing something else behind the scenes. You can't set a third party cookie on Safari with default settings. It's that simple. – Paolo Bergantino Jan 28 '09 at 05:46
  • Same on ie6 with default settings, but there's ways of getting around that. – Luca Matteis Jan 28 '09 at 05:57
  • Also I just tested on Chrome and the script tag approach works fine (no need to use the img). – Luca Matteis Jan 29 '09 at 00:35
  • Why was this Voted up! its not answering my question. – Luca Matteis Feb 02 '09 at 16:40
  • 2
    It was voted up because it's the correct answer, you can't do what you're requesting in Safari without doing a full-page redirect to the third-party domain. – Matty F Jun 18 '10 at 08:57
  • Can't you post to a hidden iframe, and in that iframe, set the cookie? – thesmart Jan 26 '11 at 01:46
  • Our developers looked at various workarounds but they seem fragile given Apple will close any such holes. Instead, they wrote a 'please register your device' page that sets a cookie when user presses a button. This page is only required once per device, not once per session, so it's not too much hassle for the user. – RichVel Dec 13 '13 at 10:16
  • I was able to provide a working solution (see below) or visit my post at http://stackoverflow.com/questions/16703176/php-multiple-cookies-not-working-on-ipad-iphone-browser – Michael Mikhjian Jul 06 '15 at 03:55
14

Here is a solution which works:

http://anantgarg.com/2010/02/18/cross-domain-cookies-in-safari/

Alec Smart
  • 94,115
  • 39
  • 120
  • 184
  • 2
    It does work, but you can't create the cookie via javascript, it must be create by the server on the form's response. +1 – Pablo Cabrera Mar 09 '11 at 18:38
  • 3
    It's my impression that this loophole has been closed in Webkit after the whole 'Google hacked Safari for ads' thing – streetlight Apr 02 '13 at 13:58
  • 8
    @streetlight - you are correct, the author of that blog post confirms here that this no longer works: http://anantgarg.com/2010/02/18/cross-domain-cookies-in-safari/#comment-924880962 – RichVel Oct 15 '13 at 14:48
12

This might not work for everyone, but I came across this issue because I was serving a React App from a different host than the API, and the solution that ultimately worked was to use DNS:

Our client was being served from www.company-name.com and our API was on company-name.herokuapp.com. By making a CNAME record api.company-name.com --> company-name.herokuapp.com, and having our client use that subdomain for API calls, Safari stopped considering it a "third-party" cookie.

The upside is that there's very little code involved, and it's all using well-established stuff... The downside is that you need some control/ownership over the API host if you're going to use https - they need a certificate that's valid for the client domain, or users will get a certificate warning - so this wouldn't work (at least not for something end-user-facing) if the API in question isn't yours or a partner's.

DrShaffopolis
  • 1,088
  • 1
  • 11
  • 14
  • This answer helped me a lot and resolved my Safari cookies issue, thank you. Just to be more specific about the certificate warning, the SSL certificate should have the client domain as a listed SAN (Subject Alternate Name) for this to work. You will need to figure out how to do this with your certificate provider. – lukio3 Sep 14 '20 at 07:14
  • I- I love you. I was about to redevelop my whole auth system until I found this. – Chano Jun 13 '21 at 07:17
  • This saved my product launch ! Thank you so much! – Aravind Jun 17 '21 at 21:35
  • @lukio3 I did the same thing, but ran into an SSL issue, that its name is invalid, do you know how to handle that? – Daman Mokha Jun 02 '23 at 12:43
7

Working method 2014-2016:

You have to do window.open to the domain / assign a cookie / close the popup, the domain is now safelisted.

Original post @ PHP multiple cookies not working on iPad / iPhone browser

Community
  • 1
  • 1
Michael Mikhjian
  • 2,760
  • 4
  • 36
  • 51
  • 1
    The code on this page works great for this: http://measurablewins.gregjxn.com/2014/02/safari-setting-third-party-iframe.html – Mark Dec 16 '16 at 19:30
4

There is a bit of an evil trick assuming they have flash installed.

I'm not sure if it still works or not, but Flash'es "Local Shared Objects" aka Flash Cookies could help you circumnavigate Safari's same-domain policies.

Local Shared Object Tutorial

However, it may be complicated to implement, to say the least.

Additonally, LSO's are comming into the light as being a security nightmare:

So think carefully before using them.

Kent Fredric
  • 56,416
  • 14
  • 107
  • 150
  • Hey Kent, yeah this is the solution I think, use a flash object... I wish I could give the correct answer to you, but it's stuck :( – Luca Matteis Feb 21 '09 at 15:42
3

There is a proper workaround for this working in 2015. Let's say there is website y.com which includes iframe with site x.com. The x.com iframe wants to store a cookie. That is not permitted by Safari policy, however, y.com is able to store it. So y.com must listen to messages from x.com and then store the cookie itself.

var _cookieEvMth = window.addEventListener ? "addEventListener" : "attachEvent";
var _cookieEvAction = window[_cookieEvMth];
var _cookieEv = _cookieEvMth == "attachEvent" ? "onmessage" : "message";
_cookieEvAction(_cookieEv, function(evt){
  if(evt.data.indexOf('cookieset')!=-1){
    var datack = evt.data.split('|');
    YOUR_CUSTOM_COOKIE_SAVE_METHOD(datack[1],datack[2],datack[3]);
  }
},false);

When x.com needs to store the cookie, it must post a message to y.com:

window.parent.postMessage('cookieset|'+ckName+'|'+ckVal+'|'+days,'*');

Also you can work your way to post message to the iframe if you want to read the cookie. Or you can include it as parameter in x.com iframe url using javascript:

iframe.setAttribute('url','x.com/?cookieval='+YOUR_COOKIE_GET_METHOD('cookiename'));
  • I tried to follow your method but i didn't succed. A succed in creating a cookie in the y.com domain using document.cookie=... but I miss something else later... Could you give me some more hints about it? Thanx – Fuvizzo Apr 13 '16 at 21:29
  • It seems as though this method will set the cookie for the domain y.com. If x.com wants to use this cookie later, from a non-iframe page, it will not be acessible. – Mark Dec 16 '16 at 18:11
3

A post to a hidden <iframe> can allow you to by-pass this restriction in Safari -- http://gist.github.com/586182:

<?php
  header('P3P: CP=HONK');
  setcookie('test_cookie', '1', 0, '/');
?>
<div id="test_cookie" style="position: absolute; top: -10000px"></div>
<script>
  window.setTimeout(function() {
    if (document.cookie.indexOf('test_cookie=1') < 0) {
      var      
        name = 'test_cookie',
        div = document.getElementById(name),
        iframe = document.createElement('iframe'),
        form = document.createElement('form');

      iframe.name = name;
      iframe.src = 'javascript:false';
      div.appendChild(iframe);

      form.action = location.toString();
      form.method = 'POST';
      form.target = name;
      div.appendChild(form);

      form.submit();
    }
  }, 10);
</script>
daaku
  • 2,797
  • 20
  • 20
  • @wenbert - Possibly doesn't work any more, as it's similar to this answer: http://stackoverflow.com/a/2288555/992887 - needs testing on iOS 6 and 7 – RichVel Oct 17 '13 at 04:37
2

I know this question is rather old, but this helped me to solve cookies problem:

var cookieForm = document.createElement("form");
cookieForm.action = "A.com/setCookie?cache=1231213123";
cookieForm.method = "post";
document.body.appendChild(cookieForm);

cookieForm.submit();

The idea to make a form post on a page that sets your cookies.

Alexander Selishchev
  • 1,180
  • 13
  • 29
2

A workaround we just came up with at my job was to set the cookie via a window.open() - it may not be optimal for you (as you'll have an ugly ass popup window open), but it worked well for us. We had to have a popup window open anyway for OAuth authentication.

So the jist of what we did was:

  1. User clicks a link from B.com
  2. Popup window opens to A.com/setCookie
  3. A.com sets its cookie, and then redirects to B.com in the proper place

Again, not valid in all solutions, but it worked in ours. Hope this helps.

1

*EDIT* This workaround has been reported closed in WebKit.

Luca,

Ok, so this answer is two years old, but... you can set a cookie from an iframe if you post a form to a hidden iframe. You can do this by creating a form:

<form id="myiframe" action="http://yourdomain.com" method="POST" target="iframe_target">

Then in Javascript, get a reference to the form and call submit:

document.getElementsByTagName('form')[0].submit();

You can listen to the iframe's onload, or you can have your iframe action page issue some javascript that signals the load. I have tested this in Safari and Chrome, and it works.

Cheers.

thesmart
  • 2,993
  • 2
  • 31
  • 34
  • 2
    FYI, this no longer works on safari/firefox because of this: http://www.webpronews.com/google-exploited-loophole-to-track-safari-users-browsing-habits-2012-02 – 0x6A75616E Mar 20 '12 at 03:17
0

Note this line:

script.src = "A.com/setCookie?cache=1231213123";

I could not get this working until I added the http, i.e.

script.src = "http://A.com/setCookie?cache=1231213123";
sth
  • 222,467
  • 53
  • 283
  • 367
0

I found a simple solution. You just need for first time setting cookie to check if request come from the same origin or not, if not as usual you need to return into iframe a script that will repeat this request, already having permission to assign cookie. After that you can do other request directly through iframe accessing this cookie. This helped me in my tracking system. Try, this works well.

Placinta Alexandru
  • 463
  • 2
  • 7
  • 20
0

Perhaps pragmatically create and click a link with an href="A.com/setCookie?cache=1231213123" and a target attribute pointing to a hidden iframe. That may bypass Safari's policy of user navigation for setting cookies (I don't have Safari handy to test.)

Zach
  • 24,496
  • 9
  • 43
  • 50
0

Its worth noting that this restriction in Safari doesn't apply across subdomains. So if you directly visit sitea.com, then you can set cookies from subdomain.sitea.com without direct user interaction (iframe/JavaScript).

This was relevant for my case when developing an API. If you're visitors are arriving at mysite.com, and then you want some JavaScript to interact with your API, then if the API is hosted at api.mysite.com, then it will work on Safari.

Dom
  • 2,980
  • 2
  • 28
  • 41
0

I did some extensive investigation around this when I was trying to deploy a site that used Windows Live ID, which depended on the ability to be able to set 3rd party cookies in order to log out. It just... didn't work. Nothing we could do would get it to work. The Live ID team also did extensive investigation and their answer was "can't make it work".

Brad Wilson
  • 67,914
  • 9
  • 74
  • 83
0

Place this JavaScript on the page making cross-domain requests, http://example1.com/index.html:

  <script>
  var gup = function(name, url) {
     if(!url) url = location.href;
     name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
     var regexS = "[\\?&]"+name+"=([^&#]*)";
     var regex = new RegExp( regexS );
     var results = regex.exec( url );
     return results == null ? null : results[1];
  }
  var isSafari = navigator.vendor && navigator.vendor.indexOf('Apple') > -1 && navigator.userAgent && !navigator.userAgent.match('CriOS');
  var n = gup("activated");
  if(isSafari && n == null) {
     //browser is Safari and cookies have not yet been activated
     var current_url = location.protocol + '//' + location.host + location.pathname;
     var query_string = '?callback=' + encodeURIComponent(current_url + '?activated=1');
     var new_url = 'http://example2.com/activate.php' + query_string;
     window.location.href = new_url;
  }
  //the rest of your code goes here, and you can now set cross-domain cookies on Safari
  </script>

Then create a file on the other server, which needs to set cookies, http://example2.com/activate.php:

  <?php
  if(isset($_GET['callback'])) {
     header('Location: '.$_GET['callback']);
     exit();
  } else {
     //in case callback param is not set, simply go back to previous page
     echo "<script>";
     echo "window.history.back();";
     echo "</script>";
     exit();
  }
  ?>

Here's how this works:

  1. When http://example1.com/index.html is first visited, a check is made to see whether the browser is Safari and whether a GET parameter of the name "activated" does not exist. If both conditions are met (which will happen on the first visit for a Safari browser), then the browser is redirected to http://example2.com/activate.php with a GET parameter, "callback", containing the calling URL appended with an "activated" parameter.

  2. http://example2.com/activate.php simply redirects back to the URL contained in the GET parameter, "callback".

  3. When http://example1.index.html is now hit this second time after being redirected-to, the GET parameter, "activated" will now be set, so the conditional from step 1 will not execute, thus allowing the script to continue execution.

This fulfills Safari's requirement of having the browser visit the 3rd party domain at least once in order to start setting cookies.

Dane Iracleous
  • 1,659
  • 2
  • 16
  • 35
  • As long as "Prevent cross-site tracking" option in safari or chrome or any other browser is checked (which it is on ios) i doubt it cares about the target is visited once or not, it rejects any cross-site cookie. – Amin Mar 15 '22 at 19:32
-2

Try something like:

var w = window.open("A.com/setCookie?cache=1231213123");
w.close();

It may bypass safari's security policy.

jcampbell1
  • 4,159
  • 3
  • 33
  • 35
-14

It isn't the missing type-attribute thats annoying you ?-)

<script type="text/javascript">
  var head = document.getElementsByTagName("head")[0];
  var script = document.createElement("script");
  script.setAttribute("type","text/javascript");
  script.src = "A.com/setCookie?cache=1231213123";
  head.appendChild(script);
</script>
roenving
  • 2,560
  • 14
  • 14