Credential Stuffing in Feathersjs with Firestore
Credential Stuffing in Feathersjs with Firestore — how this specific combination creates or exposes the vulnerability
Credential stuffing is an automated attack where attackers use lists of breached username and password pairs to gain unauthorized access. When using Feathersjs with Firestore, the risk arises from application-level authentication logic rather than Firestore itself, but the framework and database choices can inadvertently enable or amplify the impact.
Feathersjs is a flexible framework that does not enforce a specific authentication strategy. If you implement session-based or JWT authentication without additional protections, and rely on Firestore as the user data store, the service becomes a target for credential stuffing. Firestore does not inherently prevent rapid, high-volume requests to a login endpoint, so the application must enforce rate limits and other mitigations.
A typical vulnerable Feathersjs service may expose an endpoint like /authentication that accepts email and password. If the endpoint performs a Firestore query for each request without throttling, an attacker can automate sign-in attempts at scale. Even if individual requests return generic error messages, attackers can infer whether an email exists based on timing differences or response structure, especially if Firestore document reads are observable via logs or monitoring.
Additionally, if the Feathersjs application uses predictable or weakly enforced identity fields (for example, using email as the document ID in a users collection), it simplifies automation. Attackers can iterate through known email patterns and attempt authentication. Firestore security rules that do not restrict read or list operations on the users collection can also inadvertently expose metadata that aids enumeration.
Because Feathersjs supports multiple authentication strategies, developers might inadvertently enable an unsafe combination, such as JWT with a weak secret or session cookies without secure flags, increasing the attack surface. Without multi-factor authentication, account lockout, or CAPTCHA, the service remains vulnerable to sustained credential stuffing campaigns.
middleBrick detects these risks by analyzing unauthenticated attack surfaces, including authentication endpoints and Firestore interaction patterns, and flags issues such as missing rate limiting, weak authentication configurations, and excessive account enumeration. The scanner maps findings to frameworks like OWASP API Top 10 to prioritize remediation.
Firestore-Specific Remediation in Feathersjs — concrete code fixes
To harden Feathersjs applications using Firestore, implement defense-in-depth measures focused on authentication endpoints, data access patterns, and operational security.
- Enforce rate limiting at the Feathersjs service layer to restrict login attempts per IP or identity. Use a sliding window algorithm to prevent burst attacks.
- Avoid exposing whether an email exists during authentication. Return uniform responses and use constant-time comparison where applicable.
- Secure Firestore rules to prevent unauthorized enumeration. Ensure that user documents are not readable via broad queries and that rules enforce ownership checks.
- Use secure password hashing (e.g., bcrypt) before any verification and never store or transmit plaintext credentials.
- Enable secure cookie attributes, use HTTPS, and rotate secrets regularly.
Example of a secured Feathersjs authentication service with Firestore integration:
const { AuthenticationService } = require('@feathersjs/authentication');
const { expressify } = require('@feathersjs/express');
const bcrypt = require('bcrypt');
const { getFirestore } = require('firebase-admin/firestore');
const db = getFirestore();
class CustomAuthenticationService extends AuthenticationService {
async create(data, params) {
const { email, password } = data;
// Enforce strong password policy
if (password.length < 12) {
throw new Error('Password does not meet policy requirements');
}
const hashedPassword = await bcrypt.hash(password, 12);
const userRef = db.collection('users').doc(email);
await userRef.set({
email,
password: hashedPassword,
emailVerified: false,
createdAt: new Date()
});
// Do not reveal existence via timing or response differences
return { email, strategy: 'local' };
}
async authenticate(authentication, params) {
const { email, password } = authentication;
const userRef = db.collection('users').doc(email);
const doc = await userRef.get();
if (!doc.exists) {
// Always perform a hash comparison to prevent timing attacks
await bcrypt.hash(password, 12);
throw new Error('Bad credentials');
}
const userData = doc.data();
const match = await bcrypt.compare(password, userData.password);
if (!match) {
throw new Error('Bad credentials');
}
return { email, strategy: 'local' };
}
}
module.exports = function (app) {
const authentication = new CustomAuthenticationService({
name: 'authentication',
entity: 'authentication',
paginate: app.get('paginate'),
firestore: db
});
app.use('/authentication', expressify(authentication));
// Rate limiting applied externally or via middleware
app.use('/authentication', (req, res, next) => {
// Implement rate limiting logic here
next();
});
};
Example Firestore security rules to prevent enumeration and unauthorized access:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.token.email == userId;
allow list: if false;
}
// Deny public enumeration of users
match /users/{any=**} {
allow read: if false;
}
}
}
These configurations ensure that authentication requests are rate-limited, responses do not leak account existence, and Firestore rules restrict unauthorized access and enumeration. Regularly review logs and rotate credentials to maintain a strong security posture.