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

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

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

API-ключ: одна строка, которая говорит «этой программе можно»

Ключ — длинная случайная строка, которую вы кладёте в заголовок запроса:

X-Api-Key: lix_live_9f2c...

Сервер находит ключ, понимает, какому аккаунту он принадлежит, и пропускает запрос. Всё.

Свойства, которые отсюда следуют:

  • ключ не истекает сам. Он живёт, пока его не отозвали;
  • ключ идентифицирует программу, а не человека. Кто именно из команды сделал запрос, по ключу обычно не видно;
  • ключ даёт доступ ко всему, что может аккаунт, если вы отдельно не ограничили права;
  • ключ — единственный секрет. Утёк ключ — утёк доступ, промежуточных шагов нет.

OAuth: протокол о согласии

OAuth 2.0 (RFC 6749) решает другую задачу. У неё четыре участника: владелец данных, приложение, которое хочет их получить, сервер авторизации и сервер с данными.

Ход примерно такой. Приложение отправляет пользователя на страницу сервиса. Пользователь видит, кто и о чём просит, и соглашается. Приложение получает код, меняет его на access-токен и ходит с ним в API. Токен живёт недолго — минуты или часы; когда истекает, приложение меняет refresh-токен на новый.

Что это даёт, чего ключ не даёт в принципе:

  • пароль пользователя не попадает к приложению. В этом весь смысл;
  • права дробятся на scope. «Читать ссылки» и «удалять ссылки» — разные разрешения;
  • согласие видимое и отзываемое. Пользователь видит список приложений и может отключить любое, не трогая остальные;
  • токены протухают сами. Украденный access-токен бесполезен через час.

Цена — сложность. Нужен сервер авторизации, регистрация приложений, экран согласия, хранение и ротация refresh-токенов, обработка истёкших токенов на каждом вызове.

Сравнение по осям, которые влияют на решение

API-ключ OAuth 2.0
Кого удостоверяет программу пользователя, от чьего имени работает программа
Срок жизни бессрочно access-токен минуты-часы
Как прекратить доступ отозвать ключ отозвать согласие или токен
Объём прав обычно весь аккаунт scope на каждое действие
Секрет у третьей стороны сам ключ только токены, пароля нет
Сложность внедрения заголовок в запросе сервер авторизации и весь цикл
Когда оправдан сервер-серверу, скрипты, интеграции внутри компании публичные приложения, маркетплейсы, «Войти через…»

Правило выбора

Вопрос не в том, что безопаснее вообще. Вопрос в том, кто кому делегирует доступ.

Ваш сервер → чужой API, аккаунт ваш
    → API-ключ

Чужое приложение → ваш API, аккаунт пользователя
    → OAuth

Скрипт по расписанию, CI, бэкенд-интеграция
    → API-ключ

Пользователь должен видеть и отзывать доступ поприложенчески
    → OAuth

Приём постбеков — канонический случай для ключа. Когда ваш сервер сообщает нашему, что заказ оплачен, никакого пользователя в этом обмене нет, соглашаться некому, и экран согласия неоткуда взяться. То же верно для вебхуков в обратную сторону.

Что изменилось в OAuth, пока вы не смотрели

Половина статей по теме описывает OAuth образца 2015 года. С тех пор он заметно ужался.

В январе 2025 года IETF опубликовал RFC 9700, Best Current Practice for OAuth 2.0 Security. Документ сводит воедино накопленный опыт атак и формально объявляет устаревшими два способа, которые раньше считались допустимыми:

  • implicit grant — тот, где токен возвращался прямо в адресной строке;
  • resource owner password credentials — где приложение просило у пользователя логин и пароль. То есть ровно то, от чего OAuth и должен был избавить.

Одновременно PKCE стал обязательным для всех клиентов, включая серверные, а не только для мобильных, как было раньше.

Отдельно стоит знать про OAuth 2.1: это до сих пор черновик, а не опубликованный RFC. Он собирает те же изменения в один документ — обязательный PKCE, точное сравнение redirect URI, отказ от implicit и password grant, запрет токенов в query-строке. Ссылаться на него как на действующий стандарт пока рано.

Практический вывод: если вы читаете руководство по OAuth, где предлагают implicit flow, руководство устарело.

Слабое место API-ключей, о котором редко пишут

Ключ не истекает. Значит отзыв — ваш единственный рычаг. И вот тут выясняется неприятное: отзыв обычно не мгновенный.

Проверка ключа на каждом запросе — это обращение к базе. На сколько-нибудь нагруженном API результат кешируют. У нас, например, соответствие ключа аккаунту лежит в кеше пять минут. Значит между нажатием «отозвать» и фактическим отказом в доступе проходит до пяти минут, в течение которых скомпрометированный ключ продолжает работать.

Это не недосмотр, это обмен: без кеша каждый запрос стучится в базу. Похожий компромисс есть у любого API с ключами, просто про него не принято говорить вслух. Знать о нём нужно по двум причинам. Во-первых, при реальной утечке отзыв ключа — не последнее действие, а первое: дальше стоит проверить, что происходило в эти минуты. Во-вторых, это ровно та проблема, которую короткоживущие токены OAuth решают с другой стороны — они протухают сами, без участия базы.

Как жить с ключами, чтобы не было больно

Если ключей достаточно — а в большинстве интеграций их достаточно, — то минимум такой:

  1. Храните у себя только хеш. Сервер сверяет хеш присланного ключа с сохранённым; исходную строку показывают пользователю один раз при создании. Мы сверяем по SHA-256.
  2. Дайте ключу префикс. Строка вида lix_live_... опознаётся сканерами секретов в репозиториях и в логах.
  3. Свой ключ на каждую интеграцию. Тогда отзыв одного не роняет остальные, а по журналу видно, что именно скомпрометировано.
  4. Никогда не кладите ключ в query-строку. Адреса оседают в логах веб-сервера, в заголовке Referer и в истории браузера. Только заголовок.
  5. Ротация по расписанию, а не по инциденту. Ключ, который меняли хоть раз, меняется быстро. Ключ, который не меняли ни разу, окажется зашит в четырёх местах.
  6. Следите за последним использованием. Ключ, к которому не обращались полгода, — это не запас, а открытая дверь.

Что используем мы

В API Lix.li аутентификация по ключу: заголовок X-Api-Key, ключи создаются и отзываются в личном кабинете, сверка по хешу, отдельный лимит запросов на ключ. OAuth нет, и это осознанно: сценарии у API серверные — создать ссылку, забрать статистику, принять конверсию, — а в них нет третьей стороны, перед которой пользователю надо давать согласие.

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

Как устроены сами вызовы, можно посмотреть в документации API — там же видно, какие коды ответа приходят при неверном и при отозванном ключе. Соседний сюжет про то, почему редирект 301 превращает POST в GET, стоит прочитать всем, кто настраивает приём постбеков: запрос с ключом может доехать не тем методом, каким его отправляли.