Реализация авторизации через OAuth 2.0 в мобильном приложении
OAuth 2.0 в мобильном контексте — это не то же самое, что OAuth 2.0 в вебе. Authorization Code Flow с PKCE (Proof Key for Code Exchange) — единственный правильный вариант для нативных приложений. Implicit Flow официально deprecated в RFC 9700. Client Credentials не подходит — нет пользовательского контекста. Если кто-то предлагает Implicit Flow для мобилки в 2024 — это красный флаг.
Почему PKCE обязателен
Нативное приложение не может безопасно хранить client_secret. Файл APK / IPA декомпилируется за минуты — любой секрет, зашитый в бинарник, считается скомпрометированным по определению. PKCE решает эту проблему без client_secret: генерируем code_verifier (случайная строка 43–128 символов), хешируем SHA-256 → code_challenge, передаём challenge при запросе авторизации. При обмене кода на токен передаём оригинальный verifier — сервер проверяет совпадение. Перехватить code и использовать без verifier невозможно.
code_verifier генерируем криптографически:
// iOS
var buffer = [UInt8](repeating: 0, count: 32)
_ = SecRandomCopyBytes(kSecRandomDefault, buffer.count, &buffer)
let verifier = Data(buffer).base64URLEncodedString()
// Android
val bytes = ByteArray(32)
SecureRandom().nextBytes(bytes)
val verifier = Base64.encodeToString(bytes, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
Redirect URI и Custom URL Schemes
Второй болезненный момент — перехват redirect. Custom URL Scheme (myapp://callback) может быть перехвачен другим приложением на устройстве, зарегистрировавшим тот же scheme. Правильный вариант — HTTPS App Links (Android) и Universal Links (iOS). Сервер авторизации редиректит на https://yourapp.com/callback, система проверяет assetlinks.json / apple-app-site-association и передаёт URL непосредственно вашему приложению.
Настройка Universal Links требует:
- AASA-файл на домене по адресу
https://yourapp.com/.well-known/apple-app-site-association -
Associated Domainscapability в Xcode с записьюapplinks:yourapp.com - Обработка URL в
scene(_:continue:)илиapplication(_:continue:restorationHandler:)
На Android — Digital Asset Links файл и intent-filter с autoVerify="true".
Библиотеки
Не пишем OAuth 2.0 + PKCE с нуля. Используем:
- iOS: AppAuth-iOS (openid.net, актуальная версия 1.7+)
- Android: AppAuth-Android
- React Native: react-native-app-auth
- Flutter: flutter_appauth
AppAuth берёт на себя генерацию PKCE, открытие браузера (через ASWebAuthenticationSession на iOS, Custom Tabs на Android), обработку redirect и обмен кода на токен.
Хранение токенов
Access token — в памяти (@State, ViewModel). Не в UserDefaults, не в SharedPreferences. Refresh token — в Keychain (iOS) или EncryptedSharedPreferences / Android Keystore (Android). Access token живёт 15–60 минут, refresh — дни или недели. Разный уровень чувствительности — разный уровень хранения.
Перехват refresh token из SharedPreferences на рутованном Android-устройстве — реальный вектор атаки. EncryptedSharedPreferences из androidx.security:security-crypto закрывает этот сценарий на устройствах с Android Keystore (API 23+).
Работа с несколькими identity providers
Типичный кейс: вход через Google, Apple, корпоративный SAML IdP. Архитектурно — единый OAuthService с протоколом/интерфейсом, разные провайдеры как конкретные реализации. Токены разных провайдеров не смешиваем — каждый хранится под своим ключом в Keychain/Keystore.
"Sign in with Apple" обязателен для App Store, если есть вход через Google или Facebook — гайдлайн 4.8. Нарушение → отклонение на ревью.
Процесс и сроки
Аудит существующего auth (если есть) → выбор/настройка Authorization Server → реализация PKCE flow через AppAuth → настройка Universal/App Links → хранение токенов → refresh flow → тестирование на реальных устройствах → интеграционные тесты с Authorization Server.
Срок: 5–10 рабочих дней для одного провайдера. Каждый дополнительный провайдер — плюс 2–3 дня. Настройка корпоративного SAML/OIDC на стороне сервера не входит в эту оценку.







