Пользователь
0,0
рейтинг
18 января 2011 в 15:53

Cookies внутри iframe — проблема при создании приложения ВКонтакте/Facebook из песочницы

Управление сессиями при помощи посылки cookies на сайтах стало настолько распространено, что без этого не обходится, наверное, ни один проект, требующий авторизации пользователей. Казалось бы, механизм настолько изучен, что проблемы с ним просто немыслимы.

Так считали и мы при разработке iframe-приложения для ВКонтакте.
Но после того, как приложение было разработано (разработка велась в основном в Mozilla Firefox и Google Chrome), выяснилось, что оно неработоспособно в Internet Explorer, к которому позднее присоединились последние версии Opera и Safari.

Под катом подробное описание проблемы и вариантов её решения.

Описание проблемы


Первым делом было установлено, что cookies успешно передаются сервером и принимаются браузером, но тот, в свою очередь, не сохраняет их и не передаёт назад на сервер вместе со следующими запросами.

Поведение казалось странным, однако оно имеет под собой объективное обоснование: браузер обеспечивает приватность просмотра страниц и не принимает cookies от страниц внутри iframe.

Приведу пример.
Предположим, что вы подбираете себе ноутбук и читаете web-страницы с бенчмарками и различными сравнениями ноутбуков. На каких-то из этих страниц, наверняка, будет установлен Google AdSense или какая-то другая подобная система, имеющая возможность послать вам уникальное значение посредством cookies и далее идентифицировать вас по нему. Позже, читая новости, документацию по вашему любимому фреймворку или участвуя в обсуждениях на форумах, вы будете удивлены тем, что в рекламе вам показываются предложения о покупке ноутбуков. А, тем временем, система, размещающая рекламу, повысит кому-то CTR. ;)

Браузеры пытаются защитить нас от подобного рода сбора информации и запрещают приём cookies от страниц внутри iframe.

Но мы никак не связаны с рекламой и сбором сведений, поэтому рассмотрим варианты решения проблемы.

Cookie в iframe и Internet Explorer


В нашем случае решение для Internet Explorer не заставило себя ждать: проблема рассматривалась множество раз и ответы быстро находятся в гугле.

Решением является посылка HTTP-заголовка следующего содержания:
P3P: CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"

В частности для PHP это будет выглядеть так:
<?php
header('P3P: CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');


Cookie в iframe и Opera/Safari


Проблема в этих браузерах появилась не так давно, и поэтому идентифицировать и решить её оказалось немного сложнее.

Суть проблемы в том, что в последних версиях этих браузеров по умолчанию стоят галочки «Сookies: Принимать только с посещаемого сайта» (Opera) и «Принимать cookies: Только от посещаемых сайтов» (Safari)

Очевидным решением проблемы является изменения режима приёма cookies на «всегда». Однако в большинстве случаев «попросить» сделать это всех пользователей не представляется возможным.

Поэтому попытаемся рассмотреть другие способы решения проблемы.

Способ 1. Splash Screen вместо первой страницы

Более детальное рассмотрение проблемы показало, что cookies не устанавливаются до каких-либо действий пользователя. После того, как пользователь перейдёт на другую страницу в iframe, кликнув по какой-либо ссылке, cookies начинают успешно приниматься.

Стоит обратить внимание на то, что не помогает эмуляция перехода на другую страницу. А именно:
  • редирект при помощи HTTP-заголовка Location
  • редирект при помощи соответствующего meta-тега
  • редирект при помощи изменения window.location из JavaSctipt'а
  • редирект при помощи вызова из JavaScript метода click() у ссылки

Очевидное решение: вместо реального сайта первой страницей выводить пользователю приветствие со ссылкой «перейти к приложению».

Достоинства:
+ простота, пуленепробиваемость
+ независимость от наличия включённого JavaScript в браузере

Недостатки:
— требует дополнительного действия от пользователя
— несолидность (лишняя страница с «непонятным» приветствием)

Способ 2. Редирект при помощи отправки формы

В перечисленных мною способах эмуляции перехода неспроста отсутствует редирект при помощи отправки формы.

Суть метода:
  • в iframe создаётся форма, action которой ведёт на нужную страницу текущего домена
  • форма отправляется посредством JavaScript

И этот метод работает: скрипт, расположенный по адресу action'а формы(и последующие вызываемые скрипты), успешно установит cookies.

При этом не имеет значения тот факт, будет ли отправлена форма методом GET или POST.

