Broken Authentication in Hapi with Dynamodb
Broken Authentication in Hapi with DynamoDB — how this specific combination creates or exposes the vulnerability
Broken Authentication in a Hapi application that uses DynamoDB often arises from how credentials and session tokens are handled before and after DynamoDB lookups. Hapi’s authentication schemes rely on validating credentials (for example, an API key or a session identifier) and then attaching an authenticated entity to the request. If the validation step does not correctly verify the credential or if the credential material is stored or compared insecurely in DynamoDB, the authentication boundary can be bypassed.
DynamoDB itself does not perform authentication; it is a storage layer. Therefore, the vulnerability typically resides in the Hapi route logic that reads from DynamoDB and makes authorization decisions. Common patterns that lead to broken authentication include:
- Lookup by a user-supplied identifier (e.g., a username or a user ID) without verifying a corresponding secret, such as a password hash or an HMAC key stored in DynamoDB.
- Caching or reusing authentication tokens without validating their revocation status or expiry, with token metadata stored in DynamoDB.
- Insecure handling of password equivalents: storing plaintext passwords or using weak, unsalted hashes that are feasible to crack; then using DynamoDB
GetItemto retrieve the record and compare the credential in application code without proper protections. - Insufficient checks on the DynamoDB response: assuming the record exists and belongs to the request context without verifying ownership or scope, leading to logic flaws that can be chained to IDOR.
An example scenario: a Hapi route accepts a username from the request, queries DynamoDB for the user record, and if the record exists, logs the user in without verifying a server-side password check. An attacker can enumerate valid usernames or authenticate without knowing the password if the route does not enforce a constant-time comparison or require a second factor. Another scenario is using unsigned tokens or JWTs where the claims are trusted after a simple DynamoDB lookup, enabling token substitution if the token identifier is predictable.
Because DynamoDB is often used as a primary identity store, misconfigured access patterns, missing server-side validation, and weak credential storage directly contribute to broken authentication. The combination of Hapi’s flexible plugin ecosystem and DynamoDB’s schema-less design increases the surface if developers do not explicitly validate and protect each step of the authentication flow.
DynamoDB-Specific Remediation in Hapi — concrete code fixes
Remediation focuses on ensuring that authentication decisions are backed by verified secrets and that DynamoDB queries enforce strict ownership and correctness checks. Below are concrete code examples for secure credential storage and validation in Hapi with DynamoDB.
First, store credentials securely. Use a strong, adaptive hashing algorithm such as Argon2id for passwords. Never store plaintext or weakly hashed passwords in DynamoDB.
const argon2 = require('argon2');
async function storeUser(username, plaintextPassword) {
const hash = await argon2.hash(plaintextPassword);
const params = {
TableName: 'Users',
Item: {
username: { S: username },
passwordHash: { S: hash },
createdAt: { S: new Date().toISOString() }
}
};
await documentClient.put(params).promise();
}
When authenticating, retrieve the record by username and perform a constant-time comparison using the hash. Avoid leaking timing information that could enable offline brute-force attacks.
const argon2 = require('argon2');
const AWS = require('aws-sdk');
const documentClient = new AWS.DynamoDB.DocumentClient();
async function verifyUser(username, plaintextPassword) {
const params = {
TableName: 'Users',
Key: {
username: { S: username }
}
};
const data = await documentClient.get(params).promise();
if (!data.Item) {
// Use a dummy hash to keep timing consistent
await argon2.hash('dummy');
throw Boom.unauthorized('Invalid credentials');
}
const storedHash = data.Item.passwordHash.S;
const verified = await argon2.verify(storedHash, plaintextPassword);
if (!verified) {
throw Boom.unauthorized('Invalid credentials');
}
return { username: data.Item.username.S, roles: data.Item.roles?.SS || [] };
}
For session-based authentication, store session metadata in DynamoDB with a server-side session ID and validate it on each request. Use a cryptographically random session identifier and enforce expiration checks.
const crypto = require('crypto');
async function createSession(userId) {
const sessionId = crypto.randomBytes(32).toString('hex');
const expiresAt = Date.now() + 7 * 24 * 60 * 60 * 1000; // 7 days
const params = {
TableName: 'Sessions',
Item: {
sessionId: { S: sessionId },
userId: { S: userId },
expiresAt: { N: String(expiresAt) },
createdAt: { S: new Date().toISOString() }
}
};
await documentClient.put(params).promise();
return sessionId;
}
async function validateSession(sessionId) {
const params = {
TableName: 'Sessions',
Key: {
sessionId: { S: sessionId }
}
};
const data = await documentClient.get(params).promise();
if (!data.Item) {
throw Boom.unauthorized('Invalid session');
}
const expiresAt = Number(data.Item.expiresAt.N);
if (Date.now() > expiresAt) {
// Optionally delete expired session
await documentClient.delete({ TableName: 'Sessions', Key: { sessionId: { S: sessionId } } }).promise();
throw Boom.unauthorized('Session expired');
}
return data.Item.userId.S;
}
In Hapi, register an authentication strategy that uses these functions and attaches a verified user or session to request.auth.credentials. Always scope queries with the authenticated identifier to prevent IDOR, and avoid trusting client-provided identifiers for sensitive operations without a server-side DynamoDB check.
Finally, enable DynamoDB fine-grained IAM policies so that the application’s credentials only allow required actions on specific resources, reducing the impact of a compromised credential. Combine these measures with HTTPS, short-lived tokens where applicable, and regular rotation of secrets to maintain a strong authentication boundary.
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 |