Session Fixation in Adonisjs with Mutual Tls
Session Fixation in Adonisjs with Mutual Tls — how this specific combination creates or exposes the vulnerability
Session fixation occurs when an application allows an attacker to force a user to use a known session identifier. In Adonisjs, the default session management behavior combined with the use of Mutual TLS (mTLS) can inadvertently expose or create fixation conditions if the application does not rotate the session identifier after successful client authentication.
Mutual TLS authenticates the client by presenting a client certificate during the TLS handshake. Adonisjs can be configured to require and validate these certificates, but the framework’s session layer operates independently of the TLS layer. If the server creates a session before or immediately after mTLS verification and does not issue a new session identifier, an attacker who knows or predetermines the session ID can simply reuse it in a subsequent mTLS-authenticated request.
Consider a scenario where an endpoint relies solely on mTLS for client identity but still uses a static or predictable session cookie. An attacker could craft a link or host a malicious page that sets a known session cookie in the victim’s browser. When the victim visits the page over mTLS, the server associates the known session ID with the authenticated client certificate, granting the attacker access to the victim’s authenticated session.
Adonisjs does not inherently tie session regeneration to mTLS completion. Without explicit guidance, the framework will continue to accept the existing session cookie even when mTLS presents a strong client certificate. This mismatch between transport-layer authentication and application-layer session management is the root cause of the exposure.
Real-world analogies include scenarios where a bank verifies your identity via a government-issued hardware token (mTLS) but then allows you to continue using a weak, non-rotating access code (session ID). If that code is known or guessable, the overall security is compromised.
Key contributors to this risk include:
- Failure to regenerate the session ID after mTLS client verification.
- Use of default session drivers without additional entropy injected by the certificate context.
- Missing validation that ties the session to attributes derived from the client certificate (e.g., fingerprint or subject).
Mutual Tls-Specific Remediation in Adonisjs — concrete code fixes
Remediation focuses on ensuring that a new, unpredictable session identifier is established after successful mTLS validation and that the session is bound to attributes from the client certificate.
First, configure Adonisjs to require client certificates. In your TLS termination layer (e.g., a reverse proxy or Node.js HTTPS server), enforce requestCert and rejectUnauthorized. Below is an example of an HTTPS server setup in an Adonisjs project using Node’s https module with mTLS enabled:
const https = require('https');
const fs = require('fs');
const { Ignitor } = require('@adonisjs/ignitor');
const server = https.createServer({
key: fs.readFileSync('path/to/server.key'),
cert: fs.readFileSync('path/to/server.crt'),
ca: fs.readFileSync('path/to/ca.crt'),
requestCert: true,
rejectUnauthorized: true,
}, (req, res) => {
// Forward to Adonisjs bootstrap
new Ignitor(require('@adonisjs/fold'))
.appRoot(__dirname)
.handleHttpRequest(req, res);
});
server.listen(443);
Second, in your Adonisjs start/routes.js or within an authentication middleware, regenerate the session after mTLS verification. Use the session manager to create a new ID and explicitly set session-bound certificate metadata. Example middleware logic:
const crypto = require('crypto');
async function handleMtlsSession({ request, auth, response, next }) {
const cert = request.socket.getPeerCertificate();
if (cert && cert.fingerprint) {
// Ensure a fresh session is created
if (!request.session().get('mtls_regenerated')) {
await request.session().regenerate();
request.session().put('mtls_fingerprint', cert.fingerprint);
request.session().put('mtls_regenerated', true);
} else {
// Validate that the session matches the presented certificate
if (request.session().get('mtls_fingerprint') !== cert.fingerprint) {
throw new Error('Session mismatch: certificate changed');
}
}
}
await next();
}
// In a route or start/handle.js
Route.get('/secure', handleMtlsSession, async ({ auth }) => {
const user = await auth.check();
return user;
});
Third, bind session usage to the certificate context by storing a hash of the certificate fingerprint in the session and validating it on each request. This prevents fixation by ensuring that even if a session ID is known, it cannot be reused across different certificates.
Finally, consider combining mTLS with short-lived session cookies and strict SameSite attributes to reduce the window of opportunity for fixation. The goal is to make the session inseparable from the presented client identity without relying on the framework to do this automatically.