Пример реализации с использованием jQuery:
<script type="text/javascript">
$(function(){
	$('body').append('<form id="cookiesHackForm" action="http://example.com/" method="get"></form>');
	$('#cookiesHackForm').submit();
});
</script>


Достоинства:
+ не требует действий от пользователя

Недостатки:
— требуется JavaScript
— требуется пустая страница только лишь для редиректа

Способ 3. Отправка формы в фоне при помощи дополнительного iframe

Является развитием второго способа, в котором ликвидируется недостаток в наличии редиректа.

Для того, чтобы избежать наличия лишней пустой страницы для редиректа, мы можем создать дополнительный iframe, в который будем отправлять форму, описанную выше. Тем самым мы покажем пользователю какую-то полезную страницу, а cookies установим «в фоне».

Пример реализации с использованием jQuery:
<script type="text/javascript">
$(function(){
	$('body').append('<iframe id="cookiesHackFrame" name="cookiesHackFrame" src="http://example.com/blank.html" style="display:none;"></iframe>');
	$('body').append('<form id="cookiesHackForm" action="http://example.com/" method="post" target="cookiesHackFrame" >');
	$('#cookiesHackForm').submit();
});
</script>


Достоинства:
+ не требует действий от пользователя
+ не требуется редирект

Недостатки:
— требуется JavaScript
— не имеет смысла там, где вывод страницы должен произвестись только после установки cookies

Заключение


Браузеры заботятся о нашей приватности, но наличие обходных путей заставляет задуматься – баг ли эти пути или фича. В первом случае через некоторое время после соответствующих заплаток придётся отказаться от решения проблем такими способами. Но, пока они работают, – нет причин для отказа от пользования ими.

Ссылки по теме


http://www.w3.org/TR/P3P/#guiding_principles
http://stackoverflow.com/questions/389456/cookie-blocked-not-saved-in-iframe-in-internet-explorer
http://stackoverflow.com/questions/2691864/facebook-iframe-app-with-multiple-pages-in-safari-session-variables-not-persistin
http://developers.facebook.com/docs/best-practices#miscellaneous-issues
http://anantgarg.com/2010/02/18/cross-domain-cookies-in-safari/
http://lightyearsoftware.com/2009/11/on-the-pain-of-developing-for-facebook/
http://javascript.ru/unsorted/id (thx seriyPS)

UPD: если кто-то подскажет более подходящий блог, то буду очень признателен.
UPD2: переместил в блог Браузеры по рекомендации. (thx agul)
@lu4e3ar
карма
16,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое

