如何为基于Firebase Authentication的网站配置简易易维护的暴力破解防护机制?
Got it, let’s build a straightforward, low-maintenance solution that fits your needs—no 2FA required, and it leverages Firebase’s built-in tools so you don’t have to reinvent the wheel. The core idea is tracking failed login attempts per user and triggering an email verification check once a threshold (like 3 failures) is hit.
How It Works
- Track attempts securely: Use Firestore to store failed login counts tied to each user’s UID (we’ll use Cloud Functions to handle this so users can’t manipulate counts from the frontend).
- Reset on success: Clear the failed count whenever a user logs in successfully.
- Lock and verify: When the failure threshold is reached, block further logins until the user verifies their email (even if they’ve verified it before—this adds a quick, user-friendly barrier against brute-force attacks).
Step 1: Set Up Firestore Collection
First, create a Firestore collection named loginAttempts. Each document will use the user’s Firebase UID as its ID, with these fields:
failedCount: Number (starts at 0)lastFailedAt: Timestamp (optional, for adding cooldown periods later)
Step 2: Deploy Cloud Functions for Login Logic
We’ll use a callable Cloud Function to handle login attempts and track failures securely. This way, all counting logic runs on Firebase’s servers, not the user’s device.
Here’s the code for your functions/index.js file:
const functions = require("firebase-functions"); const admin = require("firebase-admin"); admin.initializeApp(); const db = admin.firestore(); const MAX_FAILED_ATTEMPTS = 3; // Adjust this number as needed exports.loginWithBruteForceProtection = functions.https.onCall(async (data, context) => { const { email, password } = data; if (!email || !password) { throw new functions.https.HttpsError("invalid-argument", "Email and password are required"); } try { // Attempt to sign in the user const userCredential = await admin.auth().signInWithEmailAndPassword(email, password); const user = userCredential.user; // Reset failed attempts on successful login const attemptsRef = db.collection("loginAttempts").doc(user.uid); await attemptsRef.set({ failedCount: 0, lastFailedAt: null }, { merge: true }); return { success: true, message: "Login successful" }; } catch (error) { // Handle login failures (wrong password or user not found) if (error.code === "auth/user-not-found" || error.code === "auth/wrong-password") { // Get the user's UID if they exist (skip tracking for non-existent users) let userId; try { const userRecord = await admin.auth().getUserByEmail(email); userId = userRecord.uid; } catch (userError) { throw new functions.https.HttpsError("invalid-argument", "Invalid email or password"); } // Update the failed attempt count const attemptsRef = db.collection("loginAttempts").doc(userId); const docSnapshot = await attemptsRef.get(); const failedCount = docSnapshot.exists ? docSnapshot.data().failedCount + 1 : 1; await attemptsRef.set({ failedCount: failedCount, lastFailedAt: admin.firestore.FieldValue.serverTimestamp() }, { merge: true }); // Trigger email verification if threshold is hit if (failedCount >= MAX_FAILED_ATTEMPTS) { // Revoke existing sessions and mark email as unverified await admin.auth().updateUser(userId, { emailVerified: false }); await admin.auth().revokeRefreshTokens(userId); // Send verification email to the user const verificationLink = await admin.auth().generateEmailVerificationLink(email); await admin.auth().sendEmailVerification(admin.auth().getUser(userId), { url: verificationLink }); throw new functions.https.HttpsError("permission-denied", "Too many failed attempts. Please verify your email before trying again."); } else { const remainingAttempts = MAX_FAILED_ATTEMPTS - failedCount; throw new functions.https.HttpsError("invalid-argument", `Invalid email or password. ${remainingAttempts} attempt(s) remaining.`); } } else { // Handle other authentication errors throw new functions.https.HttpsError("unknown", error.message); } } });
Deploy this function by running firebase deploy --only functions in your terminal.
Step 3: Update Your Frontend to Use the Callable Function
Instead of using Firebase’s native signInWithEmailAndPassword directly, call your new cloud function from your frontend code. Here’s an example in JavaScript:
import { getFunctions, httpsCallable } from "firebase/functions"; const functions = getFunctions(); const loginFunction = httpsCallable(functions, "loginWithBruteForceProtection"); // Attach this to your login form submit handler async function handleLogin(email, password) { try { const result = await loginFunction({ email, password }); console.log(result.data.message); // Redirect to your app's dashboard or handle successful login } catch (error) { // Display error messages to the user (e.g., in a toast or alert) alert(error.message); } }
Step 4: Add Firestore Security Rules (Optional but Recommended)
Prevent unauthorized changes to the loginAttempts collection by adding these rules to your Firestore rules file:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /loginAttempts/{userId} { // Only allow Cloud Functions to write to this collection allow write: if request.auth == null; // Allow users to read their own attempt count (optional) allow read: if request.auth != null && request.auth.uid == userId; } } }
Bonus: Add a Cooldown Period (Optional)
If you want to add an extra layer of protection, modify the cloud function to block login attempts for a set time (e.g., 15 minutes) after the threshold is hit. Just add a check for lastFailedAt before allowing another login attempt.
This solution is easy to maintain because it uses Firebase’s native tools, requires minimal custom code, and avoids forcing 2FA on non-technical users. The email verification step is familiar to most users and effectively stops brute-force attacks.
内容的提问来源于stack exchange,提问作者Graunephar




