- PVSM.RU - https://www.pvsm.ru -

Почему работать с OAuth сложно даже сегодня?

Почему работать с OAuth сложно даже сегодня? - 1


OAuth — это стандартный протокол. Ведь так? И для OAuth 2.0 есть [1] клиентские библиотеки [2] практически [3] на всех [4] языках [5] программирования [6], которые [7] можно [8] представить [9].

Вероятно, вы подумаете, что имея клиентскую библиотеку, можно реализовать OAuth для любого API буквально за десять минут. Или хотя бы за час.

Если вам это удастся, то, пожалуйста, сообщите об этом нам — мы угостим вас изысканным ужином и послушаем, как у вас это получилось.

▍ OAuth на практике

Мы реализовали OAuth для пятидесяти самых популярных API, например, Google (Gmail, Calendar, Sheets и так далее), HubSpot, Shopify, Salesforce, Stripe, Jira, Slack, Microsoft (Azure, Outlook, OneDrive), LinkedIn, Facebook и для других OAuth API. [10]

Наш вывод: в реальном мире опыт работы с OAuth сравним с опытом работы с браузерными JavaScript API в 2008 году. Существует общий консенсус о различных аспектах, но в реальности у каждого API есть собственная интерпретация стандарта, особенности реализации, нестандартные поведения и расширения. В результате за каждым углом вас ожидают выстрелы в ногу.

Если бы это так не раздражало, то было бы довольно забавным. Но давайте рассмотрим конкретные примеры!

Проблема 1: стандарт OAuth слишком большой и сложный

«Этот API тоже использует OAuth 2.0, и мы создали его несколько недель назад. Наверно, до завтра я справлюсь» – знаменитые слова стажёра

OAuth — очень большой стандарт. На официальном сайте OAuth 2.0 [11] сейчас указано 17 разных RFC (определяющих стандарт документов), вместе описывающих принцип работы OAuth 2. В них раскрывается всё, от фреймворка OAuth и токенов Bearer до моделей угроз и JWT приватных ключей.

«Но ведь не все эти RFC важны для простой авторизации по токенам при помощи API?», — спросите вы.

Да, вы правы. Давайте сосредоточимся только на том, что релевантно для типичного примера доступа третьей стороне при помощи API:

  • Стандарт OAuth: по умолчанию сейчас используется OAuth 2.0, но некоторые по-прежнему используют OAuth 1.0a (а на подходе уже 2.1). Разобравшись, какую версию использует ваш API, переходите к:
  • Типу Grant: вам нужны «authorization_code», «client_credentials» или «device_code»? Что они делают и когда необходимо применять каждый из них? В случае сомнений попробуйте «authorization_code».
  • Примечание: токены обновления — это тоже grant, но довольно особенный. Способ их работы стандартизирован, однако способ их запроса — нет. Подробнее об этом ниже.
  • Теперь, когда вы готовы к запросам, посмотрите, сколько (72, если быть точным) существует официальных параметров OAuth [12] с конкретным значением и поведением. Самые частые примеры — это «prompt», «scope», «audience», «resource», «assertion» и «login_hint». Однако, по нашему опыту, большинство поставщиков API, похоже, не знает об этом списке, как, наверно, и вы до сего момента, поэтому не стоит особо об этом волноваться.

Если вы думаете, что это всё равно слишком сложно и требует долгого изучения, то мы склонны с вами согласиться.

И большинство команд, разрабатывающих публичные API, наверно, тоже с этим согласно. Вместо реализации полного подмножества OAuth 2.0 они просто реализуют части OAuth, которые, по их мнению, будут необходимы в сценарии использования их API. Это приводит к созданию длинных страниц с описаниями того, как работает OAuth в конкретном API. Но мы вряд ли можем их винить; они руководствовались только самыми лучшими побуждениями. А если бы они попытались реализовать стандарт целиком, то документация представляла бы собой небольшую книгу!

Почему работать с OAuth сложно даже сегодня? - 2

Поток authorization_code OAuth в Salesforce. Что может не понравиться в этом чётком и наглядном объяснении процесса из десяти этапов?

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

Проблема 2: каждый реализует OAuth немного по-своему

