Кроссдоменный postMessage или как браузеры поддерживают стандарты

от автора

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

Учитывая аудиторию использующую софтину, было решено отказаться от поддержки древних браузеров, и всё затачивалась под современные браузеры, использующие HTML5, которые казалось бы уже вполне неплохо и одинаково поддерживают страндарты.

Но не тут-то было…

Толкование postMessage разными браузерами

Как оказалось кроссдоменное (когда страницы с разных доменов) общение между окнами/вкладками браузера с помощью postMessage возможно только в Chrome и Firefox (проверено в Chrome 30 и Firefox 25).

В IE postMessage работает только при использовании frame/iframe (включая IE 11).

Opera неожиданно удивила. В версии 12 всё работает нормально. А вот в Opera 17, которая сделана на том же движке, что и Chrome, ведет себя по другому. Если домены отличаются, то postMessage работает только в frame/iframe, и не работает в отдельных окнах/вкладках. Если домены, точнее origin (т.е. протокол + домен + порт) совпадают, то тогда работают сообщения, как между фреймами, так и между окнами/вкладками.

Казалось бы, если все браузеры поддерживают кроссдоменное общение между фреймами, так почему бы сразу в приложении не открыть окно для авторизации в iframe? Но тут подножку ставит заботливый CORS – практически на всех OAuth серверах запрещено открытие страницы авторизации в фрейме.

Прокси-фрейм

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

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

Для удобства используем следующие определения:

  1. Приложение (web-приложение расположено на домене пользователя, о котором заранее неизвестно)
  2. Прокси-фрейм (промежуточный фрейм, расположенный на сервере приложения, встроенный с помощью iframe в приложение)
  3. Сайт приложения (сайт на который переадресуется пользователь после OAuth авторизации)
  4. OAuth-сервер (сервер на который нужно получить доступ)

Итого у нас используется 3 домена: domain1 – где находится приложение пользователя, domain2 — прокси-фрейм и сайт приложения, domain3 — OAuth-сервер.

Процедура получения токена выглядит так:

  1. На странице приложения встраивается прокси-фрейм.
  2. После нажатия кнопки расположенной в прокси-фрейме, открывается страница авторизации OAuth-сервера в новом окне/вкладке.
  3. После согласия пользователя, OAuth-сервер отправляет пользователя на Сайт приложения, с кодом авторизации в параметрах запроса.
  4. Сайт приложения делает POST запрос к OAuth-серверу, и обменивает код авторизации и пароль приложения на токен для доступа и/или токен для обновления.
  5. Полученный токен вставляется в текстовое поле (на случай если не удастся скопировать его автоматически). После чего токен отправляется в прокси-фрейм, с помощью изменения location.hash (этот работает во всех браузерах, так как у прокси-фрейма и сайта приложения одинаковый протокол, домен, порт). По сути, использование location.hash нужно только для IE (в том числе в IE 11), так как в нем postMessage не работает в разных окнах.
  6. Прокси-фрейм с помощью события onhashchange сразу после изменения хэша отправляет токен уже с помощью postMessage (так как домены теперь разные) и закрывает открытое окно/вкладку, открытое для авторизации.
  7. Приложение с помощью события onmessage получает токен и уже использует по назначению.

Схематическое изображение

Не спец я, правда, по красивым схемам. Но надеюсь будет понятно.

Демонстрация

На этой странице вы можете посмотреть живой пример, разве что сама OAuth-авторизация имитируется, чтобы не задергали реальный сервер.

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

Что еще почитать про postMessage

learn.javascript.ru/cross-window-messaging-with-postmessage
javascript.ru/ajax/cross-origin-2
html5demos.com/postmessage2
habrahabr.ru/post/120336/
caniuse.com/#search=postmessage

P.S. Если заинтересует могу выложить в архиве исходники скриптов из демки.

ссылка на оригинал статьи http://habrahabr.ru/post/197038/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *