Аутентификация отвечает на вопрос «кто ты» (проверка личности — логин, пароль, токен). Авторизация — «что тебе можно» (какие действия и данные разрешены — это отдельная тема про RBAC, ABAC и доступ на уровне строк). Это разные вещи, и их постоянно путают. OAuth2 — стандарт, позволяющий стороннему приложению получить доступ к вашим данным в другом сервисе, не отдавая ему пароль: вместо пароля приложение получает временный access token. JWT — популярный формат такого токена: самодостаточная строка из трёх частей (header.payload.signature), внутри которой зашита информация о пользователе и правах, подписанная сервером. Scopes — это перечень того, что токену разрешено («только читать профиль», «читать и писать заказы»).
Однажды на ревью спеки я спросил: «А что значит „пользователь авторизован“ в этом сценарии — он вошёл или ему можно сюда?» Повисла пауза. Выяснилось, что бэкендер имел в виду «вошёл в систему», а аналитик — «имеет право видеть этот раздел». В коде это два разных места, две разные ошибки (401 и 403), и из-за смешения этих слов сценарий был дырявым. С тех пор я развожу эти два понятия в спеке жёстко.
Аутентификация и авторизация: «кто ты» против «что можно»
Самая частая путаница в этой теме — и стоит она дорого, потому что слова похожи, а смысл разный.
Аутентификация (authentication) — проверка, кто вы. Вы показываете удостоверение, система убеждается, что вы — это вы. Логин с паролем, код из СМС, токен — всё это про аутентификацию. Если не прошли — сервер отвечает 401 Unauthorized (несмотря на название, это про «не представился»).
Авторизация (authorization) — проверка, что вам разрешено. Вы уже известны системе, вопрос в правах: можете ли вы открыть этот раздел, удалить этот заказ, увидеть чужие данные. Если прав нет — сервер отвечает 403 Forbidden («ты известен, но сюда нельзя»). Коды ответов разбираются подробнее в записи про REST API.
Аналогия с офисом
Аутентификация — охранник на входе сверяет ваш пропуск с лицом: «да, это вы». Авторизация — тот же пропуск открывает дверь в бухгалтерию, но не открывает серверную. Сначала система узнаёт, кто вы; потом решает, куда вас пускать. Два разных шага, и в требованиях их надо разделять.
OAuth2 на пальцах: доступ без пароля
Представьте: приложение-планировщик хочет читать ваш календарь Google, чтобы не назначать встречи внахлёст. Отдать ему ваш пароль от Google — безумие: он получит доступ ко всей почте, и пароль придётся менять, чтобы его отозвать. OAuth2 придуман ровно для этого: дать одному сервису ограниченный доступ к вашим данным в другом, не показывая пароль.
В OAuth2 четыре роли — их полезно знать наизусть, потому что в любой спеке на интеграцию вы будете расставлять, кто здесь кто:
- Владелец ресурса — вы, пользователь, чьи данные защищены.
- Клиент — приложение, которое хочет доступ (тот самый планировщик).
- Сервер авторизации — тот, кто проверяет вас и выдаёт токены (например, аккаунты Google).
- Сервер ресурсов — где лежат данные и кто отдаёт их по токену (API календаря).
Ключевая идея: вместо пароля клиент получает access token — временный пропуск с ограниченными правами. Токен можно отозвать, он протухает сам, и он не открывает доступ ни к чему сверх разрешённого. Пароль остаётся только между вами и Google.
Типичный сценарий: authorization code flow
sequenceDiagram participant U as Пользователь participant C as Клиент (приложение) participant A as Сервер авторизации participant R as Сервер ресурсов (API) U->>C: «Подключить мой календарь» C->>A: редирект: дай доступ к календарю (scope) A->>U: страница входа + «разрешить приложению?» U->>A: ввёл пароль, нажал «разрешить» A-->>C: код авторизации (одноразовый) C->>A: меняю код на access token (+ секрет клиента) A-->>C: access token (scope: calendar.read) C->>R: запрос к API + access token R-->>C: данные календаря
Схема описывает самый распространённый сценарий OAuth2 — authorization code flow. Пользователь нажимает «подключить календарь» в приложении. Приложение не спрашивает пароль само, а перенаправляет пользователя на сервер авторизации (Google) с указанием, какой доступ нужен (scope). Google показывает свою страницу входа и спрашивает «разрешить этому приложению читать календарь?». Пользователь вводит пароль на стороне Google и соглашается — приложение пароль не видит. В ответ приложение получает одноразовый код авторизации и тут же меняет его на access token, подтверждая при этом и свою личность секретом клиента. Дальше приложение ходит в API календаря, прикладывая токен, и получает данные. Пароль за всю цепочку ни разу не попал в чужие руки.
Confidential vs public clients и зачем PKCE
В схеме выше клиент подтверждает личность секретом клиента — но это работает только для confidential-клиента: серверного бэкенда, где секрет лежит в защищённом окружении и до него не дотянуться. У public-клиента (мобильное приложение, SPA в браузере) секрета быть не может — что зашьёшь в установленное на устройство приложение, то любой расковыряет; спрятать там нечего. Для таких клиентов authorization code flow дополняют PKCE (Proof Key for Code Exchange, RFC 7636): клиент сам генерит случайный секрет на лету (code_verifier), при запросе кода присылает его хеш (code_challenge), а при обмене кода на токен предъявляет оригинал — так доказывает, что код пришёл именно к нему, а не перехвачен. OAuth 2.1 вообще делает PKCE дефолтом для любого code flow, в том числе для confidential-клиентов. В спеке на мобильную или браузерную интеграцию тип клиента и наличие PKCE надо называть явно — иначе легко спроектировать «как на сервере», а на устройстве секрет утечёт.
JWT: самодостаточный токен
Токен бывает разный. Один из самых популярных форматов — JWT (JSON Web Token). Это строка из трёх частей, разделённых точками: header.payload.signature.
- Header — что это за токен и каким алгоритмом подписан.
- Payload — полезная нагрузка: кто пользователь, его id, права, когда токен истекает. Это обычный JSON, закодированный в строку (не зашифрованный — кто угодно может прочитать, поэтому пароли туда не кладут).
- Signature — подпись, которой сервер заверяет, что содержимое не подменили.
Главная фишка JWT — он самодостаточный. Внутри уже лежит всё, что нужно: кто пользователь и что ему можно. Сервер проверяет подпись и доверяет содержимому, не заглядывая в базу при каждом запросе. Это снимает нагрузку и позволяет не хранить состояние сессии на сервере — удобно, когда серверов много.
Обратная сторона самодостаточности
Раз сервер не ходит в базу, чтобы проверить токен, он не может и легко его отозвать. Выдали JWT на час — он будет действовать час, даже если пользователя забанили пять минут назад. Поэтому JWT делают короткоживущими и придумывают обходные пути (списки отзыва, refresh-токены). Если в спеке есть требование «мгновенно разлогинить пользователя» — это прямой вопрос к выбору JWT, и его стоит проговорить с командой.
Пара access + refresh
На практике эту дилемму «короткий токен неудобен, долгий — не отозвать» решают, выдавая не один токен, а пару. Access-токен — короткоживущий (минуты), его прикладывают к каждому запросу к API; протух — запрос отвергнут. Refresh-токен — долгоживущий (дни-недели), хранится надёжнее и в API не светится: его единственная задача — обменяться на новый access-токен, когда прежний истёк. Так система получает оба свойства сразу: быстро реагирует на отзыв (access живёт минуты) и не дёргает пользователя паролем каждые пять минут. А отзыв доступа при таком раскладе — это инвалидация refresh-токена на сервере: его помечают недействительным, и как только текущий короткий access протухнет, обновить его уже не выйдет — пользователь выпадет из системы.
Scopes и ролевая модель
Scope — это конкретное разрешение, выданное токену. Не «полный доступ», а перечень: calendar.read (только читать календарь), orders.write (создавать заказы). Когда приложение запрашивает доступ, оно перечисляет нужные scopes, пользователь видит их на экране согласия («приложение хочет: читать ваш календарь») и соглашается осознанно. Это и есть авторизация в действии: токен носит с собой ровно те права, что ему дали.
Scopes часто связывают с ролевой моделью (RBAC): пользователю назначают роль (например, «менеджер»), а роль уже разворачивается в набор прав. В требованиях полезно описывать именно матрицу «роль → что можно», а scopes — это техническая форма, в которой эта матрица доезжает до API.
Откуда это взялось
Проблема «дать доступ, не отдавая пароль» остро встала в середине 2000-х, когда соцсети начали интегрироваться друг с другом и сторонние приложения просили доступ к аккаунтам. OAuth 1.0 родился около 2007 года в этой среде (заметную роль сыграл Twitter и его экосистема) — но был сложным из-за криптографических подписей на стороне клиента. OAuth 2.0 переработали и оформили как RFC 6749 в 2012 году: проще, гибче, опирается на HTTPS вместо ручных подписей. Формат JWT стандартизировали чуть позже — RFC 7519. Эта связка и стала сегодняшним стандартом входа «через Google / GitHub / VK».
Как это спрашивают на собесе
«Чем authentication отличается от authorization?» → ждут чёткого «authn — кто ты, authz — что тебе можно» и привязку к кодам: не представился — 401, представился, но прав нет — 403. Путаница этих двух слов или кодов — красный флаг даже для джуна.
«Где хранить JWT на фронте — localStorage или cookie?» → ждут, что назовёте httpOnly-cookie: её не прочитать из JavaScript, поэтому XSS-скрипт не угонит токен. localStorage читается любым скриптом на странице и потому уязвим к XSS. Мидл добавит цену httpOnly-cookie — она автоматически уходит с каждым запросом и потому открывает фланг для CSRF, который закрывают отдельно (SameSite, CSRF-токен).
«Что такое refresh-токен и зачем он?» → ждут связку «короткий access + долгий refresh»: access живёт минуты ради быстрой отзываемости, refresh живёт долго ради удобства и служит только для получения нового access. Граница мидла — добавить, что отзыв сводится к инвалидации refresh-токена на сервере.
«Зачем нужен PKCE?» → вопрос уровня мидла. Ждут: у публичного клиента (мобильное, SPA) нет места спрятать секрет клиента, и PKCE заменяет его доказательством владения кодом — клиент генерит секрет на лету и предъявляет при обмене. Бонус — что OAuth 2.1 делает PKCE дефолтом.
Подвох: «JWT можно отозвать?» → правильный ответ — напрямую нет. Подписанный токен действует до истечения TTL, потому что сервер не сверяется с базой на каждом запросе. Обходят это коротким TTL и списками отзыва (или отзывом refresh-токена). Ответ «да, просто удаляем из базы» выдаёт непонимание самодостаточности JWT.
Частые вопросы
Чем аутентификация отличается от авторизации?
Аутентификация отвечает на вопрос «кто ты» — это проверка личности (логин, пароль, токен, код из СМС). Авторизация отвечает «что тебе можно» — это проверка прав на конкретное действие или данные. Сначала система убеждается, что вы — это вы (аутентификация), потом решает, куда вас пускать (авторизация). В HTTP им соответствуют коды 401 (не представился) и 403 (представился, но нельзя).
Что такое JWT простыми словами?
JWT (JSON Web Token) — это формат токена в виде строки из трёх частей через точку: header.payload.signature. В payload зашита информация о пользователе и его правах, signature — подпись сервера, подтверждающая, что данные не подменили. JWT самодостаточен: сервер проверяет подпись и доверяет содержимому, не заглядывая в базу. Минус — такой токен трудно отозвать досрочно, поэтому его делают короткоживущим.
Что такое scope в OAuth2?
Scope — это конкретное разрешение, которое получает токен: например, calendar.read (только читать календарь) или orders.write (создавать заказы). Приложение запрашивает нужные scopes, пользователь видит их на экране согласия и осознанно разрешает. Scopes ограничивают, что именно может делать токен, и часто связаны с ролевой моделью, где роль пользователя разворачивается в набор прав.