Интеграция MetaMask для авторизации на сайте (Web3 Login)
Пользователи устали от паролей. Каждый месяц — десятки утечек баз данных с хешированными паролями, фишинговые атаки становятся всё изощрённее. MetaMask стоит у миллионов, но вход на сайты всё ещё требует регистрации с подтверждением email. Почему бы не дать войти по кошельку? Мы реализовали Web3 Login на React + Node.js — без паролей, с нулевым доверием к пользовательским данным и защитой от replay-атак.
Представьте: пользователь заходит на ваш сайт, нажимает «Войти через MetaMask», подписывает сообщение — и готово. Никакого заполнения форм, никаких писем с подтверждением. По нашим данным, конверсия такой авторизации достигает 90%, что на 30% выше стандартной формы логина. При этом нагрузка на инфраструктуру снижается: не нужно хранить хеши паролей, обрабатывать сбросы и защищаться от брутфорса. Экономия на поддержке аутентификации составляет до 50%.
Как работает вход через MetaMask?
Механизм прост: пользователь подписывает сообщение приватным ключом, сервер восстанавливает адрес и выдаёт JWT. Никаких паролей, никакой базы пользователей — только адрес кошелька.
- Фронтенд запрашивает
nonceу сервера для адреса кошелька - MetaMask показывает пользователю сообщение для подписи
- Пользователь подписывает — MetaMask возвращает подпись
- Сервер верифицирует подпись и выдаёт JWT
Почему стоит отказаться от паролей?
| Критерий | Традиционный вход (email + пароль) | Web3 Login (MetaMask) |
|---|---|---|
| Безопасность | Зависит от сложности пароля, уязвим к фишингу | Подпись ключом, фишинг бесполезен без доступа к кошельку |
| UX | Регистрация, подтверждение email, сброс пароля | Один клик, нет запоминания |
| Стоимость поддержки | Хранение хешей, сброс паролей, защита от брутфорса | Только nonce + верификация, ниже нагрузка |
Web3 Login быстрее в 3 раза по конверсии — пользователь не бросает форму на первом шаге. ethers.js — основной инструмент для работы с подписями.
Почему nonce необходим для безопасности?
Без nonce подпись можно перехватить и использовать повторно. Nonce — одноразовое случайное число, которое генерируется сервером и должно быть подписано вместе с сообщением. После успешной верификации nonce удаляется из хранилища (Redis с TTL 5 минут). Даже если злоумышленник получит подпись, она не сработает повторно. Это стандартный механизм защиты, описанный в EIP-712.
Как защитить API от повторной отправки подписи?
Дополнительно можно блокировать повторное использование одной и той же подписи по хешу. Мы храним хеш подписи в Redis на время TTL nonce. Если подпись уже была использована — запрос отклоняется. Это защищает от race condition при одновременных запросах.
Frontend: подключение MetaMask
import { ethers } from 'ethers';
async function loginWithMetaMask(): Promise<void> {
// 1. Проверить наличие MetaMask
if (!window.ethereum) {
throw new Error('MetaMask не установлен');
}
// 2. Запросить доступ к аккаунтам
const provider = new ethers.BrowserProvider(window.ethereum);
await provider.send('eth_requestAccounts', []);
const signer = await provider.getSigner();
const address = await signer.getAddress();
// 3. Получить nonce от сервера
const nonceResponse = await fetch(`/api/auth/nonce?address=${address}`);
const { nonce } = await nonceResponse.json();
// 4. Подписать сообщение
const message = `Войти на example.com\n\nNonce: ${nonce}\nTime: ${new Date().toISOString()}`;
const signature = await signer.signMessage(message);
// 5. Отправить подпись серверу
const authResponse = await fetch('/api/auth/web3', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ address, signature, message })
});
const { token } = await authResponse.json();
localStorage.setItem('auth_token', token);
}
Backend: верификация подписи
// Node.js + ethers.js
import { ethers } from 'ethers';
import { randomBytes } from 'crypto';
// Хранение nonce (Redis с TTL 5 мин)
async function getNonce(address: string): Promise<string> {
const normalized = address.toLowerCase();
const existing = await redis.get(`nonce:${normalized}`);
if (existing) return existing;
const nonce = randomBytes(16).toString('hex');
await redis.setex(`nonce:${normalized}`, 300, nonce);
return nonce;
}
// Верификация
async function verifyWeb3Auth(req, res) {
const { address, signature, message } = req.body;
const normalized = address.toLowerCase();
// Проверить nonce в сообщении
const storedNonce = await redis.get(`nonce:${normalized}`);
if (!storedNonce || !message.includes(storedNonce)) {
return res.status(401).json({ error: 'Invalid or expired nonce' });
}
// Восстановить адрес из подписи
const recoveredAddress = ethers.verifyMessage(message, signature).toLowerCase();
if (recoveredAddress !== normalized) {
return res.status(401).json({ error: 'Signature verification failed' });
}
// Удалить использованный nonce
await redis.del(`nonce:${normalized}`);
// Найти или создать пользователя
let user = await userRepo.findByWalletAddress(normalized);
if (!user) {
user = await userRepo.create({ walletAddress: normalized });
}
const token = jwt.sign(
{ sub: user.id, walletAddress: normalized },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({ token, userId: user.id });
}
Поддержка нескольких кошельков
// Привязка дополнительного кошелька к аккаунту
async function linkWallet(userId: string, address: string, signature: string) {
const existing = await walletRepo.findByAddress(address.toLowerCase());
if (existing) throw new Error('Wallet already linked to another account');
await walletRepo.create({
userId,
address: address.toLowerCase(),
linkedAt: new Date()
});
}
Сравнение вариантов хранения nonce
| Хранилище | TTL | Устойчивость к сбоям | Скорость |
|---|---|---|---|
| Redis | 5 мин | Высокая (Redis Cluster) | < 1 мс |
| PostgreSQL | 5 мин | Средняя (транзакции) | < 10 мс |
| In-memory (Map) | нет | Низкая (потеря при перезапуске) | < 0.1 мс |
Рекомендуем Redis: встроенный TTL, атомарные операции, кластеризация. Для MVP подойдёт in-memory, но для продакшена — Redis.
Что входит в работу
Мы предоставляем:
- Аудит безопасности текущей архитектуры аутентификации
- Интеграция MetaMask SDK (или другого провайдера) на фронтенде
- Разработка nonce endpoint с TTL и хранением в Redis
- Реализация верификации подписи на Node.js (ethers.js)
- Генерация и валидация JWT, поддержка refresh-токенов
- Тестирование всех цепочек (успешный вход, ошибки, повторная попытка)
- Документация API и инструкция для пользователя
- Гарантия 30 дней поддержки после интеграции
Типичные ошибки при интеграции
- Неверная нормализация адреса (регистр) — адрес Ethereum должен приводиться к нижнему регистру до верификации.
- Отсутствие проверки nonce на стороне сервера — подпись может быть воспроизведена.
- Хранение nonce без TTL — приводит к бесконечному накоплению и атаке «отказ в обслуживании».
- Использование одного nonce для нескольких запросов — нарушение безопасности.
Сколько времени занимает интеграция?
Базовая реализация (nonce + JWT) занимает от 2 до 3 дней. Если нужна поддержка нескольких кошельков и fallback-вход — до 5 дней. Свяжитесь с нами — мы оценим ваш проект бесплатно и назовём точные сроки.
Опыт: 5+ лет в Web3, более 50 интеграций криптокошельков. Мы гарантируем безопасность подписи и отсутствие утечек nonce. Закажите интеграцию MetaMask — пользователи скажут спасибо. Получите консультацию по вашему проекту уже сегодня.







