ПроКодинг - Откроем для вас мир IT!

Вы когда-нибудь сталкивались с тем, что система авторизации работает идеально в тестовой среде, но в продакшене - ломается, как старый телефон? Это не редкость. Большинство разработчиков, которые впервые внедряют OAuth 2.0 и OpenID Connect, думают, что это просто «нажми кнопку и всё заработает». На деле - это один из самых коварных источников уязвимостей в современных приложениях. И чаще всего причина не в сложности протоколов, а в типичных, хорошо известных, но игнорируемых ошибках.

Ошибка №1: Использование Authorization Code Flow без PKCE

Если вы используете OAuth 2.0 для мобильных приложений или веб-приложений, которые не могут хранить секреты (SPA, мобильные приложения), вы обязаны использовать PKCE - Proof Key for Code Exchange. Без него вы открыли дверь для атаки «code interception». Атакующий перехватывает авторизационный код, который передаётся по HTTP-перенаправлению, и использует его, чтобы получить токен доступа от вашего сервера. Это не теория - в 2024 году исследование OWASP показало, что 37% всех мобильных приложений, использующих OAuth 2.0, не реализовали PKCE. Это как оставлять ключи от машины в замке зажигания.

PKCE не сложный. Вы генерируете случайную строку (code_verifier), хешируете её (code_challenge), отправляете её на сервер при запросе авторизации, а потом отправляете оригинальный verifier при обмене кода на токен. Сервер проверяет соответствие. Это добавляет всего 3 строки кода, но защищает от десятков атак.

Ошибка №2: Доверие токенам без проверки подписи

Многие разработчики получают JWT-токен от Identity Provider (например, Auth0, Okta, Azure AD) и просто смотрят на поле exp - если время не истекло, считают токен валидным. Это опасно. JWT может быть подделан. Без проверки цифровой подписи (RS256, ES256) вы доверяете любому, кто знает формат токена. Атакующий может сгенерировать свой собственный токен с любым user_id, и ваша система примет его как легитимного пользователя.

