HIGH session fixationadonisjsfirestore

Session Fixation in Adonisjs with Firestore

Session Fixation in Adonisjs with Firestore — 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 and the way session data is stored and validated interact with Firestore in ways that can enable fixation if certain protections are absent or misconfigured.

Adonisjs uses an extensible session layer. By default, it generates a new session ID upon authentication (regeneration), and stores session records server-side. When using Firestore as the session store, the session document ID typically maps to the session ID. If the application does not enforce regeneration after login, an attacker can set a victim’s session identifier (e.g., via a predictable token or by forcing a cookie value) and later use that same identifier to hijack the authenticated session.

With Firestore, session documents are read and written using the document ID as the key. If Adonisjs does not rotate the session ID on privilege change (e.g., post-login), the same Firestore document persists across authentication states. This creates a direct path for fixation: the attacker’s pre-set session ID remains valid after the victim logs in, because the backend does not issue a new ID or properly validate that the authenticated session’s ID matches the authenticated principal.

The risk is compounded when session data in Firestore includes metadata such as IP or user-agent, and the application performs incomplete validation. For example, if the session document is keyed only by the session ID and lacks binding to the user record or a freshness indicator, an attacker can reuse the document across requests. Firestore security rules do not mitigate application-layer session fixation; they only control document read/write access. Therefore, the application must ensure session ID rotation and strict ownership checks regardless of storage backend.

Real-world patterns include an attacker sending a link with a session cookie already set, or exploiting a login flow that preserves the pre-authentication session document. In Adonisjs, this can happen when session regeneration is omitted from the authentication controller, and when the Firestore adapter does not enforce strict document ownership checks tied to the user identity.

Firestore-Specific Remediation in Adonisjs — concrete code fixes

Remediation focuses on ensuring session ID rotation, binding sessions to user identity, and validating session provenance on each request. Below are concrete, idiomatic Adonisjs code examples using Firestore as the session store.

1. Enforce session regeneration on login

Always rotate the session after successful authentication to break any pre-set identifiers.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Session from 'App/Services/SessionService'

export default class SessionsController {
  public async login({ request, auth, session, response }: HttpContextContract) {
    const { email, password } = request.only(['email', 'password'])
    const user = await auth.authenticate()

    // Rotate session ID to prevent fixation
    await session.regenerate()

    // Bind session to user identity in Firestore
    await Session.bindUserToSession(user.id, session.id)

    session.flash({ auth_status: 'Logged in' })
    return response.redirect('/dashboard')
  }
}

2. Firestore session store implementation with user binding

Implement a custom Firestore-backed session adapter that stores the user ID within the session document and validates ownership on retrieval.

import { FirestoreClient } from '@google-cloud/firestore'
import SessionContract from '@ioc:Adonis/Session/Session'

export default class FirestoreSessionStore {
  private db: FirestoreClient
  private collection = 'sessions'

  constructor() {
    this.db = new FirestoreClient()
  }

  public async create(sessionId: string, payload: string, expiresAt: Date) {
    const docRef = this.db.collection(this.collection).doc(sessionId)
    await docRef.set({
      payload,
      expires_at: expiresAt.toISOString(),
      created_at: new Date().toISOString(),
    })
  }

  public async update(sessionId: string, payload: string) {
    const docRef = this.db.collection(this.collection).doc(sessionId)
    await docRef.update({ payload, updated_at: new Date().toISOString() })
  }

  public async fetch(sessionId: string, user: { id: string }) {
    const docRef = this.db.collection(this.collection).doc(sessionId)
    const doc = await docRef.get()
    if (!doc.exists) return null

    const data = doc.data()
    // Critical: validate that the session belongs to the authenticated user
    if (data.user_id !== user.id) {
      throw new Error('Session ownership mismatch')
    }
    return data.payload
  }

  public async delete(sessionId: string) {
    await this.db.collection(this.collection).doc(sessionId).delete()
  }
}

3. Middleware to validate session-user binding on each request

Add a lightweight middleware that ensures the session document in Firestore is tied to the authenticated user.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import FirestoreSessionStore from 'App/Services/FirestoreSessionStore'

const sessionStore = new FirestoreSessionStore()

export default class ValidateSessionBinding {
  public async handle({ session, auth, response, next }: HttpContextContract) {
    const user = auth.getUserOrFail()
    const sessionData = await sessionStore.fetch(session.id, { id: user.id })

    if (!sessionData) {
      return response.unauthorized('Invalid session')
    }

    await next()
  }
}

4. Configuration: disable predictable session IDs and enable secure defaults

Ensure Adonisjs is configured to use cryptographically strong session IDs and to bind sessions to additional context where feasible.

// config/session.ts
export default {
  driver: 'firestore',
  key: '__session',
  secure: true,
  httpOnly: true,
  sameSite: 'lax',
  // Ensure session IDs are long and random; avoid custom or predictable IDs
}

5. Continuous monitoring via GitHub Action

Use the middleBrick GitHub Action to add API security checks to your CI/CD pipeline and fail builds if risk scores drop below your threshold. This complements runtime protections by catching misconfigurations before deployment.

# .github/workflows/security.yml
name: API Security Checks
on: [push, pull_request]
jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run middleBrick scan
        uses: middlebjorn/middlebrick-github-action@v1
        with:
          url: 'https://your-api.example.com/openapi.json'
          threshold: 'C'

Frequently Asked Questions

Can Firestore security rules alone prevent session fixation in Adonisjs?
No. Firestore security rules control document access but do not enforce application-level session lifecycle practices such as session ID rotation or ownership validation. Adonisjs must explicitly regenerate session IDs and bind them to the authenticated user.
How does middleBrick help detect session fixation risks in APIs using Adonisjs and Firestore?
middleBrick scans the unauthenticated attack surface and maps findings to frameworks like OWASP API Top 10. By analyzing OpenAPI specs against runtime behavior, it can highlight missing session regeneration and inconsistent session binding, which are relevant to fixation risks in Adonisjs with Firestore.