Недавно в работе с одним из наших клиентов мы столкнулись с проблемой в пользовательском сценарии: 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. Вот как это работает:

  1. В настройках приложения на стороне VK указываем один единственный redirect_uri.

  2. Клиент начинает авторизацию, перенаправляется на сайт VK в новом окне, это окно вызывается с помощью window.open. На странице, которая инициирует создание окна, создается канал обмена сообщениями:
    const bc = new BroadcastChannel('vk_auth_channel');
    И устанавливается прослушка этого канала на получение сообщений:
    bc.onmessage = event => { console.log(event.data); }

  3. На странице, куда VK перенаправит пользователя (страница из redirect_uri), будет скрипт. Он создаст такой же канал, но уже для отправки сообщений. Из параметров достаем код или ошибку, отправляем по каналу vk_auth_channel статус, code:
    bc.postMessage({status: true, code});
    Или ошибку:
    bc.postMessage({status: false, errors});

  4. После чего закрываем окно. Полученный code можно отправлять на бэкенд для обработки.

Посмотреть на полный процесс авторизации через VK вместе с бэкендом можно в этой хорошей статье. Вместе с описанным выше подходом вы получите авторизацию через ВК без привязки к странице. Это полезно, чтобы не прерывать пользовательский сценарий и случайно не устроить взрывной отток юзеров.

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


  1. Kenya-West
    25.10.2022 14:09
    +1

    А что вы будете делать, если при авторизации через OAuth произойдёт не открытие вкладки или окна, а просто переход по ссылке? Всё будет происходить в той же вкладке. Я такое много раз видел на многих сайтах, но сходу, к сожалению, не вспомню


    1. FMakareev
      25.10.2022 14:33
      +2

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


  1. y4ppieflu
    25.10.2022 14:39
    +1

    OAuth2/OpenID Connect поддерживает параметр state, который, в том числе, можно использовать для хранения состояния сессии - https://auth0.com/docs/secure/attack-protection/state-parameters#redirect-users


    1. FMakareev
      25.10.2022 15:13
      +1

      Задача была сделать так чтобы после авторизации пользователь на той же странице продолжил работать, если даже какие то параметры передавать в state то для каждого кейса они будут разными и это ад для тестирования + это не решит проблемы когда нужно по логике глубоко провалиться в страницу чтобы продолжить с того же шага.


      1. y4ppieflu
        25.10.2022 15:29

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

        Вы в вопросах авторизации городите не очень стандартную историю, передавая чувствительный код авторизации через postMessage. Ну ок, надеюсь, вы позаботились о том, чтобы он как минимум не утекал на чужие origin'ы.


        1. FMakareev
          25.10.2022 15:44
          +1

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