
Привет! Меня зовут Мялкин Максим, я занимаюсь мобильной разработкой в KTS.
Ни один сервис не обходится без логина. Часто в мобильных приложениях требуется интегрировать вход через сторонние соцсети — например, зайти через Google или VK. А при обучении мобильной разработке используются открытые API, где для авторизации используется OAuth.
Поэтому разработчикам мобильных приложений приходится работать с OAuth. В сети по этой теме есть разные материалы.
В этой статье я попробую структурированно закрыть нюансы OAuth в мобильных приложениях: на какие моменты стоит обратить внимание, какие способы реализации выбрать. А также поделюсь опытом настройки OAuth в Android-приложении с использованием библиотеки AppAuth.
-
Нюансы реализации
OAuth и flow
Когда речь идет про авторизацию и аутентификацию, используются такие понятия как OAuth2 и OpenID. В статье я не буду их раскрывать, на Хабре уже есть такой материал:
Ниже мы рассмотрим детали, касающиеся мобильной разработки. Для наших целей неважны различия между OAuth2 и OpenID, поэтому дальше мы будем использовать общий термин OAuth.
В OAuth существуют различные flow, но не все подходят для использования в приложении:
-
Authorization Code Flow. Не подходит: код можно перехватить в зловредном приложении.
-
Resource Owner Password Credentials Flow. Требует введения credentials внутри приложения. Это нежелательно, если приложение и сервис не разрабатываются одной командой.
-
Client Credentials Flow. Подходит для авторизации самого клиента на основе
client_id,client_password. Не требует введения credentials от пользователя. -
Implicit Flow. Небезопасный и устаревший.
Принцип работы Authorization Code Flow with PKCE
Для мобильных клиентов рекомендуется использовать Authorization Code Flow c дополнением: Authorization Code Flow with Proof Key for Code Exchange (PKCE). Использовать именно этот flow важно для безопасности пользовательского входа в приложение. Рассмотрим его особенности.
Этот flow основан на обычном Authorization Code Flow. Сначала вспомним его реализацию:

-
Пользователь жмет кнопку Login.
-
Приложение создает ссылку для авторизации на сервисе авторизации и открывает его в браузере.
-
Пользователь видит экран с полями для ввода логина/пароля.
-
Пользователь вводит логин/пароль и подтверждает необходимые доступы к данным.
-
Сервис авторизации возвращает код авторизации в приложение с помощью редиректа. С кодом получить доступ к требуемым ресурсам из апи пока не получится. Чтобы редирект был перехвачен только приложением, обычно используются кастомные схемы, а не http(s). Иначе код может перехватить еще и браузер — в этом случае появляется окно выбора приложений.
-
Приложение получает код из URL редиректа и обменивает код на токен. Дополнительно могут передаваться
client_id,client_secret. -
Сервис авторизации возвращает
access_tokenдля доступа к ресурсам,refresh_token. -
Приложение с помощью полученного токена общается с сервисом API.
При использовании Authorization Code Flow with PKCE cхема немного меняется. Отличия выделены.

-
Пользователь жмет кнопку Login.
-
Генерируются code_verifier и code_challenge и сохраняются в приложении. Как происходит генерация, описано в RFC-7636.
code_challengeявляется производным отcode_verifier, обратная трансформация невозможна. -
Приложение создает ссылку для авторизации с учетом сгенерированного code_challenge. Ссылка открывается в браузере. В этот момент сервис авторизации тоже запоминает code_challenge для сессии. Таким образом, code_verifier остается только внутри приложения и не передается по сети.
-
Пользователь видит экран с полями для ввода логина/пароля.
-
Пользователь вводит логин/пароль.
-
Сервис авторизации возвращает код авторизации в приложение с помощью редиректа. Обратите внимание, что
code_challengeне возвращается от сервера вместе с кодом. О нем знают только сервис авторизации и мобильное приложение. -
Приложение обменивает код на токен. При обмене приложение отправляет code_verifier, который был сохранен в пункте 2.
-
Сервис авторизации принимает code_verifier от мобильного приложения. Вычисляет от него code_challenge и сравнивает с code_challenge, переданным в пункте 3. Если они совпадают — возвращается токен.
-
Сервис авторизации возвращает
access_tokenдля доступа к ресурсам,refresh_token. -
Приложение с помощью полученного токена общается с сервисом API.
Что могло бы произойти, если бы не использовались code_verifier и code_challenge?
MITM-атака для перехвата кода
Одной из реализаций OAuth является реализация с помощью внешнего браузера.
В таком случае код возвращается обратно в приложение с помощью системной функции: когда внутри браузера происходит редирект на URL, который может обработать ваше приложение — открывается ваше приложение.
Именно в момент, когда система ищет приложение для обработки URL редиректа, возможен перехват редиректа зловредным приложением. Злоумышленник может создать приложение, которое перехватывает такие же редиректы, как у вас. Утекают все данные, которые находятся в строке редиректа.

