Как лучше организовать ответственность за авторизацию в веб-приложении
Что проверяют
Понимаете ли вы, что настоящая авторизация живёт на сервере, а фронтенд отвечает только за удобство. Следом обычно спрашивают, где хранить токен и как интерфейс узнаёт о правах пользователя.
Как отвечать
Сначала разведите понятия: аутентификация отвечает на вопрос «кто вы», авторизация на вопрос «что вам можно». Затем распределите ответственность:
- бэкенд: единственный источник истины. Каждый эндпоинт проверяет права сам и отвечает
401, если пользователь не опознан, и403, если опознан, но прав нет. Всё, что проверено только в браузере, обходится запросом из консоли; - сессия: токен или идентификатор сессии в cookie с флагами
HttpOnly,SecureиSameSite, тогда скрипт на странице его не прочитает. Хранение вlocalStorageоткрывает токен любому XSS; - права в клиенте: приложение получает профиль и список разрешений, например из
/api/me, и кладёт их в контекст или стор; - одна функция проверки:
can(user, 'edit', article)вместо россыпиuser.role === 'admin'по компонентам. Для ABAC есть готовые библиотеки вроде CASL; - маршруты: защищённый роут или загрузчик данных проверяет доступ до рендера и уводит на вход. В React Router шестой версии и новее это
<Navigate>илиredirectизloader, а не устаревший<Redirect>; - HTTP-слой: перехватчик ловит
401, пробует обновить токен или отправляет на вход, а403показывает понятным сообщением.
Если есть SSR, проверку делают и на сервере рендеринга, иначе закрытая страница успеет отдать данные в HTML.
Где спотыкаются
Считают, что скрытая кнопка и есть защита. Кнопку прячут ради удобства, а запрос всё равно должен упереться в проверку на API.
Зашивают роли прямо в компоненты. При появлении новой роли приходится искать сравнения по всему коду, а централизованная can решает это одной правкой.
Хранят JWT в localStorage и не думают о сроке жизни. Короткий access-токен и refresh в HttpOnly cookie снижают ущерб от утечки, а отзыв прав должен срабатывать без ожидания, пока истечёт долгий токен.