Пишем код

Заметки о .net разработке

Archive for the ‘asp.net core’ Category

OAuth в SPA или неожиданные сложности интеграции логина через соцсети в React с Asp.Net Core

without comments

Это история про то, как казалось бы типичная задача интеграции входа через соц.сети в React/Asp.Net Core приложении может превратиться в длинную сагу и закончиться open-source библиотекой :)

Если читать лень, то можно сразу пойти на гитхаб, где и посмотреть весёлую гифку и прочую документацию по интеграции и использованию, ну а здесь я расскажу чуть подробнее :)


В Asp.Net Core существует замечательная встроенная интеграция с внешними провайдерами аутентификации (OAuth/OpenId и прочее нестандартное), а также сторонние плагины, поддерживающие аутентификацию даже через VK. Однако весь этот механизм подразумевает, что у вас обычное server-side приложение (с forms-авторизацией), и никаких собственных access_token’ов, которые привычны в SPA вам генерироваться не будет.

Вот вот мне и загорелось желание воспользоваться всем этим огромным количеством готовых решений и подружить его с SPA. Как схема работы должна выглядеть в идеале? Согласно AuthCode Flow (который считается рекомендуемым для использования в SPA), это должно выглядеть примерно так:

SPA получает AuthCode у стороннего провайдера и передает его на бэкэнд приложения. Бэкэнд проверяет верность кода, ищет этого пользователя в своей БД (и создает, если требуется) и возвращает на фронтенд access_token, с которым и происходят все дальнейшие запросы.

Для первого шага (получения AuthCode) в SPA существуют готовые реализации в виде, например, реакт-компонентов. Но под каждого OAuth-провайдера они разные, и подбор и настройка могут отнять достаточно много времени. После интеграции написанной библиотеки IdentityOAuthSpaExtensions (и настройки бэкэнд-части согласно инструкциям от майкрософта), запрос AuthCode из SPA будет состоять из двух частей:

  1. Создание обработчиков и подписка на события:
  2.     window.addEventListener("message", this.oAuthCodeReceived, false);
        function oAuthCodeReceived(message) {
            if (message.data && message.data.type === 'oauth-result') {
                if (data.code) {
                    externalAuthSuccess(data.provider, data.code);
                } else {
                    externalAuthError(data.provider, data.error, data.errorDescription);
                }
            }
        }
        function externalAuthSuccess(provider, code) {
            alert(`Provider: ${provider}, code: ${code}`);
        }
        function externalAuthError(provider, error, errorDescription) {
              alert(`Provider: ${provider}, error: ${error}, ${errorDescription}`);
        }
    
  3. Старт процедуры авторизации:
   window.open(`${window.location.protocol}//${window.location.hostname}:${window.location.port}/external-auth/challenge?provider=${provider}`, undefined, 'toolbar=no,menubar=no,directories=no,status=no,width=800,height=600');

В результате этого ваше SPA получит AuthCode стороннего провайдера. В дальнейшем с ним можно делать что угодно ( :)), но в нашем случае, мы хотим получить access_token от нашего бэкэнда, чтобы в дальнейшем все http вызовы совершать с этим access_token’ом. Для этого в библиотеке существует возможность проверки AuthCode («), а также (рекомендуемая) интеграция с IdentityServer в виде extension grant’a. Описание интеграции очень подробно описано на гитхабе

Итоговая схема выглядит как-то так:

Из основных плюсов библиотеки:

  1. Единая точка входа и общий интерфейс интеграции любых Auth-провайдеров (не нужно менять SPA, меняется лишь одна переменная — имя провайдера — при открытии URL авторизации)
  2. Использование стороннего кода для взаимодействия с OAuth (саму библиотеку не придется обновлять, если в сторонних OAuth что-то изменится).
  3. Добавление новых провайдеров происходит стандартным способом (по инструкции для server-side приложений) и не требует модификации самой библиотеки

Пользуйтесь, задавайте вопросы и рассказывайте об успешных сценариях внедрения!

Written by Shaddix

Март 16th, 2019 at 4:55 дп