HIGH session fixationadonisjsjwt tokens

Session Fixation in Adonisjs with Jwt Tokens

Session Fixation in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability

Session fixation in AdonisJS when using JWT tokens occurs when an application accepts an attacker-supplied token or session identifier for authentication without validating its origin or ensuring it is issued after authentication. With traditional cookie-based sessions, fixation involves forcing a user to use a known session ID; with JWTs, the risk shifts to accepting a pre-shared or predictable token as proof of identity.

AdonisJS does not manage server-side sessions when JWTs are used; instead, the client presents the token on each request via an Authorization header. If your application accepts a JWT issued before login (for example, a token generated with a static subject or without tying it to an authenticated user ID), an attacker can fixate that token and reuse it after the legitimate user authenticates. This can happen when tokens are generated with weak or static subject (sub) claims, or when the application uses the same token across authentication boundaries (e.g., reusing a token issued during registration after the user logs in).

Another vector involves insufficient validation of token issuers and audiences. If AdonisJS verifies the signature but does not enforce strict checks on the iss (issuer) and aud (audience) claims, an attacker can supply a token issued by a different service or intended for another resource. Additionally, if the secret or private key used to sign tokens is exposed, or if tokens have long lifetimes without rotation, a fixed token remains valid for extended periods, increasing the window for exploitation.

The framework’s JWT utilities, such as use('jwt') helpers, provide methods to generate and verify tokens, but they do not automatically mitigate fixation. Developers must ensure that tokens are issued only after successful authentication, include dynamic per-user claims (e.g., a user ID and a random session nonce), and are rejected if presented before authentication. Without these safeguards, an attacker can trick a victim into authenticating with a token the attacker already knows, effectively hijacking the session.

Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes

Remediation centers on issuing fresh, authenticated-bound JWTs after login and enforcing strict validation on each request. Below are concrete, syntactically correct examples for AdonisJS using the official JWT provider.

1. Issue a new token only after successful authentication

Do not reuse tokens generated before login. After validating credentials, generate a new token that binds to the authenticated user.

import { BaseController } from '@ioc:Adonis/Core/Controller'
import { DateTime } from 'luxon'
import { JWTProvider } from '@ioc:Adonis/Addons/Auth'

export default class SessionsController extends BaseController {
  public async store({ request, auth, response }) {
    const { email, password } = request.only(['email', 'password'])
    const user = await User.findBy('email', email)

    if (!user || !(await user.verifyPassword(password))) {
      return response.badRequest({ error: 'Invalid credentials' })
    }

    // Issue a fresh token tied to the authenticated user
    const token = await auth.use('jwt').generate(user)

    return response.ok({ token })
  }
}

2. Validate issuer, audience, and user binding on each request

Ensure tokens include and validate dynamic claims such as user ID and a nonce or session identifier.

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

export async function validateJwt(ctx: HttpContextContract) {
  const jwt = ctx.auth.use('jwt')
  const payload = await jwt.verify((token) => {
    // Custom verification rules can be applied here if needed
    return token
  })

  // Enforce strict claims
  const jwtSchema = schema.create({
    sub: schema.number(),
    iat: schema.number(),
    exp: schema.number(),
    iss: schema.literal('your-app-issuer'),
    aud: schema.literal('your-app-audience'),
  })

  // This will throw if claims do not match
  const validated = jwtSchema.validate(payload)

  // Ensure the token is bound to the current user in your application state
  const user = await User.findOrFail(validated.sub)
  if (!user.sessionNonce) {
    throw new Exception('Invalid session', 401)
  }

  // Optional: bind token to a nonce or device fingerprint
  // if (payload.nonce !== user.sessionNonce) { ... }

  return user
}

3. Configure token lifetime and rotation

Short-lived tokens reduce the impact of a fixed token. Use refresh tokens or re-authentication for sensitive operations.

// In config/auth.ts
export const jwt = {
  secret: process.env.JWT_SECRET,
  token: {
    age: '15m',   // short lifetime
    type: 'jwt',
  },
  refreshToken: {
    age: '7d',    // longer but revocable
  },
}

4. Enforce issuer and audience checks globally

AdonisJS allows you to define these within the JWT provider configuration to prevent acceptance of tokens from other sources.

// In config/auth.ts
export const jwt = {
  secret: process.env.JWT_SECRET,
  token: {
    age: '15m',
    type: 'jwt',
  },
  provider: 'jwt',
  issuer: 'your-app-issuer',
  audience: 'your-app-audience',
}

Frequently Asked Questions

Can reusing a JWT across login and API calls cause fixation in AdonisJS?
Yes. If you generate a JWT before authentication (e.g., during registration or with static claims) and reuse it after login, an attacker who knows that token can fixate it. Always issue a new JWT after successful login and avoid reusing pre-authentication tokens.
What claims should I include to prevent fixation when using JWTs in AdonisJS?
Include dynamic, user-bound claims: a numeric subject (sub) matching the user ID, an issuer (iss) and audience (aud) validated server-side, an issued-at (iat) and expiration (exp), and optionally a nonce or session identifier that you verify against your application state.