Broken Authentication in Feathersjs with Dynamodb
Broken Authentication in Feathersjs with Dynamodb — how this specific combination creates or exposes the vulnerability
FeathersJS is a framework for real-time applications that often uses authentication hooks to manage identity and tokens. When integrating with Amazon DynamoDB as the user store, misconfigurations at the intersection of FeathersJS authentication logic and DynamoDB access patterns can lead to Broken Authentication. This occurs when identity verification, session handling, or credential validation rely on DynamoDB queries that are incomplete, improperly scoped, or bypassed due to missing authorization checks.
One common pattern is storing user records in a DynamoDB table keyed by user ID, with the partition key set to a user identifier (e.g., user_id). If FeathersJS authentication hooks do not enforce consistent ownership checks—relying only on the presence of a JWT or session cookie—an attacker can manipulate identifiers to access other users' records. For example, an authenticated request may include a user ID in the URL or query parameters, and if the FeathersJS service does not validate that the authenticated subject matches the requested ID, the DynamoDB query may return data for the wrong user, resulting in Insecure Direct Object Reference (IDOR), a subset of Broken Authentication.
Additionally, Weak Password Storage practices can amplify risk. If user passwords are stored in DynamoDB without strong adaptive hashing (e.g., using bcrypt or argon2), credential leakage or offline brute-force becomes feasible. FeathersJS authentication plugins may accept credentials and query DynamoDB with a simple email = :email filter, but if the response includes the password hash and the application layer does not properly compare hashes before issuing tokens, an attacker who can observe or intercept authentication responses may infer valid credentials.
DynamoDB-specific access patterns also contribute. Because DynamoDB does not enforce referential integrity, orphaned or stale tokens may remain in tables if revocation logic is not implemented. FeathersJS may maintain session or token records in DynamoDB; if these are not invalidated on logout or password change, an attacker can reuse them. Furthermore, unencrypted data at rest in DynamoDB combined with overly permissive IAM policies can allow unauthorized access to user credential material, enabling authentication bypass through credential replay.
Real-world attack patterns mirror OWASP API Top 10 #2 Broken Authentication. For instance, an attacker might probe endpoints with manipulated user IDs to enumerate valid accounts (IDOR), or attempt to exploit missing rate limiting on authentication endpoints to conduct credential stuffing. These issues are detectable through spec-aware scanning that cross-references OpenAPI definitions of authentication paths with runtime behavior, highlighting where DynamoDB queries do not align with expected subject ownership.
Dynamodb-Specific Remediation in Feathersjs — concrete code fixes
Remediation centers on ensuring that every DynamoDB data access is bound to the authenticated subject and that credential handling follows security best practices. Below are concrete code examples for a FeathersJS service using the AWS SDK for JavaScript v3.
1. Secure user lookup by authenticated subject
Always include the authenticated user ID in the DynamoDB key condition, rather than trusting request-supplied identifiers.
import { DynamoDBClient, GetCommand } from "@aws-sdk/client-dynamodb";
import { unmarshall } from "@aws-sdk/util-dynamodb";
const client = new DynamoDBClient({ region: "us-east-1" });
async function getUserByIdentity(userId) {
const command = new GetCommand({
TableName: process.env.USERS_TABLE,
Key: {
user_id: { S: userId } // enforce partition key matches authenticated subject
}
});
const response = await client.send(command);
return response.Item ? unmarshall(response.Item) : null;
}
In a FeathersJS hook, bind params.user._id (set by authentication) to the query key, and reject requests where the provided ID does not match.
2. Parameterized queries to prevent injection and ensure scoping
Use DynamoDB condition expressions to enforce ownership within a single request, avoiding ambiguous scans.
import { DynamoDBClient, QueryCommand } from "@aws-sdk/client-dynamodb";
import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
async function findUserSessions(userId) {
const command = new QueryCommand({
TableName: process.env.SESSIONS_TABLE,
KeyConditionExpression: "user_id = :uid",
ExpressionAttributeValues: marshall({ ":uid": userId })
});
const response = await client.send(command);
return response.Items.map(unmarshall);
}
Never construct DynamoDB expressions by concatenating user input. Always use expression attribute values to prevent injection and ensure the partition key is constrained to the authenticated user.
3. Strong password storage and verification
Store passwords using a dedicated hashing library and verify within the authentication hook before issuing tokens.
import bcrypt from "bcryptjs";
async function verifyPassword(inputPassword, storedHash) {
return await bcrypt.compare(inputPassword, storedHash);
}
// In a custom authentication hook
async function localAuthenticate(email, password) {
const user = await getUserByEmail(email); // uses secure DynamoDB query
if (!user) throw new Error("Invalid credentials");
const valid = await verifyPassword(password, user.password_hash);
if (!valid) throw new Error("Invalid credentials");
return { ...user, password_hash: undefined };
}
Ensure the DynamoDB table stores only the hash and never the plaintext password. Use sufficient work factors (e.g., salt rounds ≥ 10).
4. Token and session management with DynamoDB
Maintain revocable sessions by storing session records with TTL and binding them to subject identifiers. On logout, delete the specific session item.
import { DynamoDBClient, PutCommand, DeleteCommand } from "@aws-sdk/client-dynamodb";
async function createSession(userId, sessionId, expiresAt) {
const command = new PutCommand({
TableName: process.env.SESSIONS_TABLE,
Item: {
session_id: { S: sessionId },
user_id: { S: userId },
expires_at: { N: String(Math.floor(expiresAt.getTime() / 1000)) },
created_at: { N: String(Math.floor(Date.now() / 1000)) }
}
});
await client.send(command);
}
async function revokeSession(sessionId) {
const command = new DeleteCommand({
TableName: process.env.SESSIONS_TABLE,
Key: {
session_id: { S: sessionId }
}
});
await client.send(command);
}
Configure DynamoDB Time to Live (TTL) on the session table to automatically purge expired sessions, reducing the window for token reuse.
5. Least-privilege IAM and encryption
Ensure the DynamoDB credentials used by FeathersJS have least privilege: only allow dynamodb:GetItem, dynamodb:Query, and dynamodb:PutItem on specific tables. Enable encryption at rest using AWS KMS and enforce TLS for all service communications.
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 |