Вы должны всегда проверять подпись токена. Это значит: использовать публичный ключ, полученный из JWK-эндпоинта провайдера (например, https://your-idp.com/.well-known/jwks.json), а не жёстко прописывать его в коде. И да - если вы используете HS256 (симметричный ключ) в продакшене, вы уже делаете что-то не так. HS256 подходит только для внутренних систем, где секрет не покидает сервер. Для внешних провайдеров - только RS256 или ES256.

Ошибка №3: Неправильная обработка refresh token

Refresh token - это ваша «бумажка на случай, если токен истёк». Но если вы храните его в localStorage, он становится лёгкой мишенью для XSS-атак. Если вы отправляете его в заголовке Authorization при каждом запросе - вы рискуете перехватить его через MITM. Если вы не привязываете его к пользовательскому устройству или IP-адресу - он может быть использован с любого места.

Правильный подход: храните refresh token только в HTTP-only, Secure, SameSite=Strict cookie. Не передавайте его в API-запросах. Используйте его только для получения нового access token через защищённый эндпоинт, например, /refresh-token. И обязательно ограничьте срок его жизни - не больше 7 дней. Даже если он украден, злоумышленник не сможет использовать его месяцами.

Ошибка №4: Использование OpenID Connect без проверки ID Token

OpenID Connect - это OAuth 2.0 + аутентификация пользователя. Но многие разработчики используют его как «OAuth с дополнительным полем sub». Они получают ID Token, не проверяют его, и просто берут sub как идентификатор пользователя. Это критическая ошибка.

ID Token - это JWT, который должен быть подписан, проверен на срок действия, issuer, audience, и nonce (если вы его отправляли). Если вы не проверяете iss, вы можете получить токен от любого провайдера - даже от вредоносного. Если вы не проверяете aud, токен может быть выдан для другого приложения. Если вы не проверяете nonce, вы уязвимы к атаке «replay attack».

Используйте проверенные библиотеки: для Node.js - openid-client, для Java - nimbus-jose-jwt, для .NET - Microsoft.IdentityModel.Tokens. Не пишите свою собственную логику проверки. Это не та область, где стоит экономить время.

Слева — разработчик доверяет токену без проверки подписи, справа — злоумышленник подделывает его.

Ошибка №5: Слишком широкие scope и отсутствие ограничений

Вы когда-нибудь видели приложение, которое запрашивает доступ к вашему профилю, почте, календарю, контактам и ещё 5 вещам, которые ему абсолютно не нужны? Это не дружелюбно - это опасно. В OAuth 2.0 и OpenID Connect scope определяет, какие данные можно получить. Если вы запрашиваете openid profile email, это нормально. Но если вы запрашиваете openid profile email offline_access без веской причины - вы рискуете, что при компрометации вашего приложения злоумышленник получит не только доступ к аккаунту, но и сможет обновлять токены месяцами.

Принцип: запрашивайте только то, что вам действительно нужно. Если вам нужен только email для регистрации - не запрашивайте профиль. Если вы не делаете фоновые синхронизации - не запрашивайте offline_access. Чем меньше прав - тем меньше ущерба при утечке.

Ошибка №6: Отсутствие проверки redirect_uri

Это одна из самых старых, но самых эффективных атак на OAuth. Если вы не проверяете redirect_uri при получении авторизационного кода, злоумышленник может перенаправить пользователя на свой сервер, подменив redirect_uri. Например, вы настраиваете https://your-app.com/callback, а атакующий подставляет https://evil-site.com/steal-token. Если ваш сервер принимает этот URI - он отправит туда код авторизации. Атакующий получит его, и дальше - всё как в первом случае.

Решение простое: на сервере авторизации проверяйте, что redirect_uri совпадает с тем, что был зарегистрирован при создании клиента. Не используйте wildcard-домены вроде https://*.your-app.com без крайней необходимости. И никогда не доверяйте redirect_uri, передаваемому клиентом - только зарегистрированные URI.

Ошибка №7: Непонимание разницы между OAuth 2.0 и OpenID Connect

Многие думают, что это одно и то же. OAuth 2.0 - это протокол авторизации. Он говорит: «Дайте приложению доступ к моим данным». OpenID Connect - это протокол аутентификации. Он говорит: «Подтвердите, кто я есть».

Если вы используете OAuth 2.0 для аутентификации - вы делаете не то, что нужно. Вы можете получить токен доступа, но не будете знать, кто именно авторизовался. Вы не сможете получить его имя, email, уникальный идентификатор. Вы не сможете проверить, что это реальный человек. OpenID Connect добавляет ID Token, который содержит эти данные, подписанный провайдером.

Если вам нужно знать, кто вошёл в систему - используйте OpenID Connect. Если вам нужно получить доступ к API (например, к Google Drive или GitHub) - используйте OAuth 2.0. Не смешивайте их. Не пытайтесь «сделать OAuth 2.0 аутентификацией» - это как использовать гаечный ключ для забивания гвоздей.

Защищённый поток аутентификации с использованием HTTP-only cookie, проверки scope и подписи JWT.

Ошибка №8: Нет логирования и мониторинга

Если вы не логируете запросы на получение токенов, не отслеживаете частоту авторизаций, не проверяете подозрительные IP-адреса - вы слепы. Атаки не всегда происходят с первого раза. Они начинаются с одного подозрительного запроса, который вы проигнорировали. Потом - второй. Потом - десятый. И вдруг вы обнаруживаете, что 300 аккаунтов скомпрометированы.

Логируйте: когда и с какого IP был сделан запрос на авторизацию, какой client_id использовался, был ли PKCE, был ли токен обновлён. Установите пороги: если один пользователь авторизуется 10 раз за минуту - это не нормально. Если один IP-адрес запрашивает токены для 5 разных пользователей - это явный признак атаки. Используйте системы вроде Prometheus + Grafana или даже простые алерты в Sentry. Без мониторинга вы не знаете, что у вас утекает.

Что делать, если уже всё сломалось?

Если вы уже внедрили OAuth 2.0 и OpenID Connect, но не уверены в безопасности - начните с простого:

  1. Проверьте, используется ли PKCE для всех клиентов (особенно мобильных и SPA).
  2. Убедитесь, что все JWT-токены проверяются на подпись, issuer, audience и срок действия.
  3. Переведите refresh token на HTTP-only cookie.
  4. Ограничьте scope до минимально необходимого.
  5. Проверьте, что redirect_uri строго зарегистрированы и не используют wildcard.
  6. Добавьте логирование всех авторизационных запросов.
  7. Обновите все библиотеки - старые версии могут содержать уязвимости.

Не ждите, пока произойдёт инцидент. Безопасность - это не «включил и забыл». Это постоянная проверка, тестирование и улучшение.

Что ещё стоит проверить?

  • Используете ли вы HTTPS для всех эндпоинтов? Даже для внутренних запросов?
  • Проверяете ли вы, что пользователь не пытается подменить claims в токене?
  • Используете ли вы CORS правильно? Не разрешаете ли Access-Control-Allow-Origin: * для API, которые требуют аутентификации?
  • Есть ли у вас политика сброса токенов при смене пароля или при подозрительной активности?

Всё это - не «хорошие практики». Это обязательные меры. Без них ваша система - как дом с открытыми дверями и окнами, но с сигнализацией, которая не включена.