Safari сторонний cookie-трюк iframe больше не работает?

Таким образом, это уже десятый вопрос о том, "как заставить сторонние куки работать в Safari", но я спрашиваю еще раз, потому что думаю, что игровое поле изменилось, возможно, после февраля 2012 года. Один из стандартных приемов, чтобы получить 3-ий Партийные куки в Safari выглядели следующим образом: используйте некоторый javascript для POST для скрытого фрейма. Он (раньше) обманывал Safari, думая, что пользователь взаимодействовал с контентом третьих сторон, и поэтому позволял устанавливать файлы cookie.

Я думаю, что эта лазейка была закрыта после скандала, где выяснилось, что Google использовал этот трюк в своих объявлениях. По крайней мере, при использовании этого трюка мне не удалось установить файлы cookie в Safari. Я обнаружил несколько случайных сообщений в Интернете, в которых утверждалось, что Apple работает над закрытием лазейки, но я не нашел ни одного официального слова.

В качестве запасного варианта я даже попытался изменить дизайн основного стороннего фрейма, чтобы вам пришлось нажимать на кнопку до загрузки контента, но даже такого уровня прямого взаимодействия было недостаточно, чтобы растопить холодное сердце Safari.

Так кто-нибудь знает наверняка, действительно ли Safari закрыл эту лазейку? Если да, есть ли другие обходные пути (кроме ручного включения идентификатора сеанса в каждый запрос)?

19 ответов

Просто хотел оставить здесь простое рабочее решение, которое не требует взаимодействия с пользователем.

Как я сказал в посте, я сделал:

По сути, все, что вам нужно сделать, это загрузить свою страницу в top.location, создать сеанс и перенаправить его обратно на Facebook.

Добавьте этот код в верхней части вашего index.php и установить $page_url к последней вкладке вашего приложения / URL приложения, и вы увидите, что ваше приложение будет работать без проблем.

<?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
?>

Примечание: это было сделано для facebook, но на самом деле это будет работать в любых других подобных ситуациях.


Изменить 20 декабря 2012 г. - Ведение подписанного запроса:

Приведенный выше код не поддерживает данные публикации запросов, и вы потеряете подписанный запрос, если ваше приложение использует подписанный запрос, не стесняйтесь попробовать следующий код:

Примечание: это все еще проверяется должным образом и может быть менее стабильным, чем первая версия. Используйте на свой страх и риск / Обратная связь приветствуется.

(Спасибо CBroe за то, что он указал мне правильное направление, что позволило улучшить решение)

// 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

Вы сказали, что хотите, чтобы пользователи нажимали кнопку до загрузки контента. Мое решение состояло в том, чтобы кнопка открывала новое окно браузера. Это окно устанавливает cookie для моего домена, обновляет программу открытия и затем закрывается.

Таким образом, ваш основной скрипт может выглядеть так:

<?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 ?>

Тогда safari_cookie_fix.php выглядит так:

<?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>

Я обманул Safari с помощью.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>

И это перестало работать для меня тоже. Все мои приложения теряют сеанс в Safari и перенаправляются из Facebook. Поскольку я спешу починить эти приложения, я сейчас ищу решение. Я буду держать вас в курсе.

Изменить (2012-04-06): Очевидно, Apple "исправил" это с 5.1.4. Я уверен, что это реакция Google: "Существовала проблема с применением политики использования файлов cookie. Сторонние веб-сайты могли устанавливать файлы cookie, если для параметра" Блокировать файлы cookie "в Safari было установлено значение по умолчанию" " От третьих лиц и рекламодателей ". http://support.apple.com/kb/HT5190

Для моей конкретной ситуации я решил проблему с помощью window.postMessage() и исключил любое взаимодействие с пользователем. Обратите внимание, что это будет работать только в том случае, если вы можете каким-либо образом выполнить js в родительском окне. Либо с включением JS из вашего домена, либо если у вас есть прямой доступ к источнику.

В iframe (domain-b) я проверяю наличие cookie и, если он не установлен, отправляет postMessage родителю (domain-a). Например;

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: {} }));
}

Затем в родительском окне (домен-а) прослушайте событие.

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;
    }
}

