Я только что потратил около недели усилий на то, чтобы выяснить, как это сделать, поэтому решил написать объяснение, чтобы сэкономить время будущих разработчиков.
Краткий (есть) ответ
Вам следует следовать этому руководству в документации GitHub («Авторизация приложений OAuth») с некоторыми дополнениями (описанными ниже), позволяющими ему работать в качестве метода аутентификации пользователя.
- Я реализовал «поток веб-приложений » для случаев, когда наше приложение будет развернуто на серверах нашей компании (где мы можем сохранить в секрете «секрет клиента» приложения GitHub нашей компании), а также « поток устройства» для случаев, когда наше приложение будет развернуто на компьютерах наших клиентов (потому что в этой ситуации мы не сможем сохранить в секрете наш «секрет клиента»).
- В руководстве GitHub не упоминаются приведенные ниже шаги (поскольку это руководство не предназначено специально для реализации входа через социальные сети), но чтобы заставить работать вход через социальные сети, я также сделал следующее:
- Я создал таблицу базы данных
users
с идеей, что каждая учетная запись GitHub, используемая для входа в систему, будет иметь свою собственную соответствующую строку в этой таблице.
- Я создал таблицу базы данных
oauth_tokens
для хранения копий всех токенов доступа GitHub, которые наша серверная часть получает от GitHub.
- Я попросил серверную часть отправить интерфейсной части (пользователю) токен доступа GitHub, чтобы он мог предоставлять будущие запросы в качестве механизма аутентификации.
- Интерфейс должен хранить токен
localStorage
, если вы хотите, чтобы пользователь оставался в системе даже после закрытия вкладки браузера, с которой он вошел в систему.
- Я добавил промежуточное программное обеспечение на серверную часть, которое для каждого входящего запроса просматривает предоставленный токен доступа в нашей базе данных, чтобы узнать, истек ли его срок действия, и если да, пытается его обновить. Если ему удается обновить токен, он выполняет запрос как обычно и включает новый токен доступа в ответ интерфейсу в настраиваемый заголовок ответа, за которым следит интерфейс (я назвал его
x-updated-access-token
). . Если ему не удается обновить токен, он прерывает запрос и отправляет ответ 401, который интерфейс воспринимает как сигнал для перенаправления пользователя на страницу входа.
- Настройка вашего приложения, позволяющая использовать в качестве метода аутентификации только токены доступа с истекшим сроком действия, необходима для того, чтобы пользователь мог удаленно выйти из приложения со своей страницы настроек на GitHub.com.
- Я добавил внешний код для обработки сохранения/обновления/удаления токена доступа GitHub как в/из localStorage, так и для всех запросов к серверной части, а также перенаправления на маршрут /login, если внешний интерфейс end не находит набор переменных localStorage «access_token».
- Если вам нужно более подробное объяснение, код приведен ниже, но в основном я использовал эту статью как примерное руководство по тому, как внешний код должен работать для «потока веб-приложения»: Клиент OpenID Connect на примере — Codeburst .io
Больше информации
- Чтобы прояснить словарный запас: цель здесь — выполнить аутентификацию пользователя через вход в социальную сеть . Вход в систему через социальные сети — это разновидность системы единого входа в системе.
- Первое, что вы должны понять, это то, что на момент написания этой статьи GitHub не позиционировал себя как поставщик социальных сетей, как это сделали Facebook и Google.
- Facebook и Google разработали специальные библиотеки JavaScript, которые можно использовать для реализации входа через социальные сети без необходимости писать какой-либо (?) внутренний код для входа в систему. У GitHub нет такой библиотеки, и насколько я могу судить, третья сторона даже не может разработать такую библиотеку, потому что API GitHub не предлагает функциональности, необходимой для создания такой библиотеки (в частности, они, похоже, не поддерживают ни «неявный поток» или OpenID Connect).
- Следующее, что вы должны понять, это то, что на момент написания этой статьи API GitHub, похоже, не поддерживает использование OpenID Connect для реализации входа через социальные сети с использованием учетных записей GitHub .
- Когда я начал исследовать, как реализовать вход через социальные сети, меня смутил тот факт, что в последних онлайн-руководствах говорилось, что OpenID Connect является лучшим на данный момент способом сделать это. И это верно, если используемый вами поставщик удостоверений (например, GitHub) поддерживает его (т. е. их API может возвращать токены OpenID Connect ID). Насколько я могу судить, API GitHub в настоящее время не имеет возможности возвращать токены OpenID Connect ID от конечных точек, у которых нам нужно их запросить, хотя кажется, что они поддерживают использование токенов OpenID Connect в других местах их API.
- Таким образом, веб-приложения обычно хотят реализовать вход через социальные сети с учетными записями GitHub, используя поток OAuth 2.0, который использовался большинством веб-сайтов до OpenID Connect, который большинство онлайн-ресурсов называют «потоком кода авторизации », но который описан в документации GitHub. называется «поток веб-приложения». Он так же безопасен, но для правильной реализации требует больше работы/кода, чем другие методы. Вывод заключается в том, что реализация входа через социальные сети с помощью GitHub займет больше времени, чем использование поставщика удостоверений, такого как Facebook или Google, которые упростили процесс для разработчиков.
- Если вы (или ваш начальник) по-прежнему хотите использовать GitHub для входа в социальные сети, даже понимая, что это займет больше времени, стоит потратить некоторое время на то, чтобы посмотреть некоторые объяснения того, как работает поток OAuth 2.0 и почему был разработан OpenID Connect (хотя GitHub, похоже, его не поддерживает) и ознакомьтесь с некоторыми ключевыми техническими терминами, поскольку это облегчит понимание руководства GitHub.
- ОАутентификация 2.0
- Лучшее объяснение OAuth 2.0, которое я нашел, было от Okta: Иллюстрированное руководство по OAuth и OpenID Connect
- Наиболее важные технические термины:
- Поставщик удостоверений — это GitHub, Facebook, Google и т. д.
- Клиент — это ваше приложение; в частности, серверная часть вашего приложения.
- Код авторизации — «Недолговечный временный код, который Клиент предоставляет [Поставщику удостоверений] в обмен на токен доступа».
- Токен доступа: это то, что позволяет вашему приложению запрашивать у GitHub информацию о пользователе.
- Вам также может оказаться полезным этот график:
- Заголовок слайда — «Поток кода авторизации OIDC», но тот же поток используется для потока кода авторизации OAuth 2.0, отличного от OIDC, с той лишь разницей, что шаг 10 возвращает не токен идентификатора, а только токен доступа и токен обновления. .
- Тот факт, что шаг 11 выделен зеленым цветом, не имеет значения; это всего лишь шаг, который докладчик хотел выделить на этом слайде.
- На графике «Поставщик удостоверений» и «Сервер ресурсов» показаны как отдельные объекты, что может сбить с толку. В нашем случае это оба API GitHub; «Поставщик удостоверений» — это часть API GitHub, которая предоставляет нам токен доступа, а «Сервер ресурсов» — это часть API GitHub, на которую мы можем отправить токен доступа, чтобы выполнять действия от имени пользователя (например, спрашивать о их профиль).
- Источник: Введение в OAuth 2.0 и OpenID Connect (слайды PowerPoint) — PragmaticWebSecurity.com
- OpenID Connect (OIDC)
- Опять же, GitHub, похоже, не поддерживает это, но об этом много упоминалось в Интернете, поэтому вам может быть интересно узнать, что здесь происходит/какую проблему он решает/почему GitHub его не поддерживает.
- Лучшее объяснение того, почему был введен OpenID Connect и почему для аутентификации он предпочтительнее простого OAuth 2.0, которое я видел, — это мое собственное резюме сообщения в блоге ThreadSafe 2012 года: Зачем использовать OpenID Connect вместо простого OAuth2?.
- Короткий ответ: до появления OIDC чисто интерфейсные библиотеки JavaScript для входа в социальные сети (например, Facebook) использовали простой OAuth 2.0, но этот метод был открыт для эксплойта, когда вредоносное веб-приложение могло заставить пользователя войти на свой сайт (например, , используя вход в Facebook), а затем используйте сгенерированный токен доступа (Facebook), чтобы выдать себя за этого пользователя на любом другом сайте, который принял этот токен доступа (Facebook) в качестве метода аутентификации. OIDC предотвращает этот эксплойт.
- Но у GitHub нет чисто клиентской библиотеки JavaScript для входа в социальные сети, поэтому ему не требуется поддержка OpenID Connect для устранения этого эксплойта. Вам просто нужно убедиться, что серверная часть вашего приложения отслеживает, какие токены доступа GitHub оно сгенерировало, а не просто доверяет любому действительному полученному токену доступа GitHub.
- Во время исследования я наткнулся на HelloJS и задался вопросом, могу ли я использовать его для реализации входа через социальные сети. Насколько я могу судить, ответ «не надежно».
- Первое, что нужно понять, это то, что когда вы используете HelloJS, он использует тот же поток кода аутентификации, который я описал выше, за исключением того, что у HelloJS есть собственный внутренний («прокси») сервер, настроенный так, чтобы вы могли пропустить написание внутреннего сервера. код, который обычно необходим для реализации этого процесса, а интерфейсная библиотека HelloJS позволяет вам пропустить написание всего обычно необходимого внешнего кода.
- Проблема с использованием HelloJS для входа в социальные сети заключается в части внутреннего сервера/прокси: похоже, нет способа предотвратить тип атаки, для предотвращения которой был создан OpenID Connect: конечный результат использования HelloJS выглядит следующим образом: токен доступа GitHub, и серверная часть вашего приложения, похоже, не может определить, был ли этот токен доступа создан пользователем, пытающимся войти в ваше приложение, или он был создан, когда пользователь входил в какое-либо другое вредоносное приложение. (который затем использует этот токен доступа для отправки запросов в ваше приложение, выдавая себя за пользователя).
- Если ваше приложение не использует серверную часть, все может быть в порядке, но большинство приложений полагаются на серверную часть для хранения пользовательских данных, которые должны быть доступны только этому пользователю.
- Вы могли бы обойти эту проблему, если бы могли запросить прокси-сервер, чтобы дважды проверить, какие токены доступа он сгенерировал, но у HelloJS, похоже, нет способа сделать это «из коробки», и если вы Если вы решите создать свой собственный прокси-сервер, чтобы сделать это, вы, похоже, окажетесь в более сложной ситуации, чем если бы вы просто избегали HelloJS с самого начала.
- Вместо этого HelloJS, похоже, предназначен для ситуаций, когда ваш интерфейс просто хочет запросить API GitHub от имени пользователя, чтобы получить информацию об его учетной записи, например данные пользователя или список репозиториев, не ожидая, что ваш сервер будет использовать токен доступа пользователя GitHub в качестве метода доступа этого пользователя к своей личной информации на вашем сервере.
- Для реализации «потока веб-приложения» я использовал следующую статью в качестве справочника, хотя она не совсем соответствует тому, что мне нужно было делать с GitHub: Клиент OpenID Connect на примере — Codeburst.io
- Имейте в виду, что это руководство предназначено для реализации потока аутентификации OpenID Connect, который похож, но не совпадает с потоком, который нам нужно использовать для GitHub.
- Приведенный здесь код был особенно полезен для правильной работы моего внешнего кода.
- GitHub не позволяет использовать «nonce», как описано в этом руководстве, поскольку это функция, специфичная для (некоторых реализаций?) OpenID Connect, а API GitHub не поддерживает использование nonce таким же образом, как это API Google делает это.
- Для реализации «потока устройства» я использовал следующую статью: Использование потока устройства OAuth 2.0 для аутентификации пользователей в настольных приложениях
- Ключевая цитата такова: «По сути, когда вам необходимо пройти аутентификацию, устройство отображает URL-адрес и код (оно также может отображать QR-код, чтобы избежать необходимости копировать URL-адрес) и начинает опрашивать провайдера удостоверений, чтобы узнать, есть ли аутентификация завершена. Вы переходите по URL-адресу в браузере на своем телефоне или компьютере, входите в систему, когда будет предложено, и вводите код. Когда вы закончите, при следующем опросе устройства у IdP оно получит токен: поток завершен».
Пример кода
- Приложение, над которым я работаю, использует Vue + Quasar + TypeScript во внешнем интерфейсе и Python + aiohttp во внутреннем интерфейсе. Очевидно, что вы не сможете использовать код напрямую, но, надеюсь, использование его в качестве справочного материала даст вам достаточно представления о том, как должен выглядеть готовый продукт, и вы сможете быстрее заставить работать свой собственный код.
- Из-за ограничений длины сообщений Stack Overflow я не могу включить код в тело этого ответа, поэтому вместо этого я связываю код в отдельных GitHub Gists.
- App.vue
- Это «родительский компонент», внутри которого содержится все интерфейсное приложение. В нем есть код, который обрабатывает ситуацию во время «потока веб-приложения», когда GitHub перенаправляет пользователя обратно в наше приложение после авторизации нашего приложения. Он берет код авторизации из параметров URL-запроса и отправляет его на серверную часть нашего приложения, которая, в свою очередь, отправляет код авторизации в GitHub в обмен на токен доступа и токен обновления.
- axios.ts
- Это большая часть кода из
axios.ts
. Здесь я помещаю код, который добавляет токен доступа GitHub ко всем запросам к серверной части нашего приложения (если интерфейсная часть находит такой токен в localStorage), а также код, который просматривает любые ответы из задней части нашего приложения. -end, чтобы проверить, обновлен ли токен доступа.
- auth.py
- Это внутренний файл, который содержит все маршруты, используемые в процессе входа в систему как для «потока веб-приложения», так и для «потока устройства». Если URL-адрес маршрута содержит «oauth», то это для «потока веб-приложения», а если URL-адрес маршрута содержит «устройство», то это для «потока устройства»; Я просто следовал примеру GitHub.
- middleware.py
- Это внутренний файл, содержащий функцию промежуточного программного обеспечения, которая оценивает все входящие запросы, чтобы определить, находится ли представленный токен доступа GitHub в базе данных нашего приложения и срок его действия еще не истек. Код обновления токена доступа находится в этом файле.
- Login.vue
- Это внешний компонент, который отображает «страницу входа». Он имеет код как для «потока веб-приложения», так и для «потока устройства».
Краткое изложение двух потоков входа в систему, реализованных в моем приложении:
Поток веб-приложения
- Пользователь переходит на http://mywebsite.com/
- Код внешнего интерфейса проверяет, существует ли переменная
access_token
localStorage (которая указывает на то, что пользователь уже вошел в систему), и не находит ее, поэтому перенаправляет пользователя на маршрут /login.
- Смотри
App.vue:mounted()
и App.vue:watch:authenticated()
- На странице/представлении входа пользователь нажимает кнопку «Войти с помощью GitHub».
- Интерфейс устанавливает случайную переменную
state
localStorage, а затем перенаправляет пользователя на страницу авторизации приложения OAuth GitHub с идентификатором клиента нашего приложения и случайной переменной state
в качестве параметров URL-запроса.
- Смотри
Login.vue:redirectUserToGitHubWebAppFlowLoginLink()
- Пользователь входит в GitHub (если он еще не вошел в систему), авторизует наше приложение и перенаправляется обратно на http://mywebsite.com/ с кодом аутентификации и переменной состояния в качестве параметров URL-запроса.
- Приложение ищет эти параметры URL-запроса каждый раз при загрузке, и когда оно их видит, оно проверяет, соответствует ли переменная
state
тому, что оно хранит в localStorage, и если это так, оно отправляет код авторизации POST на наш сервер.
- Смотри
App.vue:mounted()
и App.vue:sendTheBackendTheAuthorizationCodeFromGitHub()
- Серверная часть нашего приложения получает код авторизации POSTed, а затем очень быстро:
- Примечание: шаги ниже находятся в
auth.py:get_web_app_flow_access_token_and_refresh_token()
.
- Он отправляет код авторизации в GitHub в обмен на токен доступа и токен обновления (а также срок их действия).
- Он использует токен доступа для запроса конечной точки GitHub «/user», чтобы получить имя пользователя GitHub, адрес электронной почты и имя.
- Он просматривает нашу базу данных, чтобы узнать, есть ли у нас пользователь с полученным именем пользователя GitHub, и если нет, создает его.
- Он создает новую запись базы данных «oauth_tokens» для вновь полученных токенов доступа и связывает ее с записью пользователя.
- Наконец, он отправляет токен доступа во внешний интерфейс в ответ на запрос внешнего интерфейса.
- Интерфейсный интерфейс получает ответ, устанавливает переменную
access_token
в localStorage и устанавливает переменную authenticated
Vue в значение true
, за которым приложение постоянно следит и которое заставляет внешний интерфейс перенаправить пользователя из представления «вход в систему». в представление «приложение» (т. е. ту часть приложения, которая требует аутентификации пользователя).
- Смотри
App.vue:sendTheBackendTheAuthorizationCodeFromGitHub()
и App.vue:watch:authenticated()
Поток устройства
- Пользователь переходит на http://mywebsite.com/
- Код внешнего интерфейса проверяет, существует ли переменная
access_token
localStorage (которая указывает на то, что пользователь уже вошел в систему), и не находит ее, поэтому перенаправляет пользователя на маршрут /login.
- Смотри
App.vue:mounted()
и App.vue:watch:authenticated()
- На странице/представлении входа пользователь нажимает кнопку «Войти с помощью GitHub».
- Интерфейсная часть отправляет запрос на серверную часть нашего приложения с запросом кода пользователя, который пользователь введет при входе в свою учетную запись GitHub.
- Смотри
Login.vue:startTheDeviceLoginFlow()
- Серверная часть получает этот запрос и:
- Смотри
auth.py:get_device_flow_user_code()
- Отправляет запрос на GitHub с просьбой создать новый
user_code
. - Создает асинхронный опрос задачи GitHub, чтобы узнать, ввел ли пользователь уже
user_code
. - Отправляет пользователю ответ с
user_code
и device_code
, полученный от GitHub.
- Интерфейсная часть получает ответ от серверной части нашего приложения и:
- Он хранит
user_code
и device_code
в переменных Vue.
- Смотри
Login.vue:startTheDeviceLoginFlow()
device_code
также сохраняется в localStorage, поэтому, если пользователь закроет окно браузера, в котором открыта страница входа в систему, а затем откроет новую, ему не нужно будет перезапускать процесс входа в систему.
- Он отображает
user_code
пользователю.
- См.
Login.vue
в блоке кода шаблона, начиная с <div v-if = "deviceFlowUserCode">
.
- На нем изображена кнопка, которая откроет URL-адрес GitHub, где пользователь может ввести
user_code
(страница откроется в новой вкладке). - Он показывает QR-код, который ссылается на ту же ссылку GitHub, поэтому, если пользователь использует приложение на компьютере и хочет ввести код на своем телефоне, он может это сделать.
- Приложение использует полученный
device_code
для установки переменной deviceFlowDeviceCode
. Отдельная часть кода в приложении постоянно проверяет, установлена ли эта переменная, и когда она видит, что да, она начинает опрашивать серверную часть, чтобы узнать, получила ли серверная часть access_token
уже от GitHub. .
- Смотри
Login.vue:watch:deviceFlowDeviceCode()
и Login.vue:repeatedlyPollTheBackEndForTheAccessTokenGivenTheDeviceCode()
- Пользователь либо нажимает вышеупомянутую кнопку, либо сканирует QR-код своим телефоном и вводит код пользователя по адресу
https://github.com/login/device
, войдя в свою учетную запись GitHub, либо на том же устройстве, на котором запущено это приложение, либо на каком-либо другом устройстве (например, на телефоне). - Серверная часть, опрашивая GitHub каждые несколько секунд, как упоминалось ранее, получает
access_token
и refresh_token
и, как упоминалось при описании «потока веб-приложения», отправляет запрос на конечную точку GitHub «/user» для получения пользовательских данных, а затем получает или создает запись пользовательской базы данных, а затем создает новую запись oauth_tokens
базы данных.
- Смотри
auth.py:_repeatedly_poll_github_to_check_if_the_user_has_entered_their_code()
- Интерфейсная часть, опрашивая серверную часть нашего приложения каждые несколько секунд, наконец получает ответ от серверной части с
access_token
, устанавливает переменную access_token
в localStorage, перенаправляет пользователя в представление «приложение» (т. е. часть приложение, требующее аутентификации пользователя).
- Смотри
Login.vue:repeatedlyPollTheBackEndForTheAccessTokenGivenTheDeviceCode()