Комментарии (45)

  • 0
    На днях столкнулся с той же проблемой. В итоге, пришёл к третьему способу со скрытым iframe-ом.
    Способ с редиректом плох тем, что IE издаёт щёлкающий звук при каждом редиректе, что в моём случае было неприемлемо, поскольку iframe-ов на странице может быть несколько.
  • +5
    Спасибо, интересная статья.

    Уберите из названия ВКонтакте и Facebook. В посте нет почти ничего, связанного с социальными сетями. Переместите лучше в браузеры.
    • +1
      Спасибо за совет.
      Переместил в «Браузеры».

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

  • +1
    а как эту проблему решают разработчики других популярных приложений? Возможно, эта проблема возникла у вас, потому что вы делаете что-то неправильно?
    • 0
      В том то и дело, что, как выяснилось в итоге, это нормальное поведение браузеров.
      При установке галочки в такое положение (стоит в вышеперечисленных браузерах по дефолту) он перестаёт принимать кукисы от содержимого iframe'а (подразумевается, что содержимое принадлежит другому домену) до определённых действий пользователя внутри этого iframe (в частности до перехода по ссылке).

      А решение проблемы и было в таком порядке:
      1. в гугле быстрым поиском ничего не нашлось.
      2. а как же тогда у других то работает?
      3. ковыряли другие приложения, нашли одно из описанных в топике решений
      4. решил изучить тему глубже, тщательно погуглил, посмотрел как у других, поэксперементировал и написал по результатам этот хабратопик
      • 0
        я имел в виду, что, возможно, для приложений фейсбука/вконтакте предполагается другой механизм поддержания сессии, не через куки. Например, при первом запросе идентификатор пользователя есть в урле, а во втором — он есть как минимум в рефёрере. Если же немного напрячься, то ещё при показе первой страницы можно во все ссылки и формы напихать тот же идентификатор, и вуаля! он есть у вас безо всяких кук.

        то есть, возможно, вы ошибаетесь, предполагая, что нужно использовать сессию
        • 0
          Да, об этом способе мы так же думали, но решили оставить его в качестве крайнего варианта.

          Приложение делалось на основе сайта, и первостепенной задачей было именно его создание с минимальными переделками и трудозатратами. По вашему (и, возможно, другим подобным вариантам) переделывать пришлось бы больше.

          Плюс, как выяснилось в итоге, большинство приложений пользуются именно предложенными в хабратопике способами.
          • 0
            ясно. Странно, что API платформ никак не решают эту проблему
            • 0
              Как же не решают? У ВКонтакте есть возможность хранить данные на их сервере, как вариант. Чем не решение?
            • 0
              можно хранить информацию в localStorage вместо куков, когда код полностью под вашим контролем, но когда надо использовать чужой код/приложения использующие куки, то приходится так извращаться
          • 0
            У меня в IFrame приложении пока такая задача как у Вас не стоит, но для идентификации пользователей на уже загруженной странице через JS можно навесить номера сессий на все ссылки внутри страницы, и они при клике уйдут на сервер как дополнительный GET-параметр.
            • 0
              Да, можно.
              Но мы решили, что пусть уж извращение будет в одном месте, чем на каждой странице. =)
              И для Вашего способа нам тоже пришлось бы больше переделывать…
  • 0
    Моё приложение работает уже давно, и ни я, ни кто-либо из пользователей ни разу с такой проблемой не столкнулся…
    • 0
      А ссылкой поделитесь? =)
      • 0
        В личку скинул
        • 0
          Спасибо!

          В Safari 5.0.3 (Win) приложение так и не установило ни одного cookies'а.
          В Opera 11.00 (Linux) cookies устанавливаются, но не на первой странице.

          Насколько я понял, у Вас приложение вообще не требовательно к cookies, и сессии при помощи них у вас не ведутся. Скорее всего именно из-за этого Вы не имели проблем.
          • 0
            Да, как выяснилось, в моей опере стоит «принимать все куки».

            Рассматриваю вариант при старте делать AJAX запрос на другую страницу, которая будет исключительно ставить cookie. Может, пробовали?

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

            Логин реализовал просто — из GET-параметров ID пользователя и ключ записывается в JS-переменные, а дальше всем страницам он передаётся, и на каждой код начинается с проверки пользователя:
            
            if(md5($settings["id"].'_'.$_POST["user"].'_'.$settings["secret"])==$_POST["key"]){
            
            • 0
              Про способ с AJAX не уверен, что сработает.
              Третий способ из топика для сервера и для пользователя выглядит практически так же, как если бы это был AJAX.
              • +1
                Да, через AJAX не работает.
                Зато в Opera 11 (Linux) всё прекрасно ставится через JS даже с «принимать только с посещаемого узла». Интересно, это баг или фича?
                • 0
                  Тут какой способ не попробуешь, везде думаешь, баг ли это или фича…

                  Я считаю, что нужно пользоваться, пока работает. Если вдруг пофиксят, то у нас в запасе ещё способы есть. =)

                  P.S. А Вы попробуйте ещё в Safari протестировать.
            • 0
              По поводу «принимать все куки» хотел сказать, что, по-моему(специально не проверял, но по косвенным наблюдениям это так), когда обновляете Opera с более старых версий, то новые дефолтовые настройки не применяются. Поэтому если ничего не меняли ранее, то останется «принимать все куки».
              А вот при чистой установке по дефолту будет «только с посещаемого сайта».
  • 0
    В своё время столкнулся с такой проблемой под IE, тестируя уже разработанное приложение для Вконтакте. Решение нагуглил довольно быстро, но за пост отдельное спасибо. Помнится, тогда такая ситуация поставила меня в ступор минут так на 10.
    • 0
      Аналогично: пока не разобрались в теме, находились в ступоре и не могли понять, почему не работает.
  • 0
    Cookie вроде не нужны приложениям вконтакте, так как вконтакте передает ид пользователя и там есть вконтавтовское хранилище для данных.

    А вообще, решение в браузерах бредовое — мало того, что 3-зя сторона может получить куки извращениями с отправкой формы, их банально можно получить добавлением скрипта на страницу, как это делает например Google Analitics (и который за такое поведение давно забанен в моей Опере).
  • +1
    javascript.ru/unsorted/id — тут Илья Кантор эти костыли и многие другие описывал
    • 0
      Хорошая ссылка, спасибо.
      Не против, если я её в «ссылки по теме» добавлю?
      • 0
        Ставьте конечно
  • 0
    Блин, ну почему нельзя было сделать нормально, для сайта куки свои, для айфрейма свои, и они никак не пересекаются :(
  • 0
    Являюсь разработчиком iframe приложений для ВКонтакте уже год.
    С этой проблемой столкнулись давно, костыли все пробовали, но в итоге пришли к выводу, что в iframe приложениях лучше не использовать куки.

    В основном куки нужны для авторизации, поэтому мы используем 100% рабочий метод — передаем параметры авторизации через URL.

    Вот так это выглядит: vkontakte.ru/app1905375

    — Небольшой оффтоп: вы используете ссылки в приложении? Если да, то скоро столкнетесь с проблемой, узнав что не одно iframe приложение ВКонтакте не может работать одинаково правильно во всех браузерах. За год разработчики ввели некоторые вещи, которые уменьшают возможность глюков, но до конца проблему не вылечили. Кнопки Назад и Вперед на данный момент вообще адекватно не работают, из-за проблем с методом onLocationChanged. Если будет время, напишу пост про это на хабре, может местные умельцы придумают более стабильный вариант.
    • 0
      Передавать сессию в URL — это либо через JS дописывать идентификатор, либо это же делать сразу же в html.
      В одном случае извращения не меньшие, чем описанные в топике, в другом — надо заранее это продумывать, иначе потом нужны будут трудозатраты на поиск и замену всех ссылок.
      Поэтому лично я считаю cookies'ы лучшим вариантом.

      Ссылками пользуемся, но пока передача в parent не до конца проработана. Приём с onLocationChanged вроде бы работает. Кроссбраузерность проверим. =)

      По поводу написания топика: было бы замечательно почитать, т.к. мы тоже используем/будем_использовать это.
      • +1
        > В одном случае извращения не меньшие, чем описанные в топике

        Да, но это вариант «в лоб», который 100% работает.
        А вот методы, описанные в топике, могут сломаться при очередной выходки браузера.

        Но все равно спасибо, полезный материал.
  • 0
    * Начиная использовать P3P следует серьезно задуматься над тем что конкретно будет в заголовках и каковы будут последствия если содержимое не будет соответствовать действительности. А то подадут на вас в суд по, вроде как, пустячному поводу и имей потом гемморой.

    Прикормить параною :)
  • 0
    Спасибо за решения.
    Смущает только то, что метод для ИЕ очень сильно смахивает на использование уязвимости.
  • 0
    как я уже написал выше вместо куков имеет смысл использовать localStorage. Он не имеет таких ограничений и нормально работает в iframe. Хотя конечно оперировать им придется при помощи javascript и для старых версий IE делать P3P заголовки и пользовать куки.
  • 0
    Спасибо за решение. Данная проблема оказывается и была у нас в loginza (при авторизации через Вконтакте). Теперь исправим используя ваш метод.
    • 0
      Какой из трёх (точнее из двух, т.к. очевидно не первый) выберете? :)
      • 0
        Третий вариант
      • 0
        Третий вариант не помог. Все равно в Safari странная ситуация с куки получается, настройки по умолчанию: принимать куки только с посещаемых сайтов.
        • 0
          Сначала подумал, что изменили что-то в новой версии, но сейчас обновился до 5.1 и посмотрел то приложение — работает.
          Может что-то неверно делаете?
          • 0
            Оказалось дело в другом было, в сафари почему то при авторизации через вконтакте, не ставится кука vk_app_ на наш домен. В итоге перешли на oAuth сегодня.
  • 0
    Возник вопрос, а как выглядит страничка example.com/blank.html, внутри что-то есть у нее или она просто пустая?
    • 0
      Содержимое не имеет значения. В частности можно поместить пустую страницу
  • 0
    А вот в FF 10 кажется из этих вариантов ничего не работает =(
    • 0
      FF 10.0.2 полет нормальный
  • 0
    У меня не работает способ #3 в Safari 5.1.7 на маке. Пришлось перебрасывать родительский фрейм на страницу установки куки, а с нее — обратно в приложение (Facebook Page Tab App):

    setcookie('cart_id', md5(uniqid(rand(), true)), time() + 604800);
    header('Location: www.facebook.com/xxx');

    Выглядит не очень, так как при первом просмотре идет заметный редирект на внешний сайт.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.