Session Fixation in Feathersjs with Dynamodb
Session Fixation in Feathersjs with Dynamodb — how this specific combination creates or exposes the vulnerability
Session fixation occurs when an application assigns a user a session identifier before authentication and does not regenerate that identifier after login. In a Feathersjs application using DynamoDB as the session store, this behavior can expose a predictable or attacker-controlled session ID across the authentication boundary.
Feathersjs does not enforce session regeneration by default when using custom session stores. If the session middleware (for example express-session) is configured with a DynamoDB store and the application does not call req.session.regenerate after successful authentication, the session ID issued before login may be reused after login. This means an attacker who can set or guess a session ID can later authenticate as the victim while the server-side session record in DynamoDB remains bound to the same ID.
DynamoDB stores session documents by session ID (e.g., as the partition key). If the ID is predictable (e.g., based on weak entropy or not regenerated on privilege change), an attacker who knows or sets the ID can retrieve or overwrite session attributes. Because DynamoDB is often used with longer TTLs for session persistence, a leaked or fixed session ID can remain valid across sessions unless explicitly invalidated, increasing the window for exploitation.
The combination is risky when:
- Session IDs are generated before login and not rotated on authentication.
- Session store records do not enforce strict ownership checks tied to authenticated user identifiers.
- The application relies on ID-based retrieval in DynamoDB without additional context binding (e.g., user ID or instance ID) in the session record.
For example, an attacker could send a victim a link with a known session ID, induce the victim to authenticate, and then use the same ID to hijack the session. Because the DynamoDB item remains keyed by that ID, the server may treat the authenticated and unauthenticated session as the same logical session if the application does not validate that the authenticated user identity is bound to the session record on each request.
Dynamodb-Specific Remediation in Feathersjs — concrete code fixes
Remediation focuses on ensuring a new session ID is created after authentication and that session records in DynamoDB are keyed and validated with sufficient context to prevent fixation and cross-user confusion.
Regenerate session after authentication
Always regenerate the session immediately after a successful login. With express-session and a DynamoDB store, call req.session.regenerate in the login handler before establishing any session data.
const session = require('express-session');
const DynamoDBStore = require('connect-dynamodb')(session);
const store = new DynamoDBStore({
table: 'sessions',
partitionKey: 'sessionId',
// other DynamoDB client options
});
app.use(session({
store,
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { secure: true, httpOnly: true, sameSite: 'strict' }
}));
app.post('/login', (req, res, next) => {
// authenticate credentials …
const authenticatedUser = { id: 'user-123', role: 'user' };
// Critical: regenerate before setting user data
req.session.regenerate((err) => {
if (err) return next(err);
req.session.userId = authenticatedUser.id;
req.session.role = authenticatedUser.role;
req.session.cookie.maxAge = 24 * 60 * 60 * 1000;
res.redirect('/dashboard');
});
});
Enrich DynamoDB session records with user binding and strict retrieval
Store the authenticated user identifier in the session item and enforce ownership checks on each request. When loading a session from DynamoDB, verify that the session’s user context matches the authenticated context used for authorization decisions.
// After regeneration, ensure the DynamoDB item contains userId
// Example item shape: { sessionId: 'abc', userId: 'user-123', cookie: { ... }, [other fields] }
// Middleware to validate session ownership on each request
function validateSessionOwnership(req, res, next) {
if (!req.session || !req.session.userId) {
return res.status(401).send('Unauthorized');
}
// Optionally, fetch the session from DynamoDB to confirm consistency
// get({ Key: { sessionId: req.sessionID } }, (err, data) => {
// if (err || !data || data.userId !== req.session.userId) {
// return res.status(401).send('Invalid session');
// }
// next();
// });
next();
}
app.use(validateSessionOwnership);
Use strong IDs and avoid exposing session IDs in URLs
Ensure session IDs are long, random values to prevent brute-force guessing against DynamoDB items. Configure cookies with httpOnly, secure, and sameSite attributes, and avoid URL-based session identifiers which can leak in logs or referrers.
Invalidate sessions explicitly on logout
Remove the session item from DynamoDB on logout to reduce the lifetime of any fixed session IDs.
app.get('/logout', (req, res, next) => {
const { sessionId } = req.session;
store.destroy(sessionId, (err) => {
if (err) return next(err);
req.session = null;
res.redirect('/');
});
});
Consider additional context in the key or item
For higher assurance, include a stable user identifier in the DynamoDB item and design key names or secondary indexes to support queries that detect anomalies (e.g., session IDs associated with multiple users). This does not replace regeneration but adds defense-in-depth when auditing session usage.