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?
sub claim or another stable user identifier derived from the validated JWT, rather than only the request path.