Авторизация без аутентификации

Аутентификация и авторизация решают разные задачи

Большинство приложений начинается с барьера. Прежде чем что-то сделать, пользователь должен подтвердить личность: ввести почту, получить код подтверждения, придумать пароль или перейти по magic link. Этот шаг и есть аутентификация — подтверждение того, кто человек, относительно записи, которую хранит центральный орган.

Авторизация отвечает на другой вопрос: что этому человеку разрешено делать? Команды обычно объединяют оба действия в единый процесс «входа», но это разные операции. Их разделение выявляет полезную мысль.

Серверу не нужно знать, кто пользователь, чтобы обеспечивать соблюдение того, что ему разрешено.

JustCall построен на этом разделении. Здесь нет регистрации, нет таблицы аккаунтов, нет пароля и нет персональных данных на сервере. Авторизация несёт всю систему, тогда как аутентификация в привычном смысле отсутствует. Возможным это делают три элемента: приватный ключ, публичный ключ и делегированная роль, которую мы называем мастер-ключом.

Как идентичность на публичном ключе заменяет аккаунты

При первом открытии JustCall браузер локально генерирует секрет и никогда его не передаёт. Из этого секрета он выводит пару ключей, и публичная половина становится вашим идентификатором пользователя:

// The private key is generated on your device. The server never sees it.
privateKey = crypto.randomUUID();

// The PUBLIC half, encoded as text, becomes your user ID.
userId = await derivePublicUserId(privateKey);

Идентификатор — это сам публичный ключ, а не строка базы данных, указывающая на него. Одно это решение убирает несколько рисков, которые несут традиционные системы аккаунтов:

  • Нет шага регистрации. Сервер принимает запросы, подписанные идентификатором, как только впервые его видит.
  • Нет почты и телефона. Идентификатор выводится из случайного секрета на вашем устройстве, а не из персональных данных.
  • Нет пароля. Нет общего секрета, который надо хранить, хешировать или потерять, ведь сервер держит только публичный ключ.
  • Нет сброса пароля и нет credential stuffing, потому что нет повторно используемых учётных данных для атаки.

Вы никогда не раскрываете серверу личность. Вы криптографически доказываете, что владеете приватным ключом за данным публичным ключом, и этого доказательства достаточно как авторизации.

Как сервер авторизует запрос

Без сессий и токенов входа каждый запрос несёт собственное доказательство. Клиент собирает полезную нагрузку, подписывает её приватным ключом и отправляет обе части. На сервере авторизация сводится к одной проверке подписи — без поиска сессии и без обращения к хранилищу учётных данных:

const isValid = await verifyPayload(
  request.userId,   // the public key = the claimed identity
  request.payload,  // exactly what was signed
  request.signature,
);

if (!isValid) {
  return jsonResponse({ message: "invalid signature" }, 401);
}

Действительная подпись делает запрос подлинным и авторизованным за одну операцию. Злоумышленник не подделает запрос для идентификатора, приватным ключом к которому он не владеет, потому что не сможет создать соответствующую подпись. Владение приватным ключом и есть полномочие.

Мастер-ключ: связывание устройств без передачи секретов

Модель, в которой одно устройство держит единственный ключ, рушится, как только добавляется второе устройство. Если ваш телефон держит единственный ключ к вашей личности, ваш ноутбук для системы чужой.

Мастер-ключ решает это. Первое устройство становится мастером и держит исходный секрет, из которого выведен ваш идентификатор. Каждое дополнительное устройство генерирует собственную отдельную пару ключей. Новое устройство не может подписывать от вашего имени, и мастер-ключ никогда не копируется между устройствами. Связывание работает через делегирование: мастер подписывает короткий сертификат, привязывающий публичный ключ нового устройства к вашему идентификатору.

// Only the master can authorize a new device.
const authorization = {
  action: "device-authorization",
  userId: identity.userId,
  devicePublicKey: newDevice.devicePublicKey, // the new device's PUBLIC key
  role: newDevice.role,
  createdAt: Date.now(),
};
const signature = await signPayload(identity.privateKey, authorization);

С этого момента новое устройство подписывает свои запросы собственным ключом, а сервер проверяет их по ключу, за который поручился мастер. Мастер выдаёт удостоверение, ни разу не раскрыв свой секрет, ровно так же, как удостоверяющий центр ручается за сайт, приватным ключом которого он не владеет.

Так вы получаете весь словарь продакшен-системы прав: роли, делегирование, отзыв и минимум привилегий, выраженные целиком через подписи. Мастер может переименовать или отозвать любое связанное устройство, а отозванное устройство не проходит следующую проверку подписи, и его активное соединение закрывается.

Что хранит сервер без аккаунтов

Посмотрите, что на самом деле содержит база данных. Каждый аккаунт хранит выбранное пользователем отображаемое имя, необязательный аватар и список публичных ключей устройств. И больше ничего. Нет почты, телефонов, хешей паролей и контрольных вопросов, и нет приватных ключей, которые никогда не покидают устройства пользователя.

Рассмотрите классический сценарий утечки, когда уходит вся база. Злоумышленник получает набор публичных ключей — публичных по замыслу и бесполезных без соответствующих приватных ключей — плюс горсть выбранных пользователями имён. Он не может никого выдать за себя, ведь для этого нужен приватный ключ, который сервер никогда не хранил. Он не может сопоставить пользователей с дампом паролей, ведь паролей нет. Он не может фишить через «вашу почту», ведь её никогда не собирали.

Система не может слить данные, которых никогда не держит. Самый надёжный способ защитить персональные данные — не собирать их, и модель личности, где идентификатор — это публичный ключ, делает это буквальным.

Почему авторизация без паролей снижает риск утечки

Сравните поверхности атаки напрямую. Классическому приложению с почтой и паролем приходится защищать базу учётных данных, процессы сброса пароля, доставку кодов по почте и SMS, токены сессий и массив регулируемых персональных данных, за которые оно теперь отвечает. Каждый из этих пунктов — постоянный источник захватов аккаунтов и инцидентов с утечками.

Модель на публичном ключе защищает одно: чтобы пользователь сохранил свой ключ. Это настоящий компромисс, ведь ни один центральный орган не сбросит утерянный ключ. Взамен исчезает каждый пункт предыдущего списка. Ущерб от компрометации сервера сужается до того, что злоумышленник прочитает публичные ключи и отображаемые имена — информацию, которая никогда не была чувствительной.

Как применять авторизацию по публичному ключу в своём приложении

Шаблон обобщается далеко за пределы приложения для звонков:

  • Сделайте личностью пару ключей, а идентификатором пользователя — публичный ключ. Генерируйте его на клиенте, никогда не передавайте приватную половину и позвольте аккаунту возникать при первой же действительной подписи, которую видит сервер.
  • Авторизуйте проверкой подписей, а не проверкой сессий. Каждое действие несёт полезную нагрузку и подпись. Подписывайте канонические байты, чтобы клиент и сервер сходились в том, что именно подписано.
  • Используйте мастер-ключ для делегирования, а не для раздачи. Когда субъекту нужно расширить доверие на второе устройство, коллегу или API-клиент, пусть корневой ключ подпишет ограниченный отзываемый сертификат для нового ключа. Корневой секрет никогда не двигается.
  • Храните только публичный материал, чтобы ответ на вопрос «что будет при утечке» оставался скучным.

Несколько ограничений заслуживают внимания до запуска. Хранение ключа становится центральной задачей, поэтому продакшен-системы добавляют варианты восстановления — социальное восстановление, необязательную зашифрованную резервную копию или аппаратный ключ. Подписанным запросам нужна метка времени или nonce против повтора. Поскольку личности нет, защита от спама смещается к ограничению частоты или приглашениям вместо подтверждения почты. Ничто из этого не подрывает модель. Это обычные заботы любой системы, только без базы паролей посередине.

Частые вопросы

Как приложение может авторизовать пользователя без аутентификации? Оно считает владение приватным ключом полномочием. Публичный ключ служит идентификатором пользователя, каждое действие подписывается соответствующим приватным ключом, а сервер проверяет эту подпись по публичному ключу. Пользователь доказывает, что ему разрешено действовать, не доказывая личность центральной системе аккаунтов.

Менее ли безопасна модель без аккаунта, чем почта и пароль? В большинстве аспектов она безопаснее. Нет базы паролей для утечки, нет процесса сброса для злоупотреблений и нет персональных данных для потери. Компромисс — хранение ключа: если пользователь теряет ключ без резервной копии, ни один центральный орган не восстановит аккаунт.

В чём разница между аутентификацией и авторизацией? Аутентификация устанавливает, кто вы. Авторизация устанавливает, что вам разрешено делать. Большинство систем их объединяют, но пара ключей позволяет второе без первого.

Что будет, если серверы JustCall взломают? Злоумышленник найдёт публичные ключи и отображаемые имена. Публичные ключи и должны быть публичными и бесполезны без приватных, которые никогда не покидают ваши устройства. Нет ни паролей, ни почты, ни номеров телефонов для кражи.

Можно ли пользоваться несколькими устройствами без аккаунта? Да. Первое устройство выступает мастером и подписывает сертификат, привязывающий публичный ключ каждого нового устройства к вашему идентификатору. Мастер-секрет никогда не копируется, а мастер может отозвать любое устройство в любой момент.

Похожие материалы