Наконец, на вашем сервере (http://www.domain-b.com/safari/cookiefix) вы устанавливаете cookie и перенаправляете обратно туда, откуда пришел пользователь. Ниже приведен пример использования 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/");
    }

}

В вашем контроллере Ruby on Rails вы можете использовать:

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

У меня была такая же проблема, и сегодня я нашел исправление, которое отлично работает для меня. Если пользовательский агент содержит Safari и куки не установлены, я перенаправляю пользователя в диалог OAuth:

<?php if ( ! count($_COOKIE) > 0 && strpos($_SERVER['HTTP_USER_AGENT'], 'Safari')) { ?>
<script type="text/javascript">
    window.top.location.href = 'https://www.facebook.com/dialog/oauth/?client_id=APP_ID&redirect_uri=MY_TAB_URL&scope=SCOPE';
</script>
<?php } ?>

После проверки подлинности и запроса разрешений диалог OAuth будет перенаправлен на мой URI в верхнем расположении. Так что установка куки возможна. Для всех наших приложений для холста и вкладок я уже включил следующий скрипт:

<script type="text/javascript">
    if (top.location.href==location.href) top.location.href = 'MY_TAB_URL';
</script>

Таким образом, пользователь снова будет перенаправлен на вкладку страницы Facebook с уже установленным допустимым файлом cookie и подписанный запрос будет опубликован снова.

Я наконец-то выбрал решение, подобное тому, которое предоставил Саша, но с небольшими изменениями, так как я явно устанавливаю куки в PHP:

// excecute this code if user has not authorized the application yet
// $facebook object must have been created before

$accessToken = $_COOKIE['access_token']

if ( empty($accessToken) && strpos($_SERVER['HTTP_USER_AGENT'], 'Safari') ) {

    $accessToken = $facebook->getAccessToken();
    $redirectUri = 'https://URL_WHERE_APP_IS_LOCATED?access_token=' . $accessToken;

} else {

    $redirectUri = 'https://apps.facebook.com/APP_NAMESPACE/';

}

// generate link to auth dialog
$linkToOauthDialog = $facebook->getLoginUrl(
    array(
        'scope'         =>  SCOPE_PARAMS,
        'redirect_uri'  =>  $redirectUri
    )
);

echo '<script>window.top.location.href="' . $linkToOauthDialog . '";</script>';

Это проверяет, доступен ли файл cookie, когда браузер работает в режиме сафари. На следующем шаге мы находимся в домене приложения, а именно в URI, указанном выше как URL_WHERE_APP_IS_LOCATED.

if (isset($_GET['accessToken'])) {

    // cookie has a lifetime of only 10 seconds, so that after
    // authorization it will disappear
    setcookie("access_token", $_GET['accessToken'], 10); 

} else {

  // depending on your application specific requirements
  // redirect, call or execute authorization code again
  // with the cookie now set, this should return FB Graph results

}

Таким образом, после перенаправления в домен приложения, cookie устанавливается явно, и я перенаправляю пользователя в процесс авторизации.

В моем случае (так как я использую CakePHP, но он должен хорошо работать с любой другой платформой MVC), я снова вызываю действие входа в систему, где авторизация FB выполняется в другой раз, и на этот раз это происходит успешно из-за существующего файла cookie.

После авторизации приложения у меня больше не было проблем с использованием приложения с Safari (5.1.6)

Надеюсь, что это может кому-нибудь помочь.

У меня была эта проблема на устройствах под управлением iOS. Я сделал магазин, который можно встраивать в обычный веб-сайт, используя iframe. Так или иначе, при каждой загрузке страницы пользователь получает новый идентификатор сеанса, в результате чего пользователи застряли на полпути процесса, потому что некоторые значения не присутствовали в сеансе.

Я попробовал некоторые решения, представленные на этой странице, но всплывающие окна не очень хорошо работают на iPad, и мне нужно было самое прозрачное решение.

Я решил это с помощью перенаправления. Веб-сайт, который встраивает мой сайт, должен сначала перенаправить пользователя на мой сайт, поэтому верхний фрейм содержит URL-адрес моего сайта, где я устанавливаю cookie и перенаправляю пользователя на соответствующую страницу на веб-сайте, которая встраивает мой сайт. через в URL.

