Авторизация — это не про код. Это про язык, на котором продукт описывает «кто что может». Если язык богатый и точный — код пишется почти сам. Если язык бедный — никакая библиотека не спасёт.

Что важно зафиксировать в начале
- Кто такие subject и principal — и почему это не всегда один человек.
- Что такое resource в твоей системе и как они вкладываются друг в друга.
- Какие действия атомарны, а какие — композитные.
Дальше всё становится механикой: scopes ложатся на действия, роли — на наборы scopes, делегирование — на принципалов. Но если язык в начале размыт, эта механика будет разваливаться на каждом новом требовании.
Три ловушки, в которые я видел, как падают команды
- Роль как мешок прав. «admin», «manager», «viewer» — и через год у тебя двадцать ролей, которые отличаются на одно действие. Роль должна быть именем для набора scopes, а не свалкой.
- Проверка доступа размазана по коду. Если
if user.role == 'admin'встречается в сорока местах — у тебя нет модели доступа, у тебя есть сорок мнений о ней. Решение одно: одна точка, которая отвечает на вопрос «можно ли subject сделать action над resource». - Делегирование прикручивают последним. «дай моему помощнику доступ от моего имени» — это не фича на потом, это меняет саму форму модели. Закладывай principal ≠ subject с первого дня.
Что мы выбрали
Ресурсо-ориентированную модель поверх OAuth2: scopes описывают действия над типами ресурсов, токен несёт scopes и subject, а проверка «этот subject над этим конкретным resource» живёт в одном сервисе авторизации, который дёргают все остальные. JWT — для stateless-проверки scope, обращение к сервису — когда нужно решение по конкретному объекту.
Хорошая модель доступа читается как предложение: «этот subject может выполнить это action над этим resource». Если описать конкретный кейс этим предложением не получается — модель ещё не готова.