Web3-аутентификация: адрес, подпись и сессия
Оглавление курса «Web3: вход, смарт-аккаунты и индексатор» · Урок 1 из 30.
Подключённый кошелёк показывает браузеру адрес, но сервер личного кабинета не должен доверять этой строке. Человек мог отправить любой адрес обычным HTTP-запросом. В этом курсе построим вход с проверяемым сообщением, затем историю реестра и модель смарт-аккаунта. Первый результат — понятная граница между адресом, доказательством действия и серверной сессией.
Используем ту же учебную библиотеку и курс17. Базовый CourseCatalog не меняется: он объявляет выпуск, а не хранит логины и личные данные. Предполагается знакомство с интерфейсом реестра, JavaScript и обычной HTTP-моделью. Примеры, сервер, подписи и тесты не запускались. Показанные состояния являются ожидаемыми.
Адрес не является паролем
Адрес публичен и может быть скопирован из истории сети. Его получение через расширение удобно браузерному интерфейсу, но сервер не присутствовал при этом разрешении. В запросе /account клиент может передать адрес издателя, даже не имея никакого кошелька. Если сервер сразу создаст кабинет, публичная строка превратится в бесполезную замену пароля.
Подпись сообщения позволяет проверить разрешённое действие относительно адреса и конкретного сообщения. Для обычного аккаунта проверяется соответствие криптографической подписи. Для контрактного аккаунта применяется его собственный механизм, например ERC-1271. Нельзя объявлять любой Ethereum-адрес аккаунтом одного типа и проверять всё только восстановлением одного EOA-подписанта.
Подписанное сообщение также не должно быть вечным пропуском. Если оно не содержит одноразовую серверную величину и ограничение назначения, старую подпись можно повторно предъявить. Поэтому вход начинается с challenge: сервер формирует конкретное предложение доказать контроль адреса для конкретной сессии и срока. Этот challenge отличается от nonce транзакции Ethereum, который управляет порядком сетевых операций.
Три участника входа
Браузер получает challenge, показывает человеку его смысл и просит кошелёк подписать. Кошелёк предоставляет подпись по явному действию пользователя. Сервер проверяет сообщение, подпись, допустимый домен, сеть, срок и одноразовую запись. Только после всех проверок он выдаёт собственный идентификатор сессии.
Сессия не передаёт серверу приватный ключ. Она предоставляет обычный веб-доступ в пределах правил сайта. При каждом защищённом запросе сервер проверяет свою сессию и отдельно решает, разрешено ли конкретное действие. Подпись входа не является разрешением публиковать курс от имени пользователя и не даёт серверу право отправлять транзакции его аккаунта.
Для нашей библиотеки читатель может сохранять список изученных материалов в кабинете. Эти данные не размещаются в CourseCatalog. Если позже появится проверка платного доступа, она станет отдельной авторизационной политикой. Факт входа сообщает о подтверждённом аккаунте, но сам по себе не означает владение токеном или оплату курса.
Модель состояния входа
Начнём с самостоятельного файла login-model.js. Он описывает этапы, не выполняя HTTP-запросов и не взаимодействуя с настоящим кошельком.
export const loginStates = {
anonymous: { canReadPublic: true, hasSession: false },
challengeReady: { canReadPublic: true, hasSession: false },
awaitingSignature: { canReadPublic: true, hasSession: false },
verifying: { canReadPublic: true, hasSession: false },
authenticated: { canReadPublic: true, hasSession: true },
};
export function maySaveProgress(session, courseId) {
return Boolean(session && !session.revoked && session.expiresAt > Date.now())
&& courseId === 17n;
}
Функция является учебной моделью серверного решения; реальный сервер получает сессию из контролируемого хранилища, а не из JSON посетителя. Время также берётся из серверной политики. Если клиент передаёт hasSession: true, это не делает его авторизованным. Модель помогает назвать свойства, но не создаёт защищённый endpoint сама по себе.
Публичное чтение остаётся доступным во всех состояниях. Отказ подписи не закрывает статьи. Человек может изучать курс17 без кабинета, а вход нужен для персональной функции. Такое разделение уменьшает принудительные запросы кошелька и делает назначение подписи ясным.
Назначение подписи
Сообщение входа говорит об аутентификации на определённом домене. Оно не содержит скрытое разрешение на перевод токенов или смену издателя. Разные виды подписи не смешиваются в одном универсальном тексте. Человек должен видеть, что сейчас происходит: вход в сайт, а не запись в цепочку.
Если приложению нужно подписанное разрешение отдельного действия, создаётся другой формат с собственным доменом, nonce и сроком. Серверная сессия не превращается в разрешение выполнять всё от имени аккаунта. Позже рассмотрим EIP-712 именно как отдельный прикладной механизм, сохраняя его отличие от входа.
Кошелёк может отказать или вернуть подпись неподходящего аккаунта. Сервер не доверяет строке, показанной ранее на странице: он проверяет фактическое сообщение и полученный результат. Смена аккаунта во время входа делает старый браузерный контекст устаревшим. Пользователь начинает новый challenge для текущего адреса вместо незаметной подмены полей.
Сессия и изменение сети
Серверная сессия связывается с адресом и правилами входа. Переключение сети кошелька не меняет её автоматически на другой аккаунт. Однако для контрактного адреса проверка подписи и политика её продолжения могут зависеть от сети и состояния контракта. Поэтому сеть входа фиксируется, а дальнейшие правила рассматриваются явно.
Если право читателя проверяется по состоянию сети, это ещё одно наблюдение с собственной свежестью и источником. Не следует хранить его навсегда только потому, что одна подпись входа была правильной. Сессия подтверждает участника, авторизация оценивает право, индексатор предоставляет данные наблюдения. Так три механизма не теряют свои обязанности.
Ожидаемый маршрут
Читатель открывает публичный курс, затем выбирает «Войти в кабинет». Сервер создаёт challenge. Кошелёк подписывает его по согласованию. Сервер проверяет и атомарно использует challenge, после чего создаёт сессию. При отказе или неверной подписи кабинет не создаётся, а статья остаётся доступной.
Для упражнения сравните HTTP-запрос с произвольным адресом и запрос с правильной подписью старого challenge. Первый не доказывает контроль, второй не должен создавать новую сессию после использования одноразового значения. Ожидаемый итог — вы можете объяснить, зачем нужны все три элемента, а не только подпись. Формат входа описан в ERC-4361, контрактная проверка — в ERC-1271.
Несколько идентификаторов читателя
Адрес может быть одним из способов входа, но не обязан становиться единственным идентификатором человека в базе. Если сайт допускает привязку нескольких аккаунтов, эта операция требует отдельного подтверждения и политики объединения данных. Две правильные подписи разных адресов не доказывают автоматически, что прогресс должен принадлежать одному читателю. Такое объединение важно спроектировать до добавления удобной кнопки смены кошелька.