Так как каждый API реализует собственное подмножество OAuth, мы быстро попадаем в ситуацию, когда приходится внимательно читать длинные страницы документации по OAuth:

  • Какие параметры они требуют в вызове авторизации?
    • В случае Jira, параметр «audience» — это ключ (которому должно быть присвоено конкретное фиксированное значение). Google предпочитает обрабатывать это через разные scope, но ему очень важен параметр «prompt». Тем временем, кто-то в Microsoft обнаружил параметр «response_mode», который всегда должен иметь значение «query».
    • Notion API подходит к этому радикально: повсюду использует параметр «scope». На самом деле в документации по этому API нет упоминаний слова «scope». В Notion они называются «capabilities» и задаются при регистрации приложения. Нам понадобилось полчаса, чтобы в этом разобраться. Зачем разработчики решили заново изобрести велосипед?
    • С «offline_access» ситуация ещё хуже: сегодня в большинстве API срок действия токенов доступа истекает очень быстро. Чтобы получить токен обновления, нужно затребовать «offline_access», что необходимо выполнить через параметр, scope, и то, что задаётся при регистрации приложения OAuth. Подробности нужно узнавать у специалиста по API или OAuth.

  • Что требуется указать в вызове запроса токена?
    • Некоторые API, например, Fitbit, настаивают на получении данных в заголовках. Большинству необходимо, чтобы они были в теле, закодированные как «x-www-url-form-encoded», а некоторые исключения наподобие Notion предпочитают получать их в JSON.
    • Некоторые хотят аутентифицировать этот запрос при помощи Basic auth. Многие этим не заморачиваются. Но будьте внимательны, уже завтра они могут всё поменять.

  • Куда перенаправлять моих пользователей для авторизации?
    • У Shopify и Zendesk есть модель, в которой каждый пользователь получает поддомен вида {subdomain}.myshopify.com. И да, это относится и к странице авторизации OAuth, поэтому лучше встроить динамические URL в модель и код фронтенда.
    • У Zoho Books есть разные дата-центры для клиентов в разных странах. Они должны помнить, где находятся их данные: для авторизации приложения клиенты из США должны перейти на https://accounts.zoho.com, европейцы — на https://accounts.zoho.eu, а индийцы — на https://accounts.zoho.in. И так далее.

  • Но, по крайней мере, я могу выбирать свой URL обратного вызова, ведь так?

Этот список можно продолжать ещё долго, но надеюсь, смысл вы поняли.

Почему работать с OAuth сложно даже сегодня? - 3

OAuth слишком сложен; давайте создадим более простую версию OAuth, в которой есть всё, что нам нужно!

Проблема 3: многие API добавляют к OAuth нестандартные расширения

Даже несмотря на обширность стандарта OAuth, многие API, похоже, всё равно находят в нём отсутствие нужных им функций. Часто мы встречаемся с проблемой необходимости для работы с API дополнительных данных наряду с «access_token». Разве не было бы здорово, если бы эти дополнительные данные могли возвращаться вместе с access_token в потоке OAuth?

Мы и в самом деле считаем это хорошей идеей; или, по крайней мере, она получше, чем заставлять пользователей потом выполнять изощрённые запросы к API для получения этой информации (да, мы о тебе, Jira). Но это означает, что необходимо более нестандартное поведение, которое специально требуется реализовывать для каждого API.

Вот небольшой список нестандартных расширений, которые нам встречались:

  • Quickbooks использует «realmID», который нужно передавать с каждым запросом к API. Единственный раз, когда он сообщает этот «realmID» — это дополнительный параметр в обратном вызове OAuth. Лучше хранить его где-то в безопасном месте!
  • Braintree поступает так же с «companyID»
  • Salesforce использует отдельный базовый URL API для каждого клиента; они называются «instance_url». К счастью, он возвращает «instance_url» пользователя вместе с токеном доступа в ответе токена, но его всё равно нужно спарсить оттуда и сохранить.
  • К сожалению, Salesforce делает ещё более раздражающие вещи: срок действия токенов доступа истекает после заранее заданного периода времени, который может настраивать пользователь. Пока всё здорово, но по какой-то причине он не сообщает в ответе токена, когда истечёт срок только что полученного доступа (все остальные это делают). Вместо этого нужно запрашивать дополнительную конечную точку подробностей о токенах, чтобы получить текущую дату истечения токена. Почему, Salesforce, почему?
  • В Slack есть два типа scope: scope, которые вы имеете как бот Slack, и scope, которые позволяют выполнять действия от лица пользователя, авторизовавшего приложение. Это умно, но вместо добавления разных scope для каждого они случая разработчики реализовали отдельный параметр «user_scopes», который нужно передавать в вызове авторизации. Об этом лучше знать, а поддержку этой особенности в вашей библиотеке OAuth найти вряд ли получится.

