Аутентификация в Эфириуме
Если вы пришли из традиционной веб-разработки, вы привыкли к входу по имени пользователя и паролю, потокам OAuth и сессионным файлам cookie. Аутентификация в Эфириуме работает иначе — и во многом проще.
В Эфириуме пользователь подтверждает свою личность путем подписания сообщения с помощью своего кошелька. Не нужно хранить пароли. Нет базы данных учетных данных, которая может утечь. Только криптография.
Чем это отличается от Веб2?
| Веб2 | Эфириум |
|---|---|
| Имя пользователя + пароль | Адрес кошелька + подпись |
| Сервер хранит учетные данные | Пользователь владеет приватным ключом |
| Сессии управляются через cookies / JWT | Сессии начинаются с офчейн-подписи кошелька |
| «Войти через Google» | «Войти через Эфириум» |
| Процедуры сброса пароля | Восстановление через сид-фразу |
Фундаментальный сдвиг: в Веб2 вас аутентифицирует централизованный сервер. В Эфириуме вы аутентифицируете себя сами, доказывая, что контролируете определенный адрес — и любой может проверить это независимо.
Предварительные требования
Убедитесь, что вы понимаете:
- Аккаунты Эфириума и как они работают
- Что такое кошелек и как его подключить
- Основы криптографии с открытым и приватным ключами
Как работает аутентификация на основе кошелька
Основной процесс прост:
- Ваше децентрализованное приложение (dapp) просит пользователя подключить свой кошелек (через МетаМаск, Rainbow, WalletConnect и т. д.)
- Кошелек передает адрес Эфириума пользователя — это его публичный идентификатор
- Ваше dapp генерирует уникальное сообщение (нонс или запрос)
- Пользователь подписывает сообщение своим приватным ключом (это происходит внутри кошелька)
- Ваш бэкенд проверяет подпись на соответствие заявленному адресу
- Если подпись действительна, пользователь аутентифицирован
Никакой пароль не вводился, не сохранялся и не передавался.
Вход через Эфириум (EIP-4361)
EIP-4361 (открывается в новой вкладке) определяет стандартный формат сообщения для входа через Эфириум, обычно называемый SIWE (Sign-In with Ethereum). Он заменяет произвольное подписание сообщений структурированным и безопасным стандартом.
Сообщение SIWE выглядит так:
example.com wants you to sign in with your Ethereum account:
0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B
I accept the Terms of Service: https://example.com/tos
URI: https://example.com/login
Version: 1
Chain ID: 1
Nonce: 32891757
Issued At: 2024-06-12T14:30:00Z
Ключевые особенности SIWE:
- Привязка к домену — сообщение включает домен, что предотвращает фишинг
- ID цепи — указывает, для какой сети действительна подпись
- Нонс — предотвращает атаки повторного воспроизведения
- Срок действия — необязательная временная метка, ограничивающая окно действия
- Ресурсы — необязательные URI для ограниченного доступа
Библиотеки SIWE
- siwe (открывается в новой вкладке) — официальная реализация на TypeScript от Spruce
- siwe-rs (открывается в новой вкладке) — реализация на Rust
- siwe-go (открывается в новой вкладке) — реализация на Go
Пример: вход на стороне клиента с помощью siwe
import { SiweMessage } from 'siwe'
import { BrowserProvider } from 'ethers'
async function signIn() {
const provider = new BrowserProvider(window.ethereum)
const signer = await provider.getSigner()
const address = await signer.getAddress()
// 1. Получите нонс от вашего бэкенда
const { nonce } = await fetch('/api/auth/nonce').then(r => r.json())
// 2. Создайте и подпишите сообщение SIWE
const message = new SiweMessage({
domain: window.location.host,
address,
statement: 'Sign in to My Dapp',
uri: window.location.origin,
version: '1',
chainId: 1,
nonce,
})
const signature = await signer.signMessage(message.prepareMessage())
// 3. Отправьте на бэкенд для проверки
await fetch('/api/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, signature }),
})
}
Пример: проверка на стороне сервера (Node.js)
import { SiweMessage, generateNonce } from 'siwe'
// Выдайте нонс и сохраните его в сессии, чтобы /verify мог проверить его позже
app.get('/api/auth/nonce', (req, res) => {
req.session.nonce = generateNonce()
res.json({ nonce: req.session.nonce })
})
app.post('/api/auth/verify', async (req, res) => {
try {
const { message, signature } = req.body
const siweMessage = new SiweMessage(message)
const { success, data } = await siweMessage.verify({
signature,
nonce: req.session.nonce,
})
if (success) {
// data.address — это проверенный адрес Эфириума
// Создайте сессию или JWT для пользователя
req.session.address = data.address
res.json({ ok: true, address: data.address })
}
} catch {
res.status(401).json({ error: 'Invalid signature' })
}
})
Библиотеки для подключения кошелька
Перед аутентификацией необходимо, чтобы пользователь подключил свой кошелек. Эти библиотеки упрощают задачу:
- RainbowKit (открывается в новой вкладке) — готовый к использованию компонент React с красивым пользовательским интерфейсом
- ConnectKit (открывается в новой вкладке) — готовое модальное окно для подключения кошелька
- AppKit (WalletConnect) (открывается в новой вкладке) — мультичейн-подключение кошелька со встроенным SIWE
- Wagmi (открывается в новой вкладке) — библиотека хуков React с
useAccount,useConnect
Проверка подписей вручную
Если вы предпочитаете не использовать SIWE, вы можете проверять подписи напрямую:
import { verifyMessage } from 'ethers'
// Сообщение, которое подписал пользователь
const message = `Sign in to My Dapp. Nonce: ${storedNonce}`
// Восстановите адрес подписанта из подписи
const recoveredAddress = verifyMessage(message, signature)
// Сравните с заявленным адресом
if (recoveredAddress.toLowerCase() === claimedAddress.toLowerCase()) {
// Аутентификация прошла успешно
}
Важные замечания по безопасности
- Всегда используйте нонс — это предотвращает атаки повторного воспроизведения, при которых повторно используется старая подпись
- Включайте домен — это не позволяет подписям быть действительными на разных сайтах
- Проверяйте срок действия — подписи должны иметь ограниченное окно действия
- Используйте SIWE (EIP-4361), когда это возможно — он обрабатывает все вышеперечисленное за вас
- Никогда не раскрывайте приватные ключи — подписание происходит внутри кошелька; ваше приложение видит только результат
Управление сессиями
После аутентификации вам все еще нужны сессии — так же, как и в Веб2. Распространенные паттерны:
- Токены JWT — выдавайте JWT после проверки подписи, используйте для запросов к API
- Сессии на стороне сервера — сохраняйте проверенный адрес в сессионном файле cookie
- SIWE с ресурсами — определяйте токены доступа с ограниченной областью действия, привязанные к конкретным URI
Ключевое отличие от Веб2: адрес Эфириума пользователя является его постоянной идентичностью. Он может использовать его в любом dapp без создания нового аккаунта.
Децентрализованная идентичность
Аутентификация в Эфириуме является частью более широкого движения к суверенной идентичности (self-sovereign identity). Стандарты и проекты в этой области включают:
- Служба имен Эфириума (ENS) (открывается в новой вкладке) — удобочитаемые имена (например,
vitalik.eth), которые разрешаются в адреса - Служба аттестации Эфириума (EAS) (открывается в новой вкладке) — ончейн-аттестации о личности и учетных данных
- Децентрализованные идентификаторы W3C (DIDs) (открывается в новой вкладке) — глобальный стандарт для проверяемой децентрализованной идентичности
- Ceramic Network (открывается в новой вкладке) — децентрализованные потоки данных, привязанные к DID
Дополнительная литература
- EIP-4361: Вход через Эфириум (открывается в новой вкладке)
- Документация SIWE (открывается в новой вкладке)
- Вход через Эфириум на Auth0 (открывается в новой вкладке)
- Документация по аутентификации Reown AppKit (открывается в новой вкладке)
- Документация ENS (открывается в новой вкладке)