Лучшие практики защиты OAuth в веб-приложениях

OAuth 2.0 стал стандартом для аутентификации API, но его безопасная реализация остаётся сложной задачей. Кража токенов, устаревшие потоки авторизации и уязвимости, специфичные для браузеров, ежедневно угрожают веб-приложениям, поэтому разработчикам необходимы чёткие рекомендации о том, что действительно работает. В этой статье рассматриваются основные практики безопасности OAuth 2.0, актуальные в 2025 году, от требований RFC 9700 до паттернов, специфичных для SPA.
Ключевые выводы
- Никогда не храните OAuth-токены в localStorage или sessionStorage — используйте хранение в памяти для SPA
- Authorization Code Flow с PKCE обязателен для всех клиентов согласно OAuth 2.1
- Реализуйте ротацию refresh-токенов и короткоживущие access-токены (15-30 минут)
- Рассмотрите паттерн Backend for Frontend (BFF), чтобы полностью исключить токены из браузера
Почему безопасность OAuth важна как никогда
OAuth защищает данные ваших пользователей, исключая необходимость передачи паролей между приложениями. Вместо того чтобы передавать сторонним приложениям ваши учётные данные (как если бы вы отдавали ключи от дома), OAuth выдаёт временные токены с ограниченными правами — это больше похоже на то, как вы даёте парковщику ключ, который только заводит машину.
Но вот в чём проблема: токены являются ценными целями для атак. Украденный access-токен предоставляет немедленный доступ к API. Неправильные решения при реализации — хранение токенов в localStorage, использование устаревших потоков авторизации или отказ от PKCE — превращают незначительные уязвимости в серьёзные бреши в безопасности.
Основные риски безопасности и способы их предотвращения
Кража и хранение токенов
Чего не следует делать: Хранить токены в localStorage или sessionStorage. Любая XSS-атака может прочитать эти значения и передать их на серверы, контролируемые злоумышленниками.
Что следует делать: Для SPA храните токены только в памяти. Для серверных приложений храните их в зашифрованных серверных сессиях. Используйте короткоживущие access-токены (15-30 минут), чтобы ограничить ущерб от любой кражи.
Устаревшие потоки, которых следует избегать
Implicit Flow мёртв. И RFC 9700, и OAuth 2.1 запрещают его использование. Почему? Потому что он отправляет токены непосредственно во фрагментах URL, делая их доступными для истории браузера, заголовков referrer и любого JavaScript на странице.
Также следует избегать потока Resource Owner Password Credentials — он полностью нивелирует назначение OAuth, требуя от приложений напрямую обрабатывать пользовательские пароли.
Всегда используйте: Authorization Code Flow с PKCE для всех клиентов, включая SPA и мобильные приложения.
Discover how at OpenReplay.com.
Современные стандарты: RFC 9700 и OAuth 2.1
PKCE теперь обязателен
PKCE (Proof Key for Code Exchange) предотвращает атаки перехвата кода авторизации. Вот как это работает:
// Generate code verifier and challenge
const verifier = generateRandomString(128);
const challenge = base64url(sha256(verifier));
// Include challenge in authorization request
const authUrl = `https://auth.example.com/authorize?` +
`client_id=app123&` +
`code_challenge=${challenge}&` +
`code_challenge_method=S256`;
// Send verifier when exchanging code for token
const tokenResponse = await fetch('/token', {
method: 'POST',
body: JSON.stringify({
code: authorizationCode,
code_verifier: verifier
})
});
OAuth 2.1 делает PKCE обязательным для всех потоков authorization code, а не только для публичных клиентов.
Привязка токенов с помощью mTLS или DPoP
Привязка токенов гарантирует, что украденные токены не могут быть использованы злоумышленниками. Два основных подхода:
- mTLS (Mutual TLS): Привязывает токены к клиентским сертификатам
- DPoP (Demonstrating Proof of Possession): Использует криптографическое доказательство без сертификатов
Оба метода предотвращают атаки повторного использования токенов, криптографически связывая токены с легитимным клиентом.
Ротация refresh-токенов
Никогда не используйте refresh-токены повторно. Каждое обновление должно выдавать новый refresh-токен и аннулировать старый. Это ограничивает временное окно для злоупотребления украденным refresh-токеном:
// Server-side refresh token handling
async function refreshAccessToken(oldRefreshToken) {
// Validate and revoke old refresh token
await revokeToken(oldRefreshToken);
// Issue new token pair
return {
access_token: generateAccessToken(),
refresh_token: generateRefreshToken(), // New refresh token
expires_in: 1800
};
}
Безопасность OAuth в SPA: особые соображения
Одностраничные приложения сталкиваются с уникальными вызовами, поскольку весь код выполняется в браузере. Браузер — это враждебная территория — предполагайте, что любые данные там могут быть скомпрометированы.
Паттерн Backend for Frontend (BFF)
Наиболее безопасный подход полностью исключает токены из браузера. Лёгкий backend-прокси обрабатывает OAuth-потоки и хранит токены на стороне сервера, используя безопасные cookies с флагами httpOnly и sameSite для SPA-сессии.
Паттерн Token Handler
Для команд, желающих получить преимущества SPA без сложности backend, паттерн Token Handler предоставляет компромиссное решение. Он использует специализированный прокси, который:
- Обрабатывает OAuth-потоки
- Безопасно хранит токены
- Выдаёт короткоживущие session cookies для SPA
- Преобразует запросы с аутентификацией через cookies в запросы с аутентификацией через токены к API
Если вы всё же должны хранить токены в браузере
Когда BFF невозможен:
- Храните токены только в памяти, никогда в localStorage
- Используйте service workers для изоляции доступа к токенам
- Реализуйте агрессивное истечение токенов (5-15 минут)
- Никогда не храните refresh-токены в браузере
Чек-лист реализации
✓ Используйте Authorization Code Flow с PKCE для всех клиентов
✓ Реализуйте точное совпадение redirect URI
✓ Устанавливайте минимально возможное время жизни токенов
✓ Включите ротацию refresh-токенов для публичных клиентов
✓ Используйте параметр state для защиты от CSRF
✓ Реализуйте привязку токенов (mTLS/DPoP) для высокозащищённых приложений
✓ Для SPA: рассмотрите паттерны BFF или Token Handler
✓ Следите за требованиями соответствия OAuth 2.1
Заключение
Обеспечение безопасности OAuth в веб-приложениях требует понимания как сильных сторон протокола, так и специфических угроз, с которыми сталкивается ваша архитектура. Начните с требований RFC 9700: обязательный PKCE, отсутствие implicit flow и правильная обработка токенов. Для SPA серьёзно рассмотрите возможность полного исключения токенов из браузера с помощью паттернов BFF или Token Handler. Это не просто лучшие практики — это минимальный стандарт безопасности OAuth в 2025 году.
Часто задаваемые вопросы
Нет. Даже приложения без конфиденциальных данных не должны использовать localStorage для токенов. Украденные токены могут быть использованы для захвата аккаунта, злоупотребления API и в качестве отправной точки для более серьёзных атак. Всегда храните токены в памяти или используйте серверные сессии.
Да. OAuth 2.1 требует PKCE для всех потоков authorization code, включая конфиденциальные клиенты. PKCE добавляет эшелонированную защиту от атак перехвата кода авторизации, которые client secrets сами по себе не могут предотвратить, особенно в сценариях со скомпрометированными сетями или вредоносными расширениями браузера.
Используйте паттерн Backend for Frontend, где ваш backend управляет refresh-токенами и предоставляет SPA короткоживущие access-токены через безопасные cookies. Альтернативно, используйте silent authentication с prompt=none для получения новых токенов без взаимодействия с пользователем, когда истекает access-токен.
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.