Ради краткости и простоты мы опустим множество не очень стандартных потоков OAuth, с которыми нам приходилось сталкиваться.

Проблема 4: «invalid_request» — отлаживать потоки OAuth сложно

Отлаживать распределённые системы всегда сложно. Ещё сложнее это делать, когда сервис, с которым ты работаешь, использует неопределённые и обобщённые сообщения об ошибках.

У OAuth2 есть стандартизованные сообщения об ошибках [15], но они не более информативны, чем пример из заголовка (который, кстати, является одним из рекомендованных сообщений об ошибках из стандарта OAuth).

Возможно, вы заявите, что OAuth — это стандарт, а для каждого API есть документация, так что же тут отлаживать?

Многое. Не могу передать, насколько часто в документации присутствуют ошибки. Или отсутствуют подробности. Или она не обновлялась с последним изменением. Или вы что-то упустили при первом её изучении. Добрые 80% реализуемых нами потоков OAuth имеют проблемы в первой реализации и требуют отладки.

Некоторые потоки разваливаются, казалось бы, по случайным причинам: например, LinkedIn OAuth разваливается при передаче параметров PKCE. Какую же ошибку мы получаем? «client error — invalid OAuth request». И о чём же она нам говорит? Нам потребовался час, чтобы понять, что поток ломается из-за параметров PKCE (опциональных, которые обычно игнорируют).

Ещё одна распространённая ошибка заключается в передаче scope, не совпадающих с теми, которые вы предварительно зарегистрировали с приложением. (Предварительно регистрируемые scope? Да, сегодня многие API требуют их.) Часто это приводит к получению обобщённого сообщения о проблеме с scope. Да уж.

Проблема 5: неудобство согласования при разработке поверх API

Если вы надстраиваете чужую систему, используя её API, то вы, вероятно, находитесь в неудобном положении. Клиенты просят реализовать интеграцию, потому что они уже пользуются другой системой. И вам нужно удовлетворить их потребности.

Если честно, многие API достаточно либеральны и предоставляют простые самообслуживающиеся потоки регистрации, позволяющие разработчикам регистрировать свои приложения и использовать OAuth. Однако некоторые из самых популярных API требуют проверки, прежде чем приложение станет публичным и с ним можно будет работать пользователям. Повторюсь, ради справедливости нужно сказать, что большинство процессов проверки вполне разумны и их выполнение требует всего нескольких дней. В целом, с точки зрения качества и безопасности конечных пользователей они приносят пользу.

Однако в некоторых печальных случаях проверка затягивается на месяцы и даже требует подписания договоров об участии в прибыли:

  • Если вам нужен scope доступа к уязвимым пользовательским данным, например, к содержимому электронной почты, Google требует «проверки безопасности». Мы слышали, что для прохождения таких проверок нужны дни или даже недели, а также нетривиальный объём труда со стороны разработчиков.
  • Хотите организовать интеграцию с Rippling? Готовьтесь ответить на тридцать с лишним вопросом и пройти скрининг безопасности препродакшена. Мы слышали, что для получения доступа требуются месяцы (если вас одобрили).
  • HubSpot, Notion, Atlassian, Shopify, да и практически все остальные, у кого есть маркетплейсы интеграций или магазины приложений, требуют проверки для попадания в список. Некоторые проверки вполне умеренны, а другие требуют предоставления демонстрационных логинов, видеоинструкций, постов в блогах (да!) и многого другого. Однако попадание в маркетплейс или магазин часто необязательно.
  • У Ramp, Brex, Twitter и многих других нет самообслуживаемого потока регистрации для разработчиков; они требуют, чтобы разработчики заполняли формы для доступа к руководствам. Многие обрабатывают запросы быстро, от других же нужно ждать ответа несколько недель.
  • Xero — один из радикальных примеров монетизируемого API: если вы хотите преодолеть ограничение в 25 подключенных аккаунтов, то вам придётся стать партнёром Xero [16] и зарегистрировать своё приложение в его магазине приложений. После этого он будет взимать долю в 15% от дохода с каждого лида, сгенерированного из этого магазина (информация актуальна на момент публикации статьи).

Проблема 6: безопасность OAuth — это сложная и движущаяся мишень

