Safari 3rd party cookie iframe trick no longer working?

121,623

Solution 1

Just wanted to leave a simple working solution here that does not require user interaction.

As I stated in a post I made:

Basically all you need to do is load your page on top.location, create the session and redirect it back to facebook.

Add this code in the top of your index.php and set $page_url to your application final tab/app URL and you’ll see your application will work without any problem.

<?php
    // START SAFARI SESSION FIX
    session_start();
    $page_url = "http://www.facebook.com/pages/.../...?sk=app_...";
    if (isset($_GET["start_session"]))
        die(header("Location:" . $page_url));

    if (!isset($_GET["sid"]))
        die(header("Location:?sid=" . session_id()));
    $sid = session_id();
    if (empty($sid) || $_GET["sid"] != $sid):
?>
   <script>
        top.window.location="?start_session=true";
    </script>
<?php
    endif;
    // END SAFARI SESSION FIX
?>

Note: This was made for facebook, but it would actually work within any other similar situations.


Edit 20-Dec-2012 - Maintaining Signed Request:

The above code does not maintain the requests post data, and you would loose the signed_request, if your application relies on signed request feel free to try the following code:

Note: This is still being tested properly and may be less stable than the first version. Use at your own risk / Feedback is appreciated.

(Thanks to CBroe for pointing me into the right direction here allowing to improve the solution)

// Start Session Fix
session_start();
$page_url = "http://www.facebook.com/pages/.../...?sk=app_...";
if (isset($_GET["start_session"]))
    die(header("Location:" . $page_url));
$sid = session_id();
if (!isset($_GET["sid"]))
{
    if(isset($_POST["signed_request"]))
       $_SESSION["signed_request"] = $_POST["signed_request"];
    die(header("Location:?sid=" . $sid));
}
if (empty($sid) || $_GET["sid"] != $sid)
    die('<script>top.window.location="?start_session=true";</script>');
// End Session Fix

Solution 2

You said you were willing to have your users click a button before the content loads. My solution was to have a button open a new browser window. That window sets a cookie for my domain, refreshes the opener and then closes.

So your main script could look like:

<?php if(count($_COOKIE) > 0): ?>
<!--Main Content Stuff-->
<?php else: ?>
<a href="/safari_cookie_fix.php" target="_blank">Click here to load content</a>
<?php endif ?>

Then safari_cookie_fix.php looks like:

<?php
setcookie("safari_test", "1");
?>
<html>
    <head>
        <title>Safari Fix</title>
        <script type="text/javascript" src="/libraries/prototype.min.js"></script>
    </head>
    <body>
    <script type="text/javascript">
    document.observe('dom:loaded', function(){
        window.opener.location.reload();
        window.close();
    })
    </script>
    This window should close automatically
    </body>
</html>

Solution 3

I tricked Safari with a .htaccess:

#http://www.w3.org/P3P/validator.html
<IfModule mod_headers.c>
Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID CUR ADM DEV OUR BUS\""
Header set Set-Cookie "test_cookie=1"
</IfModule>

And it stopped working for me too. All my apps are losing the session in Safari and are redirecting out of Facebook. As I'm in a hurry to fix those apps, I'm currently searching for a solution. I'll keep you posted.

Edit (2012-04-06): Apparently Apple "fixed" it with 5.1.4. I'm sure this is the reaction to the Google-thing: "An issue existed in the enforcement of its cookie policy. Third-party websites could set cookies if the "Block Cookies" preference in Safari was set to the default setting of "From third parties and advertisers". http://support.apple.com/kb/HT5190

Solution 4

For my specific situation I resolved the problem by using window.postMessage() and eliminating any user interaction. Note that this will only work if you can somehow execute js in the parent window. Either by having it include a js from your domain, or if you have direct access to the source.

In the iframe (domain-b) i check for the presence of a cookie and if it is not set will send a postMessage to the parent (domain-a). Eg;

if (navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1
    && document.cookie.indexOf("safari_cookie_fix") < 0) {
    window.parent.postMessage(JSON.stringify({ event: "safariCookieFix", data: {} }));
}

Then in the parent window (domain-a) listen for the event.

if (typeof window.addEventListener !== "undefined") {
    window.addEventListener("message", messageReceived, false);
}

function messageReceived (e) {
    var data;

    if (e.origin !== "http://www.domain-b.com") {
        return;
    }

    try {
        data = JSON.parse(e.data);
    }
    catch (err) {
        return;
    }

    if (typeof data !== "object" || typeof data.event !== "string" || typeof data.data === "undefined") {
        return;
    }

    if (data.event === "safariCookieFix") {
        window.location.href = e.origin + "/safari/cookiefix"; // Or whatever your url is
        return;
    }
}

