Недавно в работе с одним из наших клиентов мы столкнулись с проблемой в пользовательском сценарии: VK API требует конкретный, железный URL для редиректа после авторизации. А у нас были сотни ссылок с динамическими параметрами, с которым могла начаться авторизация.
Меня зовут Фёдор Макареев, я frontend-разработчик в Evrone. В статье расскажу, как я применил Broadcast Channel API, чтобы не терять состояние до авторизации и не бесить пользователей.
Пользователи полюбили SSO-авторизацию: не надо помнить десятки логинов-паролей, а сам процесс регистрации и входа в сервис теперь выглядит почти бесшовным. Сервис, в который нельзя зайти через аккаунт соцсетей или почты, сегодня выглядит несовременным, так что можно говорить о своего рода стандарте пользовательского сценария.
В нашем случае речь шла об авторизации через VK (с помощью authcode-flow-user), хотя описываемый метод применим, например, к Одноклассникам или другим соцсетям.
Как это должно работать
В процессе авторизации нужно было получить email пользователя от VK и другую полезную информацию и, в случае с регистрацией, передать на клиент в форму регистрации.
Мы заранее формируем ссылку для перехода в VK примерно такого вида:
https://oauth.vk.com/authorize?client_id=1&display=page&redirect_uri=http://example.com/callback&scope=friends&response_type=code&v=5.131
С нашего сайта клиент переходит по ссылке, предоставляет права на авторизацию с помощью своего VK аккаунта. VK объясняет пользователю, какие данные будут расшарены, получает подтверждение и перенаправляет на адрес, который мы указали в первом шаге redirect_uri=http://example.com/, предоставив нам code.
Этот код отправляется на бэкенд для получения информации о пользователе.
Проблема — форма авторизации может быть где угодно на сайте
Виджет авторизации/регистрации не привязан к конкретному адресу и может быть вызван с множества адресов. VK требует указать в настройках приложения все возможные redirect_uri. При этом, VK проверяет их на строгое сравнение и не даёт использовать динамические адреса.
Можно установить единую страницу для redirect_uri, на которой пользователь продолжит регистрацию. Тогда пользователь потеряет состояние страницы, с которой он ушел для регистрации/авторизации, и ему придется всё делать по новой.
Решение — слушаем процесс авторизации и сохраняем состояние пользователя
Я начал изучать, как вообще можно узнать, что происходит в соседней вкладке или окне. Вот, например, статья, которая даёт 4 возможных варианта: Local Storage Events, Broadcast Channel API, Service Worker Post Message и Window Post Message.
Local storage event использует промежуточное хранение данных в кэше, которым придется управлять вручную, это не так удобно. Service Worker Post Message — тоже не очень подходит для этой задачи, так как не дает явно настроить тип сообщения, которое я отправляю. Пришлось бы вручную добавлять в объект с данными поле type, а в месте, где я подписан на него, добавлять проверку значения этого поля. Window Post Message не подошёл по этой же причине.
Я использовал Broadcast Channel API для обмена сообщениями и полифил для него broadcast-channel. Вот как это работает:
В настройках приложения на стороне VK указываем один единственный redirect_uri.
Клиент начинает авторизацию, перенаправляется на сайт VK в новом окне, это окно вызывается с помощью window.open. На странице, которая инициирует создание окна, создается канал обмена сообщениями:
const bc = new BroadcastChannel('vk_auth_channel');
И устанавливается прослушка этого канала на получение сообщений:bc.onmessage = event => { console.log(event.data); }
На странице, куда VK перенаправит пользователя (страница из redirect_uri), будет скрипт. Он создаст такой же канал, но уже для отправки сообщений. Из параметров достаем код или ошибку, отправляем по каналу vk_auth_channel статус, code:
bc.postMessage({status: true, code});
Или ошибку:bc.postMessage({status: false, errors});
После чего закрываем окно. Полученный code можно отправлять на бэкенд для обработки.
Посмотреть на полный процесс авторизации через VK вместе с бэкендом можно в этой хорошей статье. Вместе с описанным выше подходом вы получите авторизацию через ВК без привязки к странице. Это полезно, чтобы не прерывать пользовательский сценарий и случайно не устроить взрывной отток юзеров.
Комментарии (6)
y4ppieflu
25.10.2022 14:39+1OAuth2/OpenID Connect поддерживает параметр state, который, в том числе, можно использовать для хранения состояния сессии - https://auth0.com/docs/secure/attack-protection/state-parameters#redirect-users
FMakareev
25.10.2022 15:13+1Задача была сделать так чтобы после авторизации пользователь на той же странице продолжил работать, если даже какие то параметры передавать в state то для каждого кейса они будут разными и это ад для тестирования + это не решит проблемы когда нужно по логике глубоко провалиться в страницу чтобы продолжить с того же шага.
y4ppieflu
25.10.2022 15:29Вам не нужно там хранить никакие параметры, в стейте хранится референс на объект на бэкенде, который может, например, содержать информацию о том, с какого адреса изначально произошел редирект на авторизацию.
Вы в вопросах авторизации городите не очень стандартную историю, передавая чувствительный код авторизации через postMessage. Ну ок, надеюсь, вы позаботились о том, чтобы он как минимум не утекал на чужие origin'ы.
FMakareev
25.10.2022 15:44+1Решение не стандартное, но даже если передать в state ссылку куда направить пользователя мы теряем состояние страницы на момент авторизации и пользователю приходится делать все по новой. Есть такой кейс, на странице пакета когда перед тем как приступить к оформлению заказа нужно пройти актуализацию пакета, этот процесс может быть не быстрым и только после него пользователь видит либо форму заполнения перс данных либо авторизацию, заставлять его проходить это по новой после авторизации очень не хочется.
Kenya-West
А что вы будете делать, если при авторизации через OAuth произойдёт не открытие вкладки или окна, а просто переход по ссылке? Всё будет происходить в той же вкладке. Я такое много раз видел на многих сайтах, но сходу, к сожалению, не вспомню
FMakareev
Открытие окна соц. сети происходит контролируемо, фронт формирует ссылку и открывает ее так как нужно. На случай если пользователь закроет окно социальной сети форма авторизации не блокируется и канал создается всегда строго 1 сколько бы клиент не кликнул на авторизацию через социальную сеть.