HIGH session fixationadonisjsbasic auth

Session Fixation in Adonisjs with Basic Auth

Session Fixation in Adonisjs with Basic Auth — how this specific combination creates or exposes the vulnerability

Session fixation in AdonisJS when Basic Auth is in use arises from a mismatch between how HTTP Basic Authentication credentials are validated and how session identifiers are assigned or accepted. In AdonisJS, Basic Auth is typically handled in an authentication layer that verifies a username and password on each request, often via an auth:basic route middleware or an Authenticator provider. When authentication succeeds, the application may create or attach a session (for example to remember post-login state) using AdonisJS session utilities such as auth.use('session'). If the session identifier (e.g., a cookie named sid) is accepted from the client without being regenerated after successful authentication, an attacker can fix the session ID before the victim authenticates.

Consider an endpoint protected by Basic Auth where the server always uses the client-supplied session ID (e.g., from request.cookies.sid) without forcing a new session after credentials are verified. An attacker can craft a link with a known session ID, get the victim to authenticate with valid Basic Auth credentials over HTTPS, and then use the known session ID to impersonate the victim. This works because Basic Auth transmits credentials on every request; if the session binding is weak or missing integrity checks, the fixed session becomes a privileged session for the attacker.

Additionally, if AdonisJS session configuration is permissive (e.g., not setting httpOnly, secure, or proper sameSite flags), and if the application does not tie the session explicitly to the authenticated identity (e.g., by validating the session user ID on each request), the fixed session can be abused across users or across authentication boundaries. The risk is higher in APIs that mix cookie-based sessions with header-based Basic Auth, where developers might assume the auth header alone suffices while inadvertently relying on a mutable session state.

Basic Auth-Specific Remediation in Adonisjs — concrete code fixes

To mitigate session fixation in AdonisJS with Basic Auth, ensure that a new session is created (or the session binding is strongly validated) immediately after successful authentication. Do not rely on the client-supplied session ID. Below are concrete code examples and configuration steps.

1. Force session regeneration after Basic Auth login

If you use session-based authentication alongside Basic Auth, regenerate the session ID after verifying credentials. This ensures the server-side session is not tied to a client-chosen identifier.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { schema } from '@ioc:Adonis/Core/Validator'

export default class SessionsController {
  public async authenticate({ request, auth, session }: HttpContextContract) {
    const bodySchema = schema.create({
      username: schema.string.optional(),
      password: schema.string.optional()
    })
    const validated = await request.validate({ schema: bodySchema })

    // Example: Basic Auth via custom header validation (not relying on client session ID)
    const username = request.header('x-api-user')
    const password = request.header('x-api-key')

    // Perform your own verification (e.g., against a database or LDAP)
    const isValid = await this.verifyBasicCredentials(username, password)

    if (!isValid) {
      return response.unauthorized({ message: 'Invalid credentials' })
    }

    // Regenerate session to prevent fixation
    await session.regenerate()

    // Bind identity to the new session explicitly
    session.put('userId', userId)
    session.put('authMethod', 'basic')

    return { message: 'Authenticated', userId }
  }

  private async verifyBasicCredentials(username: string | undefined, password: string | undefined): Promise {
    // Replace with your credential store lookup
    return username === 'alice' && password === 's3cret'
  }
}

2. Use Basic Auth without session reliance, or lock sessions to authentication context

If your API primarily uses Basic Auth statelessly, avoid creating sessions unless necessary. When sessions are required, bind them to the authenticated identity and validate on each request.

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

export default class AuthMiddleware {
  public async handle(ctx: HttpContextContract, next: () => Promise) {
    const username = ctx.request.header('authorization-user')
    const token = ctx.request.header('authorization-token')

    if (!username || !token) {
      return ctx.response.unauthorized({ message: 'Missing auth headers' })
    }

    // Verify credentials (e.g., against hashed tokens)
    const user = await User.findBy('username', username)
    if (!user || user.token !== hash(token)) {
      return ctx.response.unauthorized({ message: 'Invalid basic auth' })
    }

    // If using session, ensure it reflects the authenticated user and is not fixable
    await ctx.session.regenerate()
    ctx.session.put('userId', user.id)

    await next()
  }
}

3. Session configuration hardening

Adjust AdonisJS session settings to reduce exposure:

  • Set httpOnly: true, secure: true (in production), and sameSite: 'lax' (or 'strict' for sensitive endpoints) in config/session.ts.
  • Do not accept session identifiers from query parameters or non-HTTP-safe methods.
  • Ensure your application validates the session user ID on every request, even when Basic Auth headers are present, to avoid desync between auth and session state.

4. Validate identity on every request

Even when using Basic Auth headers, treat each request independently. Do not assume a pre-existing session alone proves authentication unless you have verified the session’s binding to the current credentials on every call.

// Example per-request validation
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export async function ensureAuthenticated(ctx: HttpContextContract) {
  const username = ctx.request.header('authorization-user')
  const password = ctx.request.header('authorization-token')
  const user = await User.findBy('username', username)

  if (!user || user.password !== hash(password)) {
    ctx.response.unauthorized({ message: 'Unauthorized' })
  }

  // Optionally ensure session matches user if sessions are used
  if (ctx.session.get('userId') !== user.id) {
    await ctx.session.regenerate()
    ctx.session.put('userId', user.id)
  }
}

Frequently Asked Questions

Does Basic Auth eliminate session fixation risks in AdonisJS?
No. Basic Auth transmits credentials on every request, but if your application creates or accepts a client-supplied session ID without regenerating it after authentication, session fixation remains possible. Always regenerate the session after successful Basic Auth and bind the session explicitly to the authenticated identity.
Should I use sessions with Basic Auth in AdonisJS APIs?
For stateless APIs, prefer header-based Basic Auth without sessions. If you must use sessions, ensure the session is regenerated upon authentication and tightly bound to the verified user, with secure cookie attributes (httpOnly, secure, sameSite) and per-request identity validation.