Именно поэтому в редиректе нужно возвращать промежуточный код, а не токен. Иначе токен будет доступен чужому приложению.
При использовании обычного Authorization Code Flow чужое приложение (Malicious app) потенциально может получить код и обменять его на токен, аналогично тому, как это сделано в вашем приложении (Real app). Но с использованием code_verifier и code_challenge зловредный перехват становится бессмысленным. Чужое приложение не знает code_verifier и code_challenge, которые были сгенерированы внутри вашего приложения, и в редиректе они не возвращаются.
Без этих данных зловредное приложение не сможет обменять код на токен.
Стоит отметить, что такая атака не сработает, если использовать universal links (ios) и applink (android). Чтобы открыть редирект-ссылку в приложении, необходимо положить на сервер json-файл с описанием подписи вашего приложения.
Но часто мы не можем добавить json-файл на сервер, если авторизуемся с помощью внешнего сервиса, который разрабатываем не мы. Поэтому не всегда это может помочь.
Нюансы реализации
Каким образом открывать страницу логина?
Страница логина в OAuth представляет из себя веб-страницу. Есть следующие способы:
-
Использовать WebView внутри вашего приложения.
-
Открыть страницу во внешнем браузере.
-
Использовать ChromeCustomTabs, SafariVC.
При выборе способа стоит иметь в виду, что основной задачей OAuth является предоставление приложению доступа к сервису без ввода credentials внутри приложения.
WebView
Преимущества:
-
При отображении веб-страницы с WebView мы можем кастомизировать ui экрана полностью, как нам нужно.
-
Сам экран с WebView будет открыт быстрее страницы в браузере: все происходит в рамках одного процесса, без межпроцессного взаимодействия.
Недостатки:
-
Реализация через WebView не является безопасной в общем случае, и некоторые соцсети не позволяют использовать такой способ реализации OAuth, например Google.
-
Общая проблема в том, что WebView находятся в рамках приложения. Создатель зловредного приложения может вклиниться между пользователем и сервисом, в котором пользователь авторизируется, и перехватить пароль и логин. Хотя одна из целей протокола OAuth — противостоять этому.
На практике удавалось это обойти путем подмены user agent. Но это не соответствует политике Google, и делать это нельзя.
-
WebView выполняет js в процессе вашего приложения, что небезопасно уже для самого приложения. Если вы используете WebView внутри, рекомендую ознакомиться с советами по настройке для обеспечения дополнительной безопасности.
-
С использованием WebView ухудшается пользовательское удобство. Пользователь мог быть уже авторизован в сервисе в браузере, но WebView об этом не узнает, так как хранилище cookie у вебвью и браузера разное.
Из-за недостатков WebView не лучший вариант для реализации OAuth в мобильном приложении.
Browser
Второй вариант — открыть страницу во внешнем браузере, установленном на устройстве.
Преимущества:
-
Открыть страницу в браузере очень просто.
-
Ваше приложение не имеет контроля над браузером и открытой веб-страницей. Это обеспечивает дополнительную безопасность для пользователя.
-
Браузер сохраняет cookie пользователя. А значит, если пользователь был уже залогинен в сервисе, ему не придется заново вводить credentials.
Недостатки:
-
Открытие браузера тяжеловесная операция, потому что нам нужно запустить внешний процесс.
-
Вы не можете настраивать UI браузера, он открывается во внешнем окне.
-
Открывая браузер, вы покидаете навигационный стек приложения.
ChromeCustomTabs, SafariVC
ChromeCustomTabs(CCT) и SafariViewController(SafariVC) аналогично браузеру позволяют легко реализовать открытие веб-страниц в вашем приложении.
Они закрывают недостатки WebView:
-
Злоумышленник не сможет перехватить вводимые данные на странице логина.
-
Данные доступны браузеру и CCT/SafariVC.
Обратите внимание: Начиная с ios 11, данные между браузером и между различными сессиями SafariVC больше не шарятся автоматически. Чтобы это реализовать, нужно использовать ASWebAuthenticationSession.
Пример: ссылка на Github Gist. -
JS выполняется во внешнем процессе, это обезопасит ваше приложение.
Недостатки браузера тоже частично закрываются:
-
CCT позволяет производить прогрев в фоне, что позволяет быстро начать загружать страницу при ее открытии.
-
Открытый CCT не понижает приоритет процесса вашего приложения, потому что это может привести к убийству процесса системой.
-
имеются возможности настройки внешнего вида, хотя и ограниченные: CCT, SafariVC.
CCT изначально был сделан только для Chrome, а сейчас поддерживается в разных браузерах. Помимо него, в Android есть еще TrustedWebActivity. Подробнее про них можно почитать на официальной странице.
Этот подход является самым оптимальным. Он закрывает почти все недостатки предыдущих двух подходов.
Редирект в Chrome не срабатывает
Как уже упоминали выше, для редиректа обратно в приложение лучше использовать кастомную схему, чтобы редирект не был перехвачен браузерами.
В процессе тестирования реализации OAuth в Android мы столкнулись с тем, что Chrome с использованием CCT после успешной авторизации не перебрасывал нас обратно в приложение на некоторых устройствах. На это заведен баг в трекере.
В Chrome сделали обновление, которое запрещает без пользовательского намерения переходить по URL с кастомной схемой. Это блокирует попадание пользователя в зловредное приложение.
Для обхода этого ограничения сделали веб-страничку, на которую браузер редиректит после успешной авторизации. Веб-страница автоматически пытается сделать редирект уже внутрь приложения. Если этого не происходит, то есть Chrome заблокировал переход, пользователь может нажать на кнопку enter и перейти явно. Этот подход сработал.

