HIGH man in the middleadonisjsjwt tokens

Man In The Middle in Adonisjs with Jwt Tokens

Man In The Middle in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability

When JWT tokens are issued and validated in an AdonisJS application without transport integrity guarantees, a Man In The Middle (MitM) can intercept and manipulate authentication and data in transit. AdonisJS does not inherently enforce HTTPS for token issuance or validation, so if your routes and token endpoints are served over HTTP, an attacker on the same network can observe or alter tokens, cookies, and API requests. This exposes the application to classic interception, downgrade, and session hijacking scenarios.

JWT tokens themselves are often assumed to be tamper-proof because they are signed; however, signing does not equal confidentiality or integrity in transit. Without HTTPS, a MitM can capture the raw token from responses or requests, then replay it to impersonate users (replay attacks). AdonisJS applications that accept tokens from query parameters, headers like Authorization: Bearer, or cookies without Secure and HttpOnly protections make it easier for a MitM to steal or modify tokens. Additionally, if tokens contain sensitive claims (such as roles or permissions) and are transmitted in clear text, attackers can learn the authorization model of the API.

AdonisJS applications that rely on asymmetric algorithms (e.g., RS256) must ensure public keys are delivered securely; otherwise, a MitM could substitute a public key to verify a maliciously crafted token. Similarly, symmetric algorithms (e.g., HS256) require the secret to remain confidential; transmitting or storing the secret over insecure channels undermines integrity. MiddleBrick scans detect unauthenticated endpoints where tokens are issued or validated over non-HTTPS origins, flagging encryption misconfigurations and insecure cookie practices as high-impact findings.

Another MitM-specific risk arises from token binding and audience/issuer validation. If AdonisJS does not strictly validate the iss, aud, and sub claims, an attacker can use a token issued for one context in another context after interception. Without strict host and protocol checks in your AdonisJS server configuration, a MitM can redirect or proxy requests to a malicious server that still presents a valid-looking token.

To illustrate, consider an AdonisJS route that issues a JWT over HTTP:

// routes.ts
Route.post('/login', async ({ request, response }) => => {
  const { email, password } = request.body()
  // Validate credentials omitted for brevity
  const token = jwt.sign({ sub: user.id, email }, Env.get('JWT_SECRET'), { expiresIn: '1h' })
  return response.send({ token })
})

If this endpoint is HTTP, a MitM can capture the token and replay it against protected endpoints. Even with HTTPS, if cookies are not marked Secure and HttpOnly, or if the Authorization header is logged inadvertently, interception remains feasible.

Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes

Remediation focuses on transport integrity, secure storage, and strict validation. Always serve authentication and token validation endpoints over HTTPS and enforce it at the framework and infrastructure level.

First, configure AdonisJS to require HTTPS in production by setting environment-driven redirects and ensuring secure cookies:

// start/hooks.ts
import { defineConfig } from '@adonisjs/core/app'

export default defineConfig({
  https: {
    enabled: () => Env.get('NODE_ENV') === 'production',
    force: true,
  },
})

Second, issue JWT tokens only over HTTPS and set secure cookie attributes if storing tokens in cookies:

// controllers/AuthController.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import jwt from 'jsonwebtoken'

export default class AuthController {
  public async login({ request, response }: HttpContextContract) {
    const { email, password } = request.body()
    // Validate credentials
    const user = await User.findBy('email', email)
    if (!user || !user.verifyPassword(password)) {
      return response.unauthorized()
    }

    const token = jwt.sign(
      { sub: user.id, email: user.email },
      Env.get('JWT_SECRET'),
      { expiresIn: '1h' }
    )

    // Use Secure, HttpOnly, SameSite=Strict cookies over HTTPS
    response.cookie('token', token, {
      httpOnly: true,
      secure: Env.get('NODE_ENV') === 'production',
      sameSite: 'strict',
      path: '/',
      domain: 'yourdomain.com',
    })

    return response.ok({ message: 'Authenticated' })
  }
}

Third, validate issuer, audience, and algorithms strictly in middleware to prevent token substitution:

// middleware/validate-jwt.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import jwt from 'jsonwebtoken'

export default class ValidateJwtMiddleware {
  public async handle(ctx: HttpContextContract, next: () => Promise<void>) {
    const authHeader = ctx.request.header('authorization')
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return ctx.response.unauthorized('Missing token')
    }

    const token = authHeader.split(' ')[1]
    try {
      const decoded = jwt.verify(token, Env.get('JWT_SECRET'), {
        algorithms: ['HS256'],
        issuer: 'your-app-issuer',
        audience: 'your-app-audience',
      })

      ctx.auth.user = decoded as { sub: number; email: string }
      await next()
    } catch (error) {
      return ctx.response.unauthorized('Invalid token')
    }
  }
}

Fourth, avoid exposing tokens in URLs or logs. Prefer headers and ensure request logging excludes Authorization headers:

// middleware/scrub-logs.ts
export default class ScrubLogsMiddleware {
  public async handle(ctx, next) {
    // Prevent logging Authorization header
    const sensitiveHeaders = ['authorization', 'cookie']
    sensitiveHeaders.forEach((h) => {
      ctx.request.toJSON().headers[h] = '[redacted]'
    })
    await next()
  }
}

Fifth, pin public keys or ensure secure distribution of secrets. If using RS256, validate the kid against a trusted JWKS endpoint and reject tokens with missing or unexpected keys:

// services/jwks.ts
import jwksClient from 'jwks-rsa'

const client = jwksClient({
  jwksUri: 'https://your-auth-server.com/.well-known/jwks.json',
  cache: true,
  rateLimit: true,
})

export function getKey(header: any, callback: any) {
  client.getSigningKey(header.kid, (err, key) => {
    if (err) return callback(err)
    const signingKey = key.getPublicKey()
    callback(null, signingKey)
  })
}

Finally, enforce strict CORS and HSTS policies to reduce the effectiveness of MitM within browsers:

// start/hooks.ts
export default defineConfig({
  cors: {
    enabled: true,
    origin: 'https://yourdomain.com',
    allowHeaders: ['Authorization', 'Content-Type'],
    allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
    credentials: true,
  },
  hsts: {
    enabled: true,
    maxAge: '180d',
    includeSubDomains: true,
    preload: false,
  },
})

Frequently Asked Questions

Can JWT tokens be safely passed in query parameters in Adonisjs?
No. Passing JWT tokens in query parameters exposes them to leakage in logs, browser history, and Referer headers. Use HTTP-only Secure cookies or Authorization headers instead.
What should I do if my Adonisjs app receives tokens from unauthenticated LLM endpoints?
Treat tokens from unauthenticated LLM endpoints as high-risk. Validate issuer, audience, and algorithm strictly, and avoid accepting tokens from sources that do not require authentication or TLS.