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 решают с другой стороны — они протухают сами, без участия базы.
Как жить с ключами, чтобы не было больно
Если ключей достаточно — а в большинстве интеграций их достаточно, — то минимум такой:
- Храните у себя только хеш. Сервер сверяет хеш присланного ключа с сохранённым; исходную строку показывают пользователю один раз при создании. Мы сверяем по SHA-256.
- Дайте ключу префикс. Строка вида
lix_live_...опознаётся сканерами секретов в репозиториях и в логах. - Свой ключ на каждую интеграцию. Тогда отзыв одного не роняет остальные, а по журналу видно, что именно скомпрометировано.
- Никогда не кладите ключ в query-строку. Адреса оседают в логах веб-сервера, в заголовке
Refererи в истории браузера. Только заголовок. - Ротация по расписанию, а не по инциденту. Ключ, который меняли хоть раз, меняется быстро. Ключ, который не меняли ни разу, окажется зашит в четырёх местах.
- Следите за последним использованием. Ключ, к которому не обращались полгода, — это не запас, а открытая дверь.
Что используем мы
В API Lix.li аутентификация по ключу: заголовок X-Api-Key, ключи создаются и отзываются в личном кабинете, сверка по хешу, отдельный лимит запросов на ключ. OAuth нет, и это осознанно: сценарии у API серверные — создать ссылку, забрать статистику, принять конверсию, — а в них нет третьей стороны, перед которой пользователю надо давать согласие.
Если бы появилась задача пускать чужие приложения к аккаунтам наших клиентов, ключей бы не хватило: пользователь должен видеть, кому он что разрешил, и уметь отключить одно приложение, не ломая остальные. Вот тогда OAuth перестаёт быть обрядом и становится единственным честным вариантом.
Как устроены сами вызовы, можно посмотреть в документации API — там же видно, какие коды ответа приходят при неверном и при отозванном ключе. Соседний сюжет про то, почему редирект 301 превращает POST в GET, стоит прочитать всем, кто настраивает приём постбеков: запрос с ключом может доехать не тем методом, каким его отправляли.