Обновление токенов
С использованием OAuth вам не нужно забывать об обновлении токенов.
Обычно это похоже на то, как менять код на токен. Вы обращаетесь к api для получения токена и указываете grant_type=refresh_token и refresh_token, который вы получили изначально при логине.
Более подробную реализацию рассмотрим в примере.
Браузер отсутствует
В Android, в отличие от iOS, может не быть браузера. Но он нам понадобится для использования CCT, причем с поддержкой этого способа.
Кроме Chrome, этот функционал поддерживается в SBrowser, Firefox и всех остальных современных браузерах. Но даже если такового у пользователя нет, откроется обычный браузер.
На официальной странице рассказывают, как проверить браузеры с поддержкой CCT.
Логаут
В большинстве случаев при пользовательском логауте в приложении нужно почистить токены/файлы/БД/кеши.
Если же для авторизации вы используете ссt/safarivc, потом в браузере остаются куки авторизованного человека. При повторном логине вы заново войдете под первым аккаунтом автоматически. Почистить cookie из приложения не получится, потому что браузер — это отдельный процесс со своим хранилищем, и доступ к нему запрещен.
Чтобы разлогиниться, необходимо открыть страницу в браузере, по которой сервер авторизации почистит куки и перенаправит вас в приложение обратно.
Варианты реализации OAuth
Мы рассмотрели OAuth flow для мобильных приложений и увидели, на какие нюансы стоит обратить внимание при реализации.
Существует несколько вариантов реализации.
Использовать SDK сервиса, через который вы хотите авторизоваться
Плюсы:
-
простая реализация;
-
возможна авторизация через нативные приложения, если они установлены.
Минусы:
-
увеличение внешних зависимостей, особенно при большом количестве внешних сервисов;
-
нет контроля над реализацией.
Использование SDK мы рассматривать в текущей статье не будем, потому что для этого нужно изучать документацию SDK.
Реализовать вручную
Реализовать логику вручную внутри собственного приложения с использованием WebView или других реализаций (CCT/SafariVC).
Плюс:
-
получаем полный контроль над реализацией.
Минус:
-
приходится писать свой код, поддерживать его и учитывать вручную нюансы, о которых говорили выше.
Ручную реализацию мы рассматривать не будем, потому что она индивидуальна для каждого приложения и сервиса.
Использовать библиотеки
Библиотеки должны поддерживать протоколы OAuth и OpenId и позволять общаться с любыми сервисами по этим протоколам. Примеры:
AppAuth IOS
AppAuth Android
Auth0 Android
-
При использовании этого подхода нужно убедиться, что сервер аутентификации работает в соответствии с протоколом, и вам не придется костылить библиотеку, чтобы связаться с ним.
-
Если разобраться с библиотекой и знать, как она работает, реализация получается достаточно простой. Но на это требуется время.
-
Реализация авторизации будет универсальная для разных сервисов, не придется подключать дополнительные зависимости и писать много кода для каждого внешнего сервиса, если таких несколько.
-
Учтите, что реализация библиотеки может быть не совсем удобной для встраивания в ваше приложение. Используемые подходы общения с библиотекой могут отличаться от принятых в команде, и нужно будет писать обертки-бриджи. Пример: AppAuth в Android использует AsyncTask под капотом, но в приложении вы, скорее всего, используете корутины. Но обычно такие вещи можно интегрировать.
В дальнейшем в статье мы рассмотрим реализацию входа с использованием библиотеки AppAuth. Тому есть несколько причин:
-
это достаточно популярная библиотека;
-
у нее есть реализации и для Android и для iOS .
Реализация в Android-приложении
Давайте посмотрим, как можно реализовать OAuth в вашем Android-приложении с использованием AppAuth. Весь код доступен на Github.
Приложение простое: отображение информации о моем github-профиле.
Для этого при каждом запуске приложения будем открывать страницу github-авторизации. После успешной авторизации переводим пользователя на главную страницу, откуда можно получить информацию о текущем пользователе.
При реализации нам необходимо разобраться с 3 ключевыми моментами:
-
авторизация пользователя;
-
обновление токена;
-
логаут пользователя.
Общая настройка
Первым делом зарегистрируем приложение OAuth в Github.
При регистрации установите CALLBACK_URL для вашего приложения на сервисе. На этот URL будет происходить перенаправление после авторизации, и ваше приложение будет его перехватывать.
В качестве CALLBACK_URL будем использовать ru.kts.oauth://github.com/callback
Не забывайте использовать кастомную схему ru.kts.oauth, чтобы только ваше приложение могло перехватить редирект.
После регистрации у вас должны быть доступны client_id и client_secret (его нужно сгенерировать). Сохраните их.
Дальше нужно понять, на какой URL нужно переходить для авторизации на веб-странице Github, и по какому обменивать код на токен. Ответ можно найти в документации по Github OAuth.
URL для авторизации: https://github.com/login/oauth/authorize
URL для обмена токена: https://github.com/login/oauth/access_token
Для авторизации нам нужно определить скоупы, к которым github предоставит доступ. Представим, что нам в приложении нужны доступ к информации пользователя и его репозиториям: user, repo.
С общими параметрами определились. Перейдем к Android-реализации.
Реализация Android
Подключим библиотеку в проект:
implementation 'net.openid:appauth:0.9.1'
Запишем все настройки OAuth в один объект, чтобы было легко с ним работать:
object AuthConfig { const val AUTH_URI = "https://github.com/login/oauth/authorize" const val TOKEN_URI = "https://github.com/login/oauth/access_token" const val END_SESSION_URI = "https://github.com/logout" const val RESPONSE_TYPE = ResponseTypeValues.CODE const val SCOPE = "user,repo" const val CLIENT_ID = "..." const val CLIENT_SECRET = "..." const val CALLBACK_URL = "ru.kts.oauth://github.com/callback" const val LOGOUT_CALLBACK_URL = "ru.kts.oauth://github.com/logout_callback" }
Тут по сравнению с общей настройкой добавились:
-
RESPONSE_TYPE. Используем константу “code” из библиотеки AppAuth. Эта константа отвечает за то, что будет возвращено на клиент после авторизации пользователем в браузере. Варианты:
code,token,id_token.В соответствии с OAuth Authorization Code Flow нам нужен
code.На самом деле Github api не требует передачи параметра
response_typeи всегда возвращает только код. Но данный параметр может потребоваться для других сервисов. -
END_SESSION_URI,LOGOUT_CALLBACK_URL. Настройки, необходимые для логаута.
Авторизация
Теперь откроем страницу авторизации с использованием CCT.
Для работы с CCT и выполнения автоматических операций обмена кода на токен библиотека AppAuth предоставляет сущность AuthorizationService. Эта сущность создается при входе на экран. При выходе с экрана она должна очиститься. В примере это делается внутри ViewModel экрана авторизации.
Создаем в init:
private val authService: AuthorizationService = AuthorizationService(getApplication())
Очищаем в onCleared:
authService.dispose()
Для открытия страницы авторизации в CCT нужен интент. Для этого получаем AuthorizationRequest на основе заполненных раньше данных в AuthConfig:
private val serviceConfiguration = AuthorizationServiceConfiguration( Uri.parse(AuthConfig.AUTH_URI), Uri.parse(AuthConfig.TOKEN_URI), null, // registration endpoint Uri.parse(AuthConfig.END_SESSION_URI) ) fun getAuthRequest(): AuthorizationRequest { val redirectUri = AuthConfig.CALLBACK_URL.toUri() return AuthorizationRequest.Builder( serviceConfiguration, AuthConfig.CLIENT_ID, AuthConfig.RESPONSE_TYPE, redirectUri ) .setScope(AuthConfig.SCOPE) .build() }
Создаем интент:
// тут можно настроить вид chromeCustomTabs val customTabsIntent = CustomTabsIntent.Builder().build() val openAuthPageIntent = authService.getAuthorizationRequestIntent( getAuthRequest(), customTabsIntent )
После этого открываем активити по интенту. Нам необходимо обработать результат активити, чтобы получить код.
Поэтому используем ActivityResultContracts. Также можно использовать startActivityForResult.
private val getAuthResponse = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { val dataIntent = it.data ?: return handleAuthResponseIntent(dataIntent) } getAuthResponse.launch(openAuthPageIntent)
Под капотом будут открыты активити из библиотеки, которые возьмут на себя ответственность открытия CCT и обработку редиректа. А в активити вашего приложения уже прилетит результат операции.

Внутри openAuthPageIntent будет зашита вся информация, которую мы раньше указывали в AuthConfig, а также сгенерированный code_challenge.

AppAuth генерирует URL для открытия страницы авторизации под капотом: https://github.com/login/oauth/authorize?redirect_uri=ru.kts.oauth%3A%2F%2Fgithub.com%2Fcallback&client_id=3fe9464f41fc4bd2788b&response_type=code&state=mrhOJm7ot4C1aE9ND3lWdA&nonce=4zVLkQrhQ4L46hfQ1jdTHw&scope=user%2Crepo&code_challenge=gs23wPEpmJYv3cdmTRWNSQLvvnPtHUhtSv4zhbfKS_o&code_challenge_method=S256
Чтобы редирект был обработан корректно, мы должны указать, что наше приложение умеет обрабатывать открытие URL с нашей кастомной схемой ru.kts.oauth. Для этого внутри build.gradle модуля приложения внутри секции defaultСonfig укажем manifest placeholder:
manifestPlaceholders = [ appAuthRedirectScheme: "ru.kts.oauth" ]
После этого в AndroidManifest.xml вашего приложения будет добавлена активити, которая обрабатывает ссылки с этой кастомной схемой. Merged manifest:
<activity android:name="net.openid.appauth.RedirectUriReceiverActivity" android:exported="true" > <intent-filter> ... <data android:scheme="ru.kts.oauth" /> </intent-filter> </activity>
Также вы можете настроить редирект с использованием стандартных схем. Более детально можно прочитать в описании к репозиторию.

Теперь мы можем открыть страницу логина:
Дальше необходимо получить код и обменять его на токен. При этом в ответе может прийти ошибка авторизации, ее тоже нужно обработать.
Библиотека AppAuth дает возможность из результирующего интента ответа получить ошибку или запрос для обмена кода на токен:
private fun handleAuthResponseIntent(intent: Intent) { // пытаемся получить ошибку из ответа. null - если все ок val exception = AuthorizationException.fromIntent(intent) // пытаемся получить запрос для обмена кода на токен, null - если произошла ошибка val tokenExchangeRequest = AuthorizationResponse.fromIntent(intent) ?.createTokenExchangeRequest() when { // авторизация завершались ошибкой exception != null -> viewModel.onAuthCodeFailed(exception) // авторизация прошла успешно, меняем код на токен tokenExchangeRequest != null -> viewModel.onAuthCodeReceived(tokenExchangeRequest) } }
Запрос на токен будет сформирован автоматически, в него будет добавлен тот code_verifier, code_challenge от которого передавался при открытии страницы авторизации. Поэтому вопрос его сохранения уже решен.
Вариант с ошибкой авторизации рассматривать не будем, тут можно показать Toast или Snackbar.
Мы получили запрос tokenExchangeRequest, который необходимо выполнить. Для этого используем AuthService.performTokenRequest.
Под капотом в методе performTokenRequest происходит запуск устаревшего AsyncTask, поэтому API построен на колбэках.
fun performTokenRequest( authService: AuthorizationService, tokenRequest: TokenRequest, onComplete: () -> Unit, onError: () -> Unit ) { authService.performTokenRequest(tokenRequest, getClientAuthentication()) { response, ex -> when { response != null -> { //обмен кода на токен произошел успешно, сохраняем токены и завершаем авторизацию TokenStorage.accessToken = response.accessToken.orEmpty() TokenStorage.refreshToken = response.refreshToken onComplete() } //обмен кода на токен произошел неуспешно, показываем ошибку авторизации else -> onError() } } }
Интерфейс колбэков можно достаточно просто превратить в suspend-вызов и использовать вместе с корутинами в вашем приложении. Вы можете посмотреть пример в проекте.
При выполнении обмена кода на токен по документации нам требуется отправлять client_secret. Поэтому при вызове метода performTokenRequest требуется передать объект ClientAuthentication. В библиотеке есть несколько имплементаций: ClientSecretBasic, ClientSecretPost, NoClientAuthentication. Выбирать нужно исходя из того, что требует сервер при обмене кода на токен.
В случае с Github необходимо отправить client_secret следующим образом:
private fun getClientAuthentication(): ClientAuthentication { return ClientSecretPost(AuthConfig.CLIENT_SECRET) }
Если сервис не требует client_secret, то можно использовать ClientSecretBasic("").
На этом мы закончили реализацию авторизации в Github с помощью AppAuth.
Еще раз кратко опишем шаги.
-
Подключаем библиотеку.
-
Создаем AuthConfig.
-
Указываем
manifestPlaceholder appAuthRedirectScheme. -
Создаем AuthorizationService, например во ViewModel.
-
Авторизуем пользователя в вебе:
-
создаем AuthorizationRequest;
-
формируем intent;
-
запускаем активити с CCT.
-
-
Меняем код на токен:
-
получаем
TokenExchangeRequestизactivity result intent; -
выполняем
TokenExchangeRequestс помощьюauthService.performTokenRequest; -
сохраняем токены в колбеке.
-
Логаут
Для логаута нам нужно не только почистить токен внутри приложения, но и почистить cookie в браузере. Просто так это сделать не получится, потому что браузер — это внешнее приложение. Для этого нужно открыть страницу, по которой у вас очистятся cookie.
Для гитхаба это https://github.com/logout. Раньше указали в AuthConfig.END_SESSION_URI.
Идея открытия страницы такая же, как для страницы авторизации:
1. Формируем request:
val endSessionRequest = EndSessionRequest.Builder(authServiceConfig) //Требуется для некоторых сервисов, idToken получается при авторизации аналогично accessToken и refreshToken .setIdTokenHint(idToken) // uri на который произойдет редирект после успешного логаута, не везде поддерживается .setPostLogoutRedirectUri(AuthConfig.LOGOUT_CALLBACK_URL.toUri()) .build()
2. Формируем custom tabs intent:
val customTabsIntent = CustomTabsIntent.Builder().build()
3. Формируем итоговый интент:
val endSessionIntent = authService.getEndSessionRequestIntent( endSessionRequest, customTabsIntent )
4. Открываем страницу логаута:
private val logoutResponse = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) {} logoutResponse.launch(endSessionIntent)
Пользователь переходит на страницу логаута, где чистится его сессия в браузере.

После логаута нам нужно перехватить редирект, чтобы вернуться в приложение. Не все сервисы позволяют указывать URL редиректа после логаута (github не позволяет). Поэтому пользователю нужно будет нажать на крестик в CCT.
После ручного закрытия активити с CCT мы получим result=cancelled, потому что редиректа в приложение не было.
В нашем примере с Github мы будем в любом случае очищать сессию и переходить на страницу входа в приложение.
private val logoutResponse = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { // очищаем сессию и переходим на экран логина viewModel.webLogoutComplete() }
Обновление токена
При работе с OAuth и библиотекой AppAuth вам, как и всегда, важно поддерживать актуальность ваших токенов. access_token, полученный с сервера, может протухнуть. Для того, чтобы не выбрасывать пользователя на страницу логина, нужно попробовать обновить токен в такой ситуации. Это делается с помощью refresh_token.
Механизм обновления похож на механизм получения token с помощью AppAuth:
1. Формируем request для обновления токена
val refreshRequest = TokenRequest.Builder( authServiceConfig, AuthConfig.CLIENT_ID ) .setGrantType(GrantTypeValues.REFRESH_TOKEN) .setScopes(AuthConfig.SCOPE) .setRefreshToken(TokenStorage.refreshToken) .build()
Тут нам важно учесть 2 строчки:
.setGrantType(GrantTypeValues.REFRESH_TOKEN) .setRefreshToken(TokenStorage.refreshToken)
В качестве grantType передаем refreshToken, и передаем непосредственно сам refreshToken из вашего хранилища, который был получен при авторизации.
2. Выполняем сформированный request:
authorizationService.performTokenRequest(refreshRequest) { response, ex -> when { response != null -> emitter.onSuccess(response) ex != null -> emitter.tryOnError(ex) else -> emitter.tryOnError(IllegalStateException("response and exception is null")) } }
Этот код можно внедрить в те места, где у вас происходит обновление токена в проекте. Например, в OkHttp interceptor. Полный пример можно взять в репозитории по ссылке.
Если обновление токена произошло с ошибкой (например, refresh_token невалидный), необходимо разлогинить пользователя.
См. пример с логаутом.
В сервисе Github токены OAuth не протухают, поэтому пример может быть использован в других сервисах.
Заключение
Код проекта в статье находится в моем репозитории на GitHub.
Мы рассмотрели нюансы реализации OAuth в мобильных приложениях и пример реализации в Android-приложении с помощью библиотеки AppAuth. Эта реализация позволит вам быстро засетапить OAuth в вашем приложении.
По нашему опыту, AppAuth позволяет упростить работу с OAuth в приложении, избавляя вас от написания деталей имплементаций. Однако она предъявляет требования к серверу авторизации. Если сервис не соответствует RFC-8252 (OAuth 2.0 for Native Apps), возможно, AppAuth покроет не весь требуемый функционал.
А как вы реализовывали OAuth в мобильных приложениях? были ли у вас проблемы? Использовали ли AppAuth?
ссылка на оригинал статьи https://habr.com/ru/company/kts/blog/654029/
Добавить комментарий