Спроектируй взаимодействие с API при двухфакторной аутентификации
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектура двухфакторной аутентификации
Двухфакторная аутентификация (2FA) требует правильного управления состоянием между этапами верификации. Вот как спроектировать это взаимодействие:
Типы 2FA методов
В современных приложениях используются несколько подходов:
- SMS-коды (отправка кода на номер)
- TOTP (Time-based One-Time Password, как Google Authenticator)
- Email подтверждение
- Биометрия (Face ID, Touch ID)
API endpoints для 2FA
Нужны отдельные endpoints для каждого этапа:
// 1. Инициализация 2FA после первичной аутентификации
POST /api/v1/auth/verify-2fa/start
Body: { userId, method: "sms" | "totp" | "email" }
Response: { sessionId, expiresIn, retriesLeft }
// 2. Отправка/получение кода подтверждения
POST /api/v1/auth/verify-2fa/send
Body: { sessionId }
Response: { sent: true, expiresIn }
// 3. Проверка кода подтверждения
POST /api/v1/auth/verify-2fa/verify
Body: { sessionId, code }
Response: { token, refreshToken, user }
// 4. Запасные коды (backup codes) для восстановления
GET /api/v1/auth/backup-codes
POST /api/v1/auth/backup-codes/use
Управление состоянием на фронтенде
Используй состояние для отслеживания этапа аутентификации:
const [authState, setAuthState] = useState({
stage: "login", // login -> 2fa -> authenticated
sessionId: null,
method: null,
expiresAt: null,
attemptsLeft: 3,
error: null,
});
Безопасность и обработка ошибок
Важные моменты:
- Session timeout: код верификации должен иметь короткий TTL (60-300 сек)
- Rate limiting: ограничить попытки (макс 3-5 попыток)
- Резервные коды: всегда предоставляй способ восстановления
- Нет хранения кодов: никогда не храни коды подтверждения в localStorage
// Обработка ошибок при верификации
const verify2FA = async (code) => {
try {
const response = await api.post("/auth/verify-2fa/verify", {
sessionId: authState.sessionId,
code: code.trim(),
});
setAuthState(prev => ({
...prev,
stage: "authenticated",
}));
// Сохраняй токен в httpOnly cookie (самый безопасный способ)
localStorage.setItem("userId", response.user.id);
} catch (error) {
if (error.response?.status === 429) {
setAuthState(prev => ({
...prev,
error: "Слишком много попыток. Попробуй позже.",
}));
} else if (error.response?.data?.message === "Invalid code") {
setAuthState(prev => ({
...prev,
attemptsLeft: error.response.data.attemptsLeft,
error: `Неверный код. Осталось ${error.response.data.attemptsLeft} попыток`,
}));
}
}
};
UX компонент для ввода кода
Лучшая практика - использовать отдельные поля для каждой цифры:
export function TwoFactorVerification() {
const [codes, setCodes] = useState(["", "", "", "", "", ""]);
const handleCodeChange = (index, value) => {
const newCodes = [...codes];
newCodes[index] = value.slice(0, 1); // только одна цифра
setCodes(newCodes);
// Автофокус на следующее поле
if (value && index < 5) {
document.getElementById(`code-${index + 1}`).focus();
}
};
const code = codes.join("");
return (
<div className="flex gap-2">
{codes.map((_, i) => (
<input
key={i}
id={`code-${i}`}
type="text"
inputMode="numeric"
maxLength="1"
value={codes[i]}
onChange={(e) => handleCodeChange(i, e.target.value)}
className="w-12 h-12 text-center text-2xl font-bold border rounded"
/>
))}
</div>
);
}
Обработка истечения сессии
Если сессия истекла, нужно позволить пользователю начать заново:
const checkSessionExpiry = () => {
if (authState.expiresAt && Date.now() > authState.expiresAt) {
setAuthState(prev => ({
...prev,
error: "Сессия истекла. Пожалуйста, начните заново.",
sessionId: null,
}));
}
};
Хранение tokens безопасно
Лучший подход для веб-приложения:
- JWT token -> httpOnly, secure, samesite=strict cookie
- Refresh token -> httpOnly cookie (более длительный TTL)
- localStorage -> только некритичные данные (userId, theme)
Это защищает от XSS атак, так как JavaScript не может получить доступ к httpOnly cookies.
Итого: 2FA требует тщательного управления сессией, безопасного хранения токенов, хорошей UX при вводе кодов и механизма восстановления доступа.