You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何为基于Firebase Authentication的网站配置简易易维护的暴力破解防护机制?

Simple Brute-Force Protection for Firebase Auth (No 2FA)

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

  1. 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).
  2. Reset on success: Clear the failed count whenever a user logs in successfully.
  3. 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);
  }
}

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

火山引擎 最新活动