HIGH cache poisoningadonisjsjwt tokens

Cache Poisoning in Adonisjs with Jwt Tokens

Cache Poisoning in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability

Cache poisoning occurs when an attacker causes a cache to store malicious or incorrect data that is subsequently served to other users. In AdonisJS applications that rely on JSON Web Tokens (JWT) for stateless authentication, combining HTTP caching with JWT handling can unintentionally expose sensitive data or enable privilege escalation if cached responses vary by authorization context.

When an AdonisJS app issues JWTs with role or scope claims (e.g., role: admin), a cached response generated for an authorized user might be reused for an unauthenticated or lower-privileged user if the cache key does not incorporate token-specific or user-specific values. This mismatch can reveal data that should be restricted and may facilitate horizontal or vertical privilege escalation. For example, an endpoint that returns user profile information with an Authorization: Bearer <token> header might be cached at a gateway or CDN without considering the token payload, so a subsequent request without a valid admin token receives the cached admin response.

Common causes in AdonisJS include:

  • Caching entire HTTP responses without including the JWT or a derived user identifier in the cache key.
  • Caching based only on the request URL and query parameters while ignoring headers that carry authorization tokens.
  • Using shared cache storage for multiple tenants or users without segregating data by token claims.

These patterns can violate the principle of least privilege and expose information across users. Because JWTs are often validated in middleware, it is important to ensure that any caching layer respects authorization context and does not treat authenticated responses as safely shareable across different principals.

Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes

To mitigate cache poisoning when using JWTs in AdonisJS, ensure cache keys incorporate token claims or user identifiers, avoid caching sensitive responses, and explicitly vary cache behavior by authorization headers.

Include JWT claims in cache keys

When caching must occur for authenticated endpoints, derive the cache key from a non-sensitive, stable claim such as the subject (sub) or a tenant identifier, rather than the raw token.

const { auth } = use('Adonis/Addons/Auth')
const { Cache } = use('Adonis/Addons/Cache')

Route.get('/profile', async ({ request }) => {
  const token = request.header('authorization')?.replace('Bearer ', '')
  if (!token) {
    return { error: 'Unauthorized' }
  }
  // Verify and decode the JWT using your existing strategy
  const payload = verifyJwt(token) // implement verifyJwt using jsonwebtoken or similar
  const cacheKey = `profile:${payload.sub}`
  const cached = await Cache.get(cacheKey)
  if (cached) {
    return cached
  }
  const profile = await Profile.find(payload.sub)
  const result = profile.toJSON()
  await Cache.put(cacheKey, result, 300)
  return result
})

function verifyJwt(token) {
  // Example using jsonwebtoken (pseudo-implementation)
  const jwt = require('jsonwebtoken')
  return jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] })
}

Do not cache sensitive or user-specific responses

For endpoints that return private or role-sensitive data, disable caching or scope caching to the authenticated user explicitly. Use HTTP headers to prevent shared caches from storing responses.

Route.get('/admin/users', async ({ auth, response }) => {
  const user = await auth.getUserOrFail()
  if (user.role !== 'admin') {
    return response.unauthorized()
  }
  // Prevent caching of sensitive admin data
  response.header('Cache-Control', 'no-store, no-cache, must-revalidate, private')
  response.header('Pragma', 'no-cache')
  response.header('Expires', '0')
  return Users.all()
})

Vary cache by authorization headers

If your caching layer supports it, configure cache variability based on the Authorization header to prevent one user's authorized response from being served to another.

// Example for a reverse proxy or gateway configuration conceptually described in code
// Ensure that cache keys include the Authorization header or its derived user context
const cacheKey = `resource:${request.url()}:${request.header('authorization') || 'anonymous'}`

Use role-aware middleware to control exposure

Do not rely on cached data for authorization decisions. Re-validate token claims on each request and enforce permissions server-side.

Route.get('/settings', async ({ auth, response }) => {
  const user = await auth.getUserOrFail()
  // Always fetch fresh data for sensitive operations
  const settings = await Settings.query().where('user_id', user.id).first()
  return settings
})

Frequently Asked Questions

How can I verify that my cache keys include the JWT subject in AdonisJS?
Log or monitor the cache key used in your route logic and confirm it incorporates the sub claim or another stable user identifier derived from the validated JWT, rather than only the request path.
Should I ever cache responses that include an Authorization header in AdonisJS?
Generally no. Avoid caching responses for authenticated endpoints unless you explicitly include the token-derived user context in the cache key and you accept the risks; prefer no-store cache directives for sensitive data.