Credential Stuffing in Express with Cockroachdb
Credential Stuffing in Express with Cockroachdb — how this specific combination creates or exposes the vulnerability
Credential stuffing is an automated attack where lists of breached username and password pairs are used to sign in to accounts. When Express applications use CockroachDB as the user store without defenses targeted at this threat, the combination can expose or enable abuse of the authentication flow. Key factors include:
- Express typically relies on developer‑chosen controls such as rate limiting, account lockout, and captcha. If these are missing or misconfigured, attackers can submit many credentials against the same endpoint or a small set of endpoints.
- CockroachDB, while strongly consistent and resilient, does not inherently prevent high‑rate queries from a single connection or client IP. Without server‑side throttling or per‑user limits, an attacker can drive many login attempts quickly.
- The authentication route often performs a lookup like
SELECT * FROM users WHERE email = $1. Without protections, each request results in a round‑trip to CockroachDB, making the database a visible component of the attack surface and potentially enabling enumeration via timing differences or error messages. - If login responses reveal whether an account exists (e.g., different messages for unknown user vs. wrong password), attackers can iterate usernames while confirming valid accounts. Combined with high request volume, this turns CockroachDB into an efficient backend for credential validation.
- Session management issues (e.g., missing secure, HttpOnly cookies; lack of SameSite; predictable session tokens) can compound risk by allowing attackers to reuse compromised sessions after credential stuffing succeeds.
An attacker using this technique against an Express + CockroachDB service may execute scripts that cycle through credential lists, probe for weak passwords, and exploit missing protections. Because the stack does not enforce per‑user or per‑IP rate limits at the database or application layer, the workload translates into many queries in CockroachDB, increasing the likelihood of successful unauthorized access.
Cockroachdb-Specific Remediation in Express — concrete code fixes
Defend Express apps backed by CockroachDB with layered controls focused on authentication. Below are concrete, realistic code examples that reduce the effectiveness of credential stuffing.
1. Parameterized queries with the CockroachDB Node.js driver
Always use placeholders to avoid injection and ensure stable query plans. This does not stop credential stuffing directly, but it keeps the authentication path reliable and inspectable.
const { Client } = require('pg'); // CockroachDB speaks PostgreSQL wire protocol
const client = new Client({
connectionString: process.env.DATABASE_URL,
});
await client.connect();
async function findUserByEmail(email) {
const res = await client.query('SELECT id, password_hash, mfa_enabled FROM users WHERE email = $1', [email]);
return res.rows[0] || null;
}
2. Rate limiting at the API gateway and within Express
Apply throttling close to the attacker (edge or load balancer) and also in Express to protect CockroachDB from excessive queries. Example using express-rate-limit and a shared store to coordinate across instances:
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const limiter = rateLimit({
store: new RedisStore({ client: redisClient }),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // total requests per window
message: { error: 'Too many requests from this IP, please try again later.' },
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/auth/login', limiter);
3. Per‑user and sliding‑window rate limits in CockroachDB
Track attempts per account with a resilient table and enforce rejections at the application level. CockroachDB’s transactional guarantees ensure accurate counting even under high concurrency.
CREATE TABLE login_attempts (
user_id UUID REFERENCES users(id),
attempt_at TIMESTAMPTZ DEFAULT now(),
ip INET
);
-- After a failed login, insert attempt within a transaction
BEGIN;
INSERT INTO login_attempts (user_id, ip) VALUES ($1, $2);
-- Count attempts in the last 15 minutes for this user
SELECT COUNT(*) FROM login_attempts WHERE user_id = $1 AND attempt_at > now() - INTERVAL '15 minutes';
-- Optionally, raise an error or delay response if threshold exceeded
COMMIT;
4. Uniform error messages and gradual delays
Return the same generic message for unknown users and failed password checks to prevent enumeration. Add incremental delays on repeated failures to increase attacker cost without affecting legitimate users significantly.
async function onLogin(email, password, ip) {
const user = await findUserByEmail(email);
if (!user) {
// Generic response to avoid user enumeration
await delay(300);
return { error: 'Invalid credentials' };
}
const isMatch = await verifyPassword(password, user.password_hash);
if (!isMatch) {
// Incrementally slow down to hinder rapid attempts
const recent = await countRecentAttempts(user.id, ip);
const delayMs = Math.min(300 + recent * 150, 2000);
await delay(delayMs);
return { error: 'Invalid credentials' };
}
// issue session token, etc.
}
5. Multi‑factor authentication (MFA) and suspicious‑activity detection
Require MFA for risk‑based contexts (new device/IP) and store signals that help downstream systems identify bursts originating from CockroachDB records. This shifts part of the burden to the application logic while keeping the database as the source of truth.
Together, these measures reduce the throughput that credential stuffing can achieve against the Express + CockroachDB stack, protect the integrity of user accounts, and keep the authentication flow resilient without changing the core database capabilities.