С обнаружением разных видов атак и эволюцией веб-технологий стандарт OAuth тоже менялся. Если вы стремитесь реализовать современные рекомендации по безопасности, то у рабочей группы OAuth есть для вас довольно длинное руководство [17]. А если работаете с API, который и сегодня использует OAuth 1.0a, то поймёте, что обратная совместимость — это проблема, которую нужно решать постоянно.

К счастью, уровень защиты с каждой итерацией повышается, но часто за это приходится расплачиваться дополнительным трудом разработчиков. Грядущий стандарт OAuth 2.1 может сделать некоторые из современных рекомендаций обязательными и включает в себя обязательный PKCE (сегодня его требуют очень немногие API), а также дополнительные ограничения на токены обновления.

Самое важное заявленное изменение, вероятно, будет связано с истечением срока действия и с развитием токенов обновления. На поверхности процесс кажется простым: когда истекает срок действия токена доступа, мы обновляем его при помощи токена обновления и сохраняем новый токен доступа и токен обновления.

Однако в реальности, когда мы реализуем это, нужно учесть следующее:

  • Условия гонки: как гарантировать, что при обновлении текущего токена доступа не будут выполняться другие запросы?
  • Некоторые API завершают срок действия токена обновления, если его не используют определённое количество дней (или если пользователь аннулировал доступ). Стоит ожидать, что некоторые обновления не будут выполняться.
  • Некоторые API передают новый токен обновления при каждом запросе обновления …
  • … но некоторые молчаливо предполагают, что вы сохраните старый токен обновления и продолжите его использовать.
  • Некоторые API сообщают срок истечения токена доступа в абсолютных значениях. Другие сообщают относительные «секунды от текущего момента». А в некоторых, например, в Salesforce, получить подобный вид информации не так уж просто.

И последнее: то, о чём мы пока не говорили

К сожалению, мы затронули только малую часть аспектов реализации OAuth. Запустив поток OAuth и начав получать токены доступа, мы должны ещё подумать о следующем:

  • Как безопасно хранить эти токены доступа и токены обновления. Они похожи на пароли к аккаунтам пользователей. Но хэширование — это не вариант: вам нужно безопасное и обратимое шифрование.
  • О проверке того, что предоставленные scope соответствуют запрошенным scope (некоторые API позволяют пользователям менять scope, предоставляемые в потоке авторизации).
  • Как избегать условий гонки при обновлении токенов.
  • О выявлении токенов доступа, аннулированных пользователем на стороне поставщика.
  • Как сообщать пользователям об истечении срока действия токенов доступа, чтобы они при необходимости повторно авторизировали приложение.
  • Как аннулировать токены доступа, которые вам больше не нужны (или когда пользователь запросил их удаление согласно GDPR).
  • Об изменениях в scope OAuth, багах поставщиков, отсутствующей документации и так далее.

Пол-лимона подарков от RUVDS. Отвечай на вопросы и получай призы 🍋 [18]

Автор:
ru_vds

Источник [19]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/384646

Ссылки в тексте:

[1] есть: https://oauth.net/code/nodejs/

[2] клиентские библиотеки: https://oauth.net/code/php/

[3] практически: https://oauth.net/code/java/

[4] на всех: https://oauth.net/code/python/

[5] языках: https://oauth.net/code/go/

[6] программирования: https://oauth.net/code/rust/

[7] которые: https://oauth.net/code/dotnet/

[8] можно: https://oauth.net/code/kotlin/

[9] представить: https://oauth.net/code/swift/

[10] других OAuth API.: https://docs.nango.dev/providers

[11] официальном сайте OAuth 2.0: https://oauth.net/2/

[12] официальных параметров OAuth: https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml

[13] localhost: http://localhost

[14] существуют решения для перенаправления OAuth на localhost: https://www.nango.dev/blog/oauth-redirects-on-localhost-with-https

[15] стандартизованные сообщения об ошибках: https://datatracker.ietf.org/doc/html/rfc6749#section-5.2

[16] стать партнёром Xero: https://developer.xero.com/documentation/xero-app-store/app-partner-guides/app-partner-steps/

[17] довольно длинное руководство: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics

[18] Пол-лимона подарков от RUVDS. Отвечай на вопросы и получай призы 🍋: https://habr.com/ru/specials/731732/

[19] Источник: https://habr.com/ru/companies/ruvds/articles/734204/?utm_source=habrahabr&utm_medium=rss&utm_campaign=734204