коротко

Аутентификация отвечает на вопрос «кто ты» (проверка личности — логин, пароль, токен). Авторизация — «что тебе можно» (какие действия и данные разрешены — это отдельная тема про 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 ограничивают, что именно может делать токен, и часто связаны с ролевой моделью, где роль пользователя разворачивается в набор прав.