Time Of Check Time Of Use in Adonisjs with Api Keys
Time Of Check Time Of Use in Adonisjs with Api Keys — how this specific combination creates or exposes the vulnerability
Time of Check Time of Use (TOCTOU) occurs when the state used for an access decision changes between the check and the use of that resource. In AdonisJS applications that rely on API keys for authentication, TOCTOU can manifest when authorization logic performs a lookup or validation of an API key early in the request lifecycle, then uses a cached or stale representation of the key’s permissions or validity at the point of action. Because API key validation is often decoupled from runtime data ownership (BOLA/IDOR) and privilege checks, this gap can allow an attacker to change permissions, scope, or even the key’s association between the check and the use.
Consider an AdonisJS route that first validates an API key via middleware, attaches a user-like context to the request, and later performs authorization checks scoped to that context. If the key’s permissions are stored in a cache or in-memory map, and those permissions are updated (e.g., scope reduced or revoked) after the middleware check but before a downstream handler evaluates ownership or role-based access, the handler may operate with outdated privileges. This is especially relevant for endpoints that expose sensitive records or perform state changes. The TOCTOU gap is compounded when authorization is split across multiple checks—such as validating a key, then checking a record’s ownership separately—because the record’s state or the key’s bindings may change between those checks.
In practice, an attacker might rotate or revoke an API key on the server side after the middleware validation but before a sensitive operation executes. If the application relies on cached claims rather than revalidating scope or ownership at the point of use, the operation may proceed with elevated or unintended permissions. This pattern maps directly to BOLA/IDOR when the authorization check uses a key-derived subject ID without reconfirming that the subject still holds access to the specific target resource. Similarly, privilege escalation may occur when an API key is initially scoped to limited operations but later used to perform broader actions because the privilege check was not re-evaluated with the latest bindings.
Because middleBrick tests authentication, BOLA/IDOR, and privilege escalation in parallel, it can surface these TOCTOU patterns in unauthenticated scans by observing inconsistent authorization outcomes across requests. The scanner does not alter server state; it detects whether timing and separation of checks create opportunities where a key’s validation and its subsequent use are not tightly synchronized. Developers should treat these findings as indicators to consolidate authorization decisions and ensure that permissions and ownership are verified at the moment of action, not earlier.
Api Keys-Specific Remediation in Adonisjs — concrete code fixes
Remediation focuses on ensuring that authorization decisions involving API keys are verified at the point of use, with minimal separation between check and use. Avoid caching permissions or user contexts derived from API keys across asynchronous boundaries or middleware layers unless the cache is tightly invalidated on any change to key scope or bindings. Prefer a single, atomic authorization check that revalidates the key and evaluates the target resource together.
Below are concrete AdonisJS examples that demonstrate a vulnerable pattern and a corrected approach using scoped API key validation and runtime rechecks.
Vulnerable Pattern: Cached Key Scopes with Delayed Authorization
// routes.ts
Route.get('/documents/:id', async (ctx) => {
const { id } = ctx.params
// Middleware attaches request.user from cached key claims
const document = await Document.find(id)
// TOCTOU: user.permissions may be stale by this point
if (!userCanAccessDocument(ctx.request.user, document)) {
throw new ForbiddenException('Access denied')
}
return document
})
// middleware/Auth.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class AuthMiddleware {
public async handle(ctx: HttpContextContract, next: () => Promise) {
const key = ctx.request.header('x-api-key')
const payload = validateApiKey(key) // cached validation
ctx.request.user = { id: payload.sub, permissions: payload.permissions }
await next()
}
}
Remediated Pattern: Re-validate Key and Resource Together
// routes.ts
Route.get('/documents/:id', async (ctx) => {
const { id } = ctx.params
const key = ctx.request.header('x-api-key')
// Re-validate key and fetch permissions at point of use
const { scope, subjectId } = await validateApiKeyAtUse(key)
const document = await Document.find(id)
// Reconfirm ownership/scope immediately before action
if (!await canAccessDocument(subjectId, document.id, scope)) {
throw new ForbiddenException('Access denied')
}
return document
})
// services/authorization.ts
export async function canAccessDocument(subjectId: string, documentId: string, scope: string[]): Promise {
// Perform a direct check against the latest bindings
const binding = await ApiKeyBinding.findBy({ subjectId, resourceId: documentId, resourceType: 'document' })
return binding !== null && scope.includes(binding.permission)
}
Key Practices for AdonisJS
- Minimize the window between checking an API key and using it; perform validation and authorization in the same handler or transaction where possible.
- When using scopes, re-resolve them at runtime rather than relying on middleware-injected claims that may become outdated.
- Ensure that changes to API key bindings or scopes invalidate any caches or tokens that represent permissions.
- Map findings to frameworks such as OWASP API Top 10 and compliance regimes (PCI-DSS, SOC2) to align remediation with recognized controls.