Во время прикручивания облачных хранилищ к скрипту для бэкапа, встала необходимость использовать 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.
После того как были испробованы различные варианты, я остановился на следующем довольно простом решении для получения токена.
Для удобства используем следующие определения:
- Приложение (web-приложение расположено на домене пользователя, о котором заранее неизвестно)
- Прокси-фрейм (промежуточный фрейм, расположенный на сервере приложения, встроенный с помощью iframe в приложение)
- Сайт приложения (сайт на который переадресуется пользователь после OAuth авторизации)
- OAuth-сервер (сервер на который нужно получить доступ)
Итого у нас используется 3 домена: domain1 – где находится приложение пользователя, domain2 — прокси-фрейм и сайт приложения, domain3 — OAuth-сервер.
Процедура получения токена выглядит так:
- На странице приложения встраивается прокси-фрейм.
- После нажатия кнопки расположенной в прокси-фрейме, открывается страница авторизации OAuth-сервера в новом окне/вкладке.
- После согласия пользователя, OAuth-сервер отправляет пользователя на Сайт приложения, с кодом авторизации в параметрах запроса.
- Сайт приложения делает POST запрос к OAuth-серверу, и обменивает код авторизации и пароль приложения на токен для доступа и/или токен для обновления.
- Полученный токен вставляется в текстовое поле (на случай если не удастся скопировать его автоматически). После чего токен отправляется в прокси-фрейм, с помощью изменения location.hash (этот работает во всех браузерах, так как у прокси-фрейма и сайта приложения одинаковый протокол, домен, порт). По сути, использование location.hash нужно только для IE (в том числе в IE 11), так как в нем postMessage не работает в разных окнах.
- Прокси-фрейм с помощью события onhashchange сразу после изменения хэша отправляет токен уже с помощью postMessage (так как домены теперь разные) и закрывает открытое окно/вкладку, открытое для авторизации.
- Приложение с помощью события 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. Если заинтересует могу выложить в архиве исходники скриптов из демки.
Автор: zapimir