Пример кода PHP

Удаленный сайт перенаправляет пользователя на

http://clientname.example.com/init.php?redir=http://www.domain.com/shop/frame

init.php

<?php
// set a cookie for a year
setcookie('initialized','1',time() + 3600 * 24 * 365, '/', '.domain.com', false, false);
header('location: ' . $_GET['redir']);
die;

Пользователь заканчивается http://www.domain.com/shop/frame где мой сайт встроен, хранит сессии как надо и ест куки.

Надеюсь, это кому-нибудь поможет.

Позвольте мне поделиться своим исправлением в ASP.NET MVC 4. Основная идея, как в правильном ответе на PHP. Следующий код добавлен в основной макет в заголовке рядом с разделом скриптов:

@if (Request.Browser.Browser=="Safari")
{
    string pageUrl = Request.Url.GetLeftPart(UriPartial.Path);
    if (Request.Params["safarifix"] != null && Request.Params["safarifix"] == "doSafariFix")
    {
        Session["IsActiveSession"] = true;
        Response.Redirect(pageUrl);
        Response.End();
    }
        else if(Session["IsActiveSession"]==null)
    {
        <script>top.window.location = "?safarifix=doSafariFix";</script>
    }
}

Это решение применяется в некоторых случаях - если возможно:

Если страница содержимого iframe использует поддомен страницы, содержащей iframe, файл cookie больше не блокируется.

Google фактически выпустил кошку из сумки на этом. Некоторое время они использовали его для доступа к файлам cookie отслеживания. Это было исправлено почти сразу Apple =\

оригинальная запись в Wall Street Journal

Немного более простая версия в PHP того, что опубликовали другие:

if (!isset($_COOKIE, $_COOKIE['PHPSESSID'])) {
    print '<script>top.window.location="https://example.com/?start_session=true";</script>';
    exit();
}

if (isset($_GET['start_session'])) {
    header("Location: https://apps.facebook.com/YOUR_APP_ID/");
    exit();
}

Вот код, который я использую. Я обнаружил, что если я установлю какой-либо файл cookie с моего сайта, с тех пор файлы cookie будут волшебным образом работать в iframe.

http://developsocialapps.com/foundations-of-a-facebook-app-framework/

 if (isset($_GET['setdefaultcookie'])) {
        // top level page, set default cookie then redirect back to canvas page
        setcookie ('default',"1",0,"/");
        $url = substr($_SERVER['REQUEST_URI'],strrpos($_SERVER['REQUEST_URI'],"/")+1);
        $url = str_replace("setdefaultcookie","defaultcookieset",$url);
        $url = $facebookapp->getCanvasUrl($url);
        echo "<html>\n<body>\n<script>\ntop.location.href='".$url."';\n</script></body></html>";
        exit();
    } else if ((!isset($_COOKIE['default'])) && (!isset($_GET['defaultcookieset']))) {
        // no default cookie, so we need to redirect to top level and set
        $url = $_SERVER['REQUEST_URI'];
        if (strpos($url,"?") === false) $url .= "?";
        else $url .= "&";
        $url .= "setdefaultcookie=1";
        echo "<html>\n<body>\n<script>\ntop.location.href='".$url."';\n</script></body></html>";
        exit();
    }

Я нашел идеальный ответ на это, все благодаря парню по имени Аллан, который заслуживает всяческих похвал здесь. ( http://www.allannienhuis.com/archives/2013/11/03/blocked-3rd-party-session-cookies-in-iframes/)

Его решение простое и легкое для понимания.

На сервере содержимого iframe (домен 2) добавьте файл startsession.php на уровне корневого домена, который содержит:

<?php
// startsession.php
session_start();
$_SESSION['ensure_session'] = true;
die(header('location: '.$_GET['return']));

Теперь на веб-сайте верхнего уровня, содержащем iframe (domain1), вызов страницы, содержащей iframe, должен выглядеть следующим образом:

<a href="https://domain2/startsession.php?return=http://domain1/pageWithiFrame.html">page with iFrame</a>

И это все! Просто:)

Причина, по которой это работает, заключается в том, что вы направляете браузер на сторонний URL-адрес и тем самым указываете ему доверять ему, прежде чем показывать содержимое из него в iframe.

Я использовал измененный (добавленный в ссылку параметр Param_request) трюк Whiteagle, и он работал нормально для сафари, но в этом случае IE постоянно обновляет страницу. Таким образом, мое решение для сафари и Internet Explorer:

$fbapplink = 'https://apps.facebook.com/[appnamespace]/';
$isms = stripos($_SERVER['HTTP_USER_AGENT'], 'msie') !== false;

// safari fix
if(! $isms  && !isset($_SESSION['signed_request'])) {

    if (isset($_GET["start_session"])) {
        $_SESSION['signed_request'] = $_GET['signed_request'];
        die(header("Location:" . $fbapplink ));

    }
    if (!isset($_GET["sid"])) {
        die(header("Location:?sid=" . session_id() . '&signed_request='.$_REQUEST['signed_request']));
    }
    $sid = session_id();
    if (empty($sid) || $_GET["sid"] != $sid) {
    ?>
    <script>
        top.window.location="?start_session=true";
    </script>
    <?php
    exit;
    }
}

// IE fix
header('P3P: CP="CAO PSA OUR"');
header('P3P: CP="HONK"');


.. later in the code

$sr = $_REQUEST['signed_request'];
if($sr) {
        $_SESSION['signed_request'] = $sr;
} else {
        $sr = $_SESSION['signed_request'];
}

Я также страдал от этой проблемы, но наконец-то получил решение: сначала непосредственно загрузить URL-адрес iframe в браузере, например, маленькое всплывающее окно, а затем получить доступ только к значениям сеанса внутри iframe.

Некоторый контекст, который я не видел четко изложенного в существующих ответах (а также многое изменилось с 2012 года!):

Если вы можете управлять как сторонним iframe, так и родительской страницей (т. Е. Вы можете вставить JavaScript на родительскую страницу), тогда доступны несколько обходных путей. Я бы предположил, что наиболее элегантным из них является использование API postMessage, как описано в ответе @Frank, поскольку а) это не требует перенаправления и б) не требует взаимодействия с пользователем.

Если вы НЕ управляете как сторонним iframe, так и родительской страницей, например, у вас есть виджет, размещенный на сайте, который вы не контролируете, то большинство опубликованных здесь ответов не будут работать в Safari с мая 2020 года и перестанут работать. в Chrome около 2022 года. То есть, если пользователь уже не посещал ваш домен или не взаимодействовал с iframe, вы не можете устанавливать файлы cookie. Однако есть некоторые коммерческие службы, предлагающие решения этой проблемы, например https://cloudcookie.io/.

Safari теперь блокирует все сторонние файлы cookie. Вы можете использовать API хранилища только для того, чтобы попытаться получить доступ пользователей к их сторонним файлам cookie.

https://www.infoq.com/news/2020/04/safari-third-party-cookies-block/

Я решил избавиться от $_SESSION Переменная все вместе и написал обертку вокруг memcache для имитации сеанса.

Проверьте https://github.com/manpreetssethi/utils/blob/master/Session_manager.php

Вариант использования: в тот момент, когда пользователь попадает в приложение, сохраняет подписанный запрос с помощью Session_manager и, поскольку он находится в кеше, вы можете получить к нему доступ на любой странице впредь.

Примечание: это не будет работать при приватном просмотре в Safari, так как session_id сбрасывается каждый раз при перезагрузке страницы. (Глупое Сафари)

Я недавно столкнулся с той же проблемой на Safari. Решение, которое я понял, основано на HTML5 API локального хранилища. Используя локальное хранилище, вы можете эмулировать куки.

Вот мой пост в блоге с подробностями: http://log.scalemotion.com/2012/10/how-to-trick-safari-and-set-3rd-party.html

Вы можете решить эту проблему, добавив заголовок в качестве политики p3p. У меня была такая же проблема в сафари, поэтому после добавления заголовка поверх файлов моя проблема была решена.

<?php
header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
?>
Другие вопросы по тегам