Система ролей та прав доступу мобільного додатку
Права доступу в мобільному додатку — не просто if (user.role == "admin") перед кнопкою. Коли ролі розмножуються, з'являються вложені дозволи, тимчасовий доступ, гранулярні політики — примітиви розсипаються в розкиданий по коду хаос, який неможливо протестувати та легко сломати.
Моделі управління доступом
Вибір моделі — перше архітектурне рішення. Три основні варіанти:
RBAC (Role-Based Access Control) — роль прив'язана до набору дозволів. Користувач отримує роль, роль визначає доступ. Просто, зрозуміло, працює для більшості B2B-додатків. Проблема: ролі розростаються. Починається з admin, manager, viewer — закінчується 40 ролями з частковим перекриттям.
ABAC (Attribute-Based Access Control) — доступ визначається атрибутами суб'єкта, ресурсу, контексту. Правило типу allow if user.department == resource.department AND action == "read" AND time.hour < 18. Гнучкіше RBAC, але складніше у реалізації.
ReBAC (Relationship-Based Access Control) — доступ через граф відношень. Google Zanzibar, open-source: OpenFGA, SpiceDB. Добре для систем типу Google Drive.
Для більшості корпоративних мобільних додатків достатньо RBAC з розширенням через permission-флаги.
Реалізація на клієнті
Золоте правило: клієнт ничому не довіряє сам собі. Перевірки прав на мобільному — це UX-шар, не захист. Реальна захист — на backend. Але UX без правильної реалізації прав приводить до того, що користувач бачить кнопки, які він натиснути не може, або не бачить функції, які йому доступні.
На Android паттерн через ViewModel:
data class UserPermissions(
val canCreateOrder: Boolean,
val canApproveOrder: Boolean,
val canViewReports: Boolean,
val managedDepartments: List<String>
)
class OrderViewModel(
private val permissionsRepository: PermissionsRepository
) : ViewModel() {
val permissions = permissionsRepository.currentPermissions
.stateIn(viewModelScope, SharingStarted.Eagerly, UserPermissions())
fun createOrder(order: Order) {
check(permissions.value.canCreateOrder) { "Access denied" }
// ...
}
}
У Compose скриваємо недоступні елементи, а не просто робимо disabled:
val permissions by viewModel.permissions.collectAsState()
if (permissions.canCreateOrder) {
Button(onClick = { viewModel.createOrder(draft) }) {
Text("Створити заявку")
}
}
На iOS аналогічна логіка через @Published у ObservableObject:
class PermissionsManager: ObservableObject {
@Published private(set) var permissions: UserPermissions = .empty
func refreshPermissions() async {
guard let token = authService.currentToken else { return }
permissions = try await permissionsAPI.fetch(token: token)
}
}
Зберігання та синхронізація
Дозволи приходять з backend при логіні та кешуються локально. Критично: кеш повинен інвалідуватися при зміні ролі. Стандартний підхід — TTL (15–30 хвилин) + принудовий рефреш при старті сесії.
Якщо роль може змінюватися в реальному часі (керівник відозве доступ), потрібні WebSocket-уведомлення або server-sent events з принудовим рефрешем дозволів без перелогіну.
На Android — EncryptedSharedPreferences для кеша дозволів. На iOS — Keychain для токена, UserDefaults з кодуванням через Codable для кеша прав.
Типові проблеми
Гонка станів при логіні. Дозволи грузяться паралельно з навігацією. Користувач встигає натиснути до отримання дозволів. Рішення: екран завантаження доки не отримаємо дозволи, або оптимістичний UI з перепровіркою.
Hardcoded перевірки в UI. if (role == "admin") в 30 екранах. Рефакторинг болючий. Рішення з самого початку: централізований PermissionsManager, UI читає лише з нього.
Немає логування відмов. Backend відхиляє з 403, мобільний показує generic помилку. Невідомо хто, що, чому не мога зробити. Потрібен логінг через Firebase Analytics або Sentry з permission_denied event та контекстом.
Сроки та вартість
Проектування моделі доступу, реалізація PermissionsManager, інтеграція з API, UI-адаптація, тестування сценаріїв — 2–4 тижні в залежності від кількості ролей та екранів. Якщо потрібна реалізація ReBAC з OpenFGA — додати налаштування backend-частини. Вартість розраховується індивідуально.