Finally on your server (http://www.domain-b.com/safari/cookiefix) you set the cookie and redirect back to where the user came from. Below example is using ASP.NET MVC

public class SafariController : Controller
{
    [HttpGet]
    public ActionResult CookieFix()
    {
        Response.Cookies.Add(new HttpCookie("safari_cookie_fix", "1"));

        return Redirect(Request.UrlReferrer != null ? Request.UrlReferrer.OriginalString : "http://www.domain-a.com/");
    }

}

Solution 5

In your Ruby on Rails controller you can use:

private

before_filter :safari_cookie_fix

def safari_cookie_fix
  user_agent = UserAgent.parse(request.user_agent) # Uses useragent gem!
  if user_agent.browser == 'Safari' # we apply the fix..
    return if session[:safari_cookie_fixed] # it is already fixed.. continue
    if params[:safari_cookie_fix].present? # we should be top window and able to set cookies.. so fix the issue :)
      session[:safari_cookie_fixed] = true
      redirect_to params[:return_to]
    else
      # Redirect the top frame to your server..
      render :text => "<script>alert('start redirect');top.window.location='?safari_cookie_fix=true&return_to=#{set_your_return_url}';</script>"
    end
  end
end
Share:
121,623
gs hurley
Author by

gs hurley

Updated on July 08, 2022

Comments

  • gs hurley
    gs hurley almost 2 years

    So this is the umteenth revenge of the "how do I get 3rd party cookies to work in Safari" question but I'm asking again because I think the playing field has changed, perhaps after February 2012. One of the standard tricks to get 3rd party cookies in Safari was as follows: use some javascript to POST to a hidden iframe. It (used to) trick Safari into thinking that the user had interacted with the 3rd party content and so then allow cookies to be set.

    I think this loophole has been closed in the wake of the mild scandal where it was revealed that Google was using that trick with its ads. At the very least, while using this trick I have been completely unable to set cookies in Safari. I unearthed some random internet postings that claimed that Apple was working on closing the loophole but I haven't found any official word.

    As a fallback I even tried redesigning the main third party frame so that you had to click on a button before the content would load but even that level of direct interaction was not enough to melt Safari's cold cold heart.

    So does anyone know for certain if Safari has indeed closed this loophole? If so, are there other workarounds (other than manually including a session ID in every request)?

  • mscha
    mscha about 12 years
    I know about the Google thing, but I haven't seen anything about Apple actually “fixing” this (and breaking half the internet). Do you have any details on that?
  • vwoelm
    vwoelm about 12 years
    Apparently Apple "fixed" it with 5.1.4. I'm sure this is the reaction to the Google-thing: "An issue existed in the enforcement of its cookie policy. Third-party websites could set cookies if the "Block Cookies" preference in Safari was set to the default setting of "From third parties and advertisers". support.apple.com/kb/HT5190
  • gs hurley
    gs hurley about 12 years
    So I think this comment by vwoelm is the closest to the answer that I was looking for. First and foremost, I wanted confirmation that Apple definitively closed the loophole and the reference to the Apple support article is just that. The second part of my question though still pertains. What are the range of options for workarounds. Clearly, we can encode session IDs as GET/POST params but what are the other options. Does local storage work in this context? Flash cookies?
  • mscha
    mscha about 12 years
    Are you sure this (still) works with the latest version of Safari? We've had P3P headers for years, for IE, but Safari's still broken.
  • mscha
    mscha about 12 years
    @vwoelm: that is indeed the answer I was looking (but not hoping) for. If you put this in an answer instead of a comment, I'll assign you the bounty.
  • rmarscher
    rmarscher about 12 years
    Try clearing all of your cookies and test again. The problem doesn't show itself if your browser already has cookies for your iframed site.
  • Seth Brown
    Seth Brown about 12 years
    Hmmm, I can't get the P3P headers to work either. They still work in IE though!
  • vwoelm
    vwoelm about 12 years
    I was thinking of something like that. Works flawlessly. I load it along with the permission dialog. Thanks!
  • hekevintran
    hekevintran about 12 years
    @gshurley I think sending session ids through GET/POST params is the only option. It's insecure, but then again Facebook forces us to serve canvas apps without SSL anyway. And besides what do you really get by hacking a Facebook app haha? We are at the mercy of Apple and they are currently in cruel mode.
  • chantheman
    chantheman about 12 years
    This used to work. But for the most recent version of Safari it does not. Clear your cache and try it again....
  • LocalPCGuy
    LocalPCGuy about 12 years
    Seems like this is also working around Safari's settings, and that, once it is common knowledge will get axed just like the other "solutions." I am looking for a different solution altogether, because it is becoming pretty obvious that 3rd party cookies are now the devil, even when used in appropriate ways.
  • Anthony Hastings
    Anthony Hastings almost 12 years
    This solution worked for me, however, is the popup necessary? Could you redirect your iframe to the safari page, set the cookie, then re-direct back to the game with redirect headers? Or do you need the popup so that the user has some form of direct contact with the server?
  • Khalizar
    Khalizar almost 12 years
    this worked for me!! thanks!! .. i was having this problem with Safari 5.1.7.... now it's solved!
  • Chris Disley
    Chris Disley almost 12 years
    Definitely not working with 5.1.7 (7534.57.2) with the P3P headers.
  • SamiSalami
    SamiSalami over 11 years
    Thank you, works like a charm and is so easy to implement in existing apps :-)
  • Tom Kincaid
    Tom Kincaid over 11 years
    Does not work for me. Cleared everything and used this header. Cookies still are not set in iframe. Using Safari 6.0. Maybe you changed the default cookie preferences.
  • littlered
    littlered over 11 years
    this worked well; fortunately, the pushing of a button to initialize this wasn't a big deal in my app.
  • Aaron Gibralter
    Aaron Gibralter over 11 years
    @LocalPCGuy I'm not so sure it'll get axed like other solutions because it requires that a user actually click/interact with the page in order to have the popup not blocked. This solution that Safari has come up with seems to work well: advertisers won't be able to secretly set cross-domain cookies, and embedded apps will require a user to interact before being able to set a cookie.
  • Aaron Gibralter
    Aaron Gibralter over 11 years
    So this one basically works with a couple of redirects, right?
  • Diogo Raminhos
    Diogo Raminhos over 11 years
    Exactly @AaronGibralter, it makes two redirects when needed. The main idea is to avoid requiring a user click.
  • Aaron Gibralter
    Aaron Gibralter over 11 years
    Makes senses... unfortunately, I'm working on a widget that gets embedded on other pages and I can't redirect. Think I have to go the popup route. Only problem is on the iPhone, popups don't work nicely. Sucks that iOS 6 went the same way as Safari 6.
  • Mike
    Mike over 11 years
    Great work on this. I updated the user agent check to make sure Chrome wasn't in the user agent as well since Chrome has Safari in the user agent string as well. Redirecting to your domain's page, setting the needed cookies/starting session and then redirecting back to your app on apps.facebook.com worked like a charm. Seems silly, but works well. Thanks Sascha for the tip!
  • dnuske
    dnuske over 11 years
    I want to state that this solution works. make sure you DELETE ALL cookies on safari. And then try this.
  • hugo der hungrige
    hugo der hungrige over 11 years
    Thank you very much! I've been searching for something like this for hours!
  • Diogo Raminhos
    Diogo Raminhos over 11 years
    @hugoderhungrige you're welcome, I just added a new version, feel free to check it out if you need to maintain the signed request on your apps.
  • CBroe
    CBroe over 11 years
    The post-the-signed_request-again-workaround is quite unnecessary - you could just put the POST parameter value into the session, and read it from there again on the next page.
  • Diogo Raminhos
    Diogo Raminhos over 11 years
    @CBroe, actually it is not, this is a fix for a safari session problem, as the question explains, at the time I have to make the redirect I still don't have the session initialized on safari (or if I have I'm not sure yet) - and that is the point of the whole answer - that is why I use the form. Thanks.
  • Diogo Raminhos
    Diogo Raminhos over 11 years
    @CBroe thinking a little bit about it I think you may be right! If the session is already initialized it will pass to the next request, Thansk for pointing that out! I'll be checking that solution and seeing if it works!
  • Diogo Raminhos
    Diogo Raminhos over 11 years
    @CBroe Thank you very much for pointing that out! You were right, it works, since that by the second request the user already has a session started! I guess that "the worst blind is the one who doesn't want to see".
  • hugo der hungrige
    hugo der hungrige over 11 years
    @Whiteagle: The Updated Version is much better. I would give you more credit for this, but I cant =) great work!
  • Diogo Raminhos
    Diogo Raminhos over 11 years
    Thanks again @hugoderhungrige! :) Glad to know I could help someone.
  • TheRightChoyce
    TheRightChoyce over 11 years
    I've been using this fix and it works great, however in new versions of chrome (24.0.1312.52+) that have 'Block third-party cookies and site data' checked this fix no longer seems to work. Has anyone else experienced this?
  • Diogo Raminhos
    Diogo Raminhos over 11 years
    Hello @Thechoyce, I've made some research and found this (code.google.com/p/chromium/issues/detail?id=113401 - dated from 2012) and it appears that chrome's option not only blocks the cookie write but also the read (this does not happen in safari, only the write is blocked). I've made some unsuccessful tests and it appears to be very difficult / impossible to break. Any way it's not a default option correct? The user must activate it right? The only possibility is to make a redirect count trough app_data and, if it's greater than one, show a message asking the user to unblock cookies.
  • TheRightChoyce
    TheRightChoyce over 11 years
    @Whiteagle Its not a default option (thankfully!). I've been browsing around with it on and its pretty vicious. I managed to modify your above code to detect to a potential redirect loop and, as a last result, dump the user outside of the canvas app. I can post it a bit later if you think it'd be helpful = )
  • Jean-Georges
    Jean-Georges about 11 years
    Is there any way to detect if the navigator is accepting third party cookies using php and redirect user only it doesn't ?
  • Rails beginner
    Rails beginner about 11 years
    Is there a similar problem with sessions in safari?
  • Gonzalingui
    Gonzalingui almost 10 years
    Thank you! You saved my weekend :)
  • Diogo Raminhos
    Diogo Raminhos over 9 years
    @GajusKuizinas, you're commit dates Aug 01, 2014, this post is from 2012 eitherway, most people don't want an SDK on the server-side (most applications we make at the company I work at use the JS SDK), this solution is standalone and you won't need any extra load on the server-side by loading a library just to fix the cookie problem :)
  • Gajus
    Gajus over 9 years
    @Whiteagle Either implementation requires server side and client side. However, your solution is not bulletproof. 1) It does not cater for Canvas apps. 2) It does not cater for app_data/GET data in the Page Tab/Canvas context. 3) It requires hardcoding URLs. Refer to github.com/gajus/fuss/blob/master/src/App.php#L154, method bypassThirdPartyCookie, to learn how to do it.
  • Diogo Raminhos
    Diogo Raminhos over 9 years
    @GajusKuizinas, 1) It DOES work on Canvas apps. 2) It DOES works with app_data -> app_data is passed over the signed request, and my solution covers signed_request. What's the point of coming here spamming your library and criticising an answer with more than two years that has helped at least 39 other users besides me? The question here is "Safari 3rd party cookie iframe trick no longer working?", as the title says the author was looking for a "3rd party cookie iframe trick", not a whole library, just something he could rollout to a pre-made application that would make it work on the spot...
  • Gajus
    Gajus over 9 years
    @Whiteagle Can you provide a test case demonstrating 1 and 2?
  • Luc Boissaye
    Luc Boissaye over 9 years
    you can replace set_your_return_url to request.env['HTTP_REFERER'] as we are inside an iframe :-D
  • akousmata
    akousmata about 9 years
    You are not getting a syntax error with your call to postMessage?
  • idejuan
    idejuan almost 9 years
    I have tried this solution, and the only issue is that I cannot return to the parent iframe url. Is there a method in Rails to obtain the url of the parent iframe? thanks
  • guyaloni
    guyaloni almost 9 years
    It took me time but finally I figured out that the easiest way (TMO) is to simply add it as a query-string parameter to the redirection url of the rails app. Wasy and clean.
  • Elocution Safari
    Elocution Safari over 8 years
    This is useful information - just what I was looking for. While I'm sure most people can't change domains around, that's what I'm going to do. Have the site I'm embedding in create a <mycompany>.<theircompany>.com subdomain that maps to my IP and a VHost on my side for that domain and use that URL in the iFrame. Complicated, but should be bulletproof.
  • Roger Halliburton
    Roger Halliburton over 8 years
    This can get complicated if you are using https with the subdomain, since the subdomain server will need an SSL certificate to match.
  • Michael Baldry
    Michael Baldry over 8 years
    Need to add a second parameter "*" to PostMessage to fix the syntax error
  • AaronP
    AaronP about 8 years
    Wish I could give this answer more upvotes. It's the simplest and most correct way to actually address this issue. Thank you for posting it!
  • phylae
    phylae almost 8 years
    This answer creates an open redirector, which is a common security problem. The dangerous code is redirect_to params[:return_to]. That param needs to be checked against a whitelist of safe places to redirect to. See owasp.org/index.php/…
  • mb21
    mb21 over 7 years
    but this results in a redirect and then reload of the entire parent page, right? Somehow the facebook like-button plugin manages to do without, even if third-party cookies are disabled...
  • cheack
    cheack almost 5 years
  • Vasil Popov
    Vasil Popov about 3 years
    this actually worked very well, thanks! I didnt added any checks for browsers since there are a lot browsers actually and one redirect is not a problem