Код-ревью Senior Безопасность мобильных приложений обновлено
Код-ревью: хранение сессии и вход (Swift)
Вопрос
Найдите проблемы безопасности в коде входа.
enum Secrets {
static let paymentApiKey = "sk_live_51H8xQ2"
}
final class AuthService {
func login(email: String, password: String) async throws {
let session = try await api.login(email: email, password: password)
UserDefaults.standard.set(session.refreshToken, forKey: "refresh")
UserDefaults.standard.set(password, forKey: "password")
print("Logged in: \(email), token: \(session.accessToken)")
}
func unlockWithFaceID() async -> Bool {
let ok = (try? await LAContext().evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Вход")) ?? false
if ok { isLoggedIn = true }
return ok
}
}
Коротко
Исправленная схема: ключи платёжного провайдера только на сервере, клиент получает от своего бэкенда одноразовый токен платежа.
Что должен найти ревьюер
- Безопасностьстрока 2Секретный ключ платёжного API в бинарнике: его извлекут за минуты. Операции с ключом должен выполнять сервер.
- Безопасностьстрока 8Refresh-токен в
UserDefaultsхранится открытым текстом. Нужен Keychain. - Безопасностьстрока 9Пароль пользователя сохраняется на устройстве. Пароль не хранят вообще.
- Безопасностьстрока 10Email и access-токен в логах: попадут в системный журнал и крэш-репорты.
- Безопасностьстрока 15Биометрия только возвращает
true, и флаг ставится в коде: проверка обходится хуком. Биометрия должна открывать секрет из Keychain сSecAccessControl.
Разбор
Исправленная схема: ключи платёжного провайдера только на сервере, клиент получает от своего бэкенда одноразовый токен платежа. Refresh-токен в Keychain с доступностью AfterFirstUnlockThisDeviceOnly, а для входа по биометрии отдельная копия с SecAccessControl(.biometryCurrentSet): успешная биометрия буквально отдаёт токен. Пароль не сохраняется, логи без персональных данных.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.