Как реализовать обновление токенов и обработку ошибок авторизации в сети
Что проверяют
Умеете ли вы обновить токен так, чтобы десять параллельных запросов, получивших 401, не отправили десять запросов на refresh и не выкинули пользователя из сессии. Следом обычно спрашивают, где вы храните токены и что делать, если refresh тоже вернул ошибку.
Как отвечать
Логику обновления держат в одном месте сетевого слоя, а не в каждом вызове:
- перехват 401: у
Alamofireдля этого естьRequestInterceptorс методамиadapt(подставить заголовокAuthorization) иretry(решить, повторять ли запрос), а также готовыйAuthenticationInterceptor. На чистомURLSessionэто обёртка надdata(for:), которая ловит 401 и повторяет запрос один раз; - один refresh на всех: первый упавший запрос запускает обновление, остальные ждут его результата. В Swift Concurrency это
actor TokenManager, который хранит текущийTask<Token, Error>и отдаёт его всем ожидающим вместо запуска нового; - проактивное обновление: если сервер отдаёт
expires_in, токен обновляют заранее, за минуту до истечения, и 401 становится редкостью; - лимит повторов: запрос повторяется максимум один раз. Если со свежим токеном снова 401, это уже не протухший токен, а отозванная сессия;
- провал refresh: 401 или 400 от эндпоинта обновления значит, что refresh-токен недействителен. Чистим хранилище, отменяем очередь ожидающих запросов и ведём пользователя на экран входа. Сетевая ошибка при refresh это другой случай: разлогинивать из-за пропавшего интернета нельзя;
- хранение: оба токена в Keychain с подходящим атрибутом доступности, например
kSecAttrAccessibleAfterFirstUnlock, если обновлять нужно из фона.UserDefaultsдля этого не годится.
Где спотыкаются
Нет синхронизации: каждый упавший запрос сам зовёт refresh. При одноразовых refresh-токенах второй вызов инвалидирует результат первого, и пользователя разлогинивает на ровном месте.
Запрос повторяется без ограничений: 401 вызывает refresh, повтор снова получает 401, и приложение засыпает сервер запросами по кругу.
Путают 401 и 403. На 403 токен обновлять бессмысленно: пользователь аутентифицирован, но прав на ресурс у него нет.