HIGH insufficient loggingadonisjsjwt tokens

Insufficient Logging in Adonisjs with Jwt Tokens

Insufficient Logging in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability

When JWT tokens are used for authentication in AdonisJS, insufficient logging creates a gap between token issuance/validation and observable security events. JWTs are typically stateless: once issued, the server does not keep a server-side session record. This design reduces storage overhead but removes a natural audit trail unless application-layer logging is intentionally added.

Without explicit logging, several security-relevant token events go unrecorded: issuance (especially for long-lived or refresh tokens), validation failures (malformed tokens, expired tokens, signature mismatches), token refresh attempts, and use of potentially compromised tokens. An attacker who steals a JWT can reuse it until expiration, and without timestamps, actor identifiers, or token identifiers in logs, detecting misuse is difficult. In AdonisJS, this often occurs when developers rely on default HTTP request logging that records only method and path, omitting authorization context.

Additionally, AdonisJS applications that use the jwt provider from @adonisjs/auth may not log important claims such as scopes, roles, or tenant identifiers. If a token is accepted with insufficient validation (for example, not checking revocation or not validating iss/aud), the lack of claim-specific logs makes it harder to correlate anomalies. For example, a token used from an unusual IP or geolocation may be silently accepted because the application does not log IP or user-agent alongside the token validation outcome.

Real attack patterns such as token replay or credential stuffing often succeed quietly when logging is insufficient. Without structured logs capturing token metadata (jti, sub, iat, exp, scopes), incident response relies on incomplete access logs and delayed detection. This increases the window for lateral movement or privilege escalation, especially if the token has elevated scopes due to weak token binding or missing checks.

To address this, AdonisJS applications should log key security events with sufficient context: token issuance (subject, audience, scopes, not before, expiration), validation outcomes (valid/invalid/expired/malformed), refresh attempts, and failures tied to user identifiers and request metadata. Correlation identifiers that tie requests to tokens and users help trace flows across services. This makes token-related events observable and supports timely detection of suspicious patterns.

Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes

Remediation focuses on adding structured, actionable logs at critical points in the JWT lifecycle while preserving the stateless nature of tokens. Below are concrete examples using AdonisJS with the @adonisjs/auth package and JWT utilities.

1) Log token issuance with claims and context. When issuing tokens, log the subject, audience, scopes, and expiration:

import { DateTime } from 'luxon'
import logger from '@ioc:Adonis/Core/Logger'
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { JWTProvider } from '@ioc:Adonis/Addons/Auth'

export async function issueToken(ctx: HttpContextContract, user: any, scopes: string[]) {
  const jwtProvider = new JWTProvider()
  const payload = {
    sub: user.id,
    name: user.username,
    scopes,
    aud: 'api.example.com',
    iss: 'adonisjs-app',
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(DateTime.local().plus({ hours: 1 }).toSeconds()),
  }
  const token = await jwtProvider.generate(payload)
  logger.info('jwt.issued', {
    event: 'jwt_issued',
    subject: payload.sub,
    audience: payload.aud,
    scopes: payload.scopes,
    notBefore: payload.iat,
    expiresAt: payload.exp,
    requestId: ctx.request.id,
    ip: ctx.request.ip(),
    userAgent: ctx.request.userAgent(),
  })
  return token
}

The log entry includes structured fields (event, subject, audience, scopes, notBefore, expiresAt) alongside request metadata for correlation.

2) Log token validation outcomes, including failure reasons. In an authentication provider or middleware, capture and log validation results:

import logger from '@ioc:Adonis/Core/Logger'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { JwtProvider } from '@ioc:Adonis/Addons/Auth'

export async function validateToken(ctx: HttpContextContract) {
  const token = ctx.request.header('authorization')?.replace('Bearer ', '')
  if (!token) {
    logger.warn('jwt.validation_failed', {
      event: 'jwt_validation_failed',
      reason: 'missing_token',
      requestId: ctx.request.id,
      ip: ctx.request.ip(),
    })
    ctx.response.unauthorized('Authorization token missing')
    return
  }
  const jwtProvider = new JWTProvider()
  try {
    const payload = await jwtProvider.verify(token)
    logger.info('jwt.validated', {
      event: 'jwt_validated',
      subject: payload.sub,
      audience: payload.aud,
      scopes: payload.scopes,
      requestId: ctx.request.id,
      ip: ctx.request.ip(),
      userAgent: ctx.request.userAgent(),
    })
    return payload
  } catch (error) {
    const reason = error.name === 'TokenExpiredError' ? 'expired' : error.message
    logger.warn('jwt.validation_failed', {
      event: 'jwt_validation_failed',
      reason,
      requestId: ctx.request.id,
      ip: ctx.request.ip(),
      userAgent: ctx.request.userAgent(),
    })
    ctx.response.unauthorized('Invalid token')
    return null
  }
}

This captures both successful validations and distinct failure reasons (missing, expired, malformed, invalid signature), enabling alerting on repeated failures.

3) Log token refresh and revocation events. For refresh flows, log old and new token identifiers and associated user context:

import logger from '@ioc:Adonis/Core/Logger'

export async function logTokenRefresh(ctx: HttpContextContract, oldTokenPayload: any, newToken: string) {
  logger.info('jwt.refreshed', {
    event: 'jwt_refreshed',
    oldSubject: oldTokenPayload.sub,
    oldAudience: oldTokenPayload.aud,
    newTokenTruncated: newToken.slice(0, 8) + '...',
    requestId: ctx.request.id,
    ip: ctx.request.ip(),
    userAgent: ctx.request.userAgent(),
  })
}

4) Correlate logs with request IDs and user identifiers. Include a request-scoped correlation ID in every log entry so that token events can be traced alongside HTTP request logs. This supports end-to-end investigation across services.

5) Avoid logging raw tokens in full to prevent accidental exposure in log stores. If necessary for debugging, log only token identifiers (jti) or truncated values, and ensure logs are protected at rest and in transit.

Frequently Asked Questions

What specific JWT events should be logged in AdonisJS to detect misuse?
Log token issuance (with subject, audience, scopes, expiration), validation outcomes (valid/expired/malformed), refresh attempts, and revocation events. Include request metadata (IP, user-agent, request ID) and avoid logging raw tokens.
How can I prevent token leakage in logs while maintaining auditability?
Log token metadata (jti, sub, aud, scopes, timestamps) and truncate or hash tokens when necessary. Use structured logging with consistent fields and protect log storage with access controls and encryption.