Broken Authentication in Koa with Firestore
Broken Authentication in Koa with Firestore — how this specific combination creates or exposes the vulnerability
Broken Authentication occurs when identity management functions are implemented incorrectly, allowing attackers to compromise passwords, session tokens, or other credentials. In a Koa application using Google Firestore as the user store, the risk emerges from mismatches between application logic and Firestore security rules, as well as insecure session handling patterns common in JavaScript runtimes.
Koa is a minimalist web framework that does not include authentication primitives by default. Developers often introduce session-based or token-based flows by combining Koa middleware with Firestore documents that store user credentials or session records. If password hashing is omitted, sessions are not invalidated securely, or rules allow unauthenticated reads of user collections, the attack surface expands.
For example, storing passwords in Firestore without strong adaptive hashing (such as bcrypt) makes offline cracking feasible if Firestore rules are misconfigured or leaked. A rule like allow read, write: if request.auth != null; may appear protective, but if the application exposes a public endpoint that enumerates users via an unauthenticated route, attackers can harvest valid usernames or emails to aid credential stuffing or social engineering.
Another pattern specific to Koa is the use of ctx.session via koa-session or similar middleware. If session identifiers are predictable, stored client-side without integrity protection, or not bound to IP/user-agent context, an attacker who obtains a session ID can hijack authenticated sessions. Firestore-backed sessions must ensure that session documents are not readable or writable by other users and that session expiration is enforced both server-side and in Firestore TTL policies.
Additionally, over-permissive Firestore rules that allow writes to user documents from unauthenticated contexts can enable attackers to modify authentication-linked fields, such as email or password reset tokens. In Koa, route handlers that trust client-supplied identifiers (e.g., userId in path parameters) without verifying ownership can lead to Broken Object Level Authorization (BOLA), which is closely related to Broken Authentication because it enables horizontal privilege escalation across user accounts.
Real-world attack patterns include credential enumeration via timing differences in Koa route responses, brute-force attacks on weak passwords stored in Firestore, and token replay when session invalidation is incomplete. These issues are compounded when Firestore security rules are not aligned with the principle of least privilege, allowing broader access than intended. Proper authentication in the Koa–Firestore stack requires strong password hashing, tightly scoped rules, secure session storage, and explicit ownership checks on every data access.
Firestore-Specific Remediation in Koa — concrete code fixes
To remediate Broken Authentication in Koa with Firestore, adopt defense-in-depth measures: enforce strong password hashing, apply least-privilege Firestore rules, validate ownership on every request, and manage sessions securely. The following examples illustrate concrete implementations aligned with these practices.
First, use bcrypt to hash passwords before writing them to Firestore. Never store plaintext or weakly hashed credentials.
const koa = require('koa');
const router = require('koa-router')();
const bcrypt = require('bcrypt');
const { initializeApp } = require('firebase-admin');
const { getFirestore } = require('firebase-admin/firestore');
initializeApp();
const db = getFirestore();
router.post('/register', async (ctx) => {
const { email, password } = ctx.request.body;
const hashedPassword = await bcrypt.hash(password, 10);
const userRef = db.collection('users').doc(email);
await userRef.set({
email,
passwordHash: hashedPassword,
createdAt: new Date(),
});
ctx.status = 201;
ctx.body = { message: 'User created' };
});
Second, tighten Firestore security rules to enforce authentication and ownership. Rules should prevent unauthenticated access and ensure users can only modify their own documents.
For Firestore rules (version 2), a secure baseline looks like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
match /sessions/{sessionId} {
allow create: if request.auth != null;
allow read, update, delete: if request.auth != null && request.auth.uid == request.resource.data.userId;
}
}
}
Third, in Koa route handlers, always verify that the requesting user matches the resource they are trying to access. Avoid trusting path parameters alone.
router.get('/profile/:userId', async (ctx) => {
const requestingUserId = ctx.state.user?.uid; // from verified auth middleware
const targetUserId = ctx.params.userId;
if (!requestingUserId || requestingUserId !== targetUserId) {
ctx.throw(403, 'Unauthorized');
}
const userDoc = await db.collection('users').doc(targetUserId).get();
if (!userDoc.exists) {
ctx.throw(404, 'User not found');
}
ctx.body = userDoc.data();
});
Finally, manage sessions with server-side storage in Firestore and short lifetimes. Store session metadata server-side and bind sessions to fingerprints where possible.
const uuid = require('uuid');
router.post('/login', async (ctx) => {
const { email, password } = ctx.request.body;
const userSnapshot = await db.collection('users').where('email', '==', email).limit(1).get();
if (userSnapshot.empty) {
ctx.throw(401, 'Invalid credentials');
}
const user = userSnapshot.docs[0].data();
const passwordMatch = await bcrypt.compare(password, user.passwordHash);
if (!passwordMatch) {
ctx.throw(401, 'Invalid credentials');
}
const sessionId = uuid.v4();
const sessionRef = db.collection('sessions').doc(sessionId);
await sessionRef.set({
userId: userSnapshot.id,
createdAt: new Date(),
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
});
ctx.cookies.set('sessionId', sessionId, { httpOnly: true, secure: true, path: '/' });
ctx.body = { message: 'Logged in' };
});
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |