Safari 3rd party cookie iframe trick no longer working?
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
gs hurley
Updated on July 08, 2022Comments
-
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 about 12 yearsI 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 about 12 yearsApparently 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 about 12 yearsSo 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 about 12 yearsAre 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 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 about 12 yearsTry 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 about 12 yearsHmmm, I can't get the P3P headers to work either. They still work in IE though!
-
vwoelm about 12 yearsI was thinking of something like that. Works flawlessly. I load it along with the permission dialog. Thanks!
-
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 about 12 yearsThis used to work. But for the most recent version of Safari it does not. Clear your cache and try it again....
-
LocalPCGuy about 12 yearsSeems 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 almost 12 yearsThis 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 almost 12 yearsthis worked for me!! thanks!! .. i was having this problem with Safari 5.1.7.... now it's solved!
-
Chris Disley almost 12 yearsDefinitely not working with 5.1.7 (7534.57.2) with the P3P headers.
-
SamiSalami over 11 yearsThank you, works like a charm and is so easy to implement in existing apps :-)
-
Tom Kincaid over 11 yearsDoes 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 over 11 yearsthis worked well; fortunately, the pushing of a button to initialize this wasn't a big deal in my app.
-
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 over 11 yearsSo this one basically works with a couple of redirects, right?
-
Diogo Raminhos over 11 yearsExactly @AaronGibralter, it makes two redirects when needed. The main idea is to avoid requiring a user click.
-
Aaron Gibralter over 11 yearsMakes 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 over 11 yearsGreat 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 over 11 yearsI want to state that this solution works. make sure you DELETE ALL cookies on safari. And then try this.
-
hugo der hungrige over 11 yearsThank you very much! I've been searching for something like this for hours!
-
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 over 11 yearsThe 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 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 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 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 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 over 11 yearsThanks again @hugoderhungrige! :) Glad to know I could help someone.
-
TheRightChoyce over 11 yearsI'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 over 11 yearsHello @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 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 about 11 yearsIs there any way to detect if the navigator is accepting third party cookies using php and redirect user only it doesn't ?
-
Rails beginner about 11 yearsIs there a similar problem with sessions in safari?
-
Gonzalingui almost 10 yearsThank you! You saved my weekend :)
-
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 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 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 over 9 years@Whiteagle Can you provide a test case demonstrating 1 and 2?
-
Luc Boissaye over 9 yearsyou can replace set_your_return_url to request.env['HTTP_REFERER'] as we are inside an iframe :-D
-
akousmata about 9 yearsYou are not getting a syntax error with your call to postMessage?
-
idejuan almost 9 yearsI 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 almost 9 yearsIt 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 over 8 yearsThis 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 over 8 yearsThis can get complicated if you are using https with the subdomain, since the subdomain server will need an SSL certificate to match.
-
Michael Baldry over 8 yearsNeed to add a second parameter
"*"
toPostMessage
to fix the syntax error -
AaronP about 8 yearsWish 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 almost 8 yearsThis 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 over 7 yearsbut 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 almost 5 yearsSnapshot from weabrchive: web.archive.org/web/20121124020234/http://log.scalemotion.com/…
-
Vasil Popov about 3 yearsthis 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