HIGH cross site request forgeryadonisjsapi keys

Cross Site Request Forgery in Adonisjs with Api Keys

Cross Site Request Forgery in Adonisjs with Api Keys

Cross Site Request Forgery (CSRF) in AdonisJS when using API keys arises when a state-changing route relies solely on an API key for authentication without anti-CSRF protections. AdonisJS commonly uses session-based CSRF protection for browser-facing forms, but API routes that accept an API key in a header (e.g., X-API-Key) often skip CSRF checks because APIs are considered stateless. The vulnerability occurs when an authenticated user’s browser automatically includes cookies (e.g., the session cookie) alongside a forged request that also contains a valid API key in a header, if the endpoint mistakenly accepts both or relies on the session cookie for authorization rather than the key itself.

Consider an AdonisJS route that updates a user’s email and uses an API key header for authentication but does not validate the key against the intended resource or enforce a same-site/CSRF token:

// routes.ts
import Route from '@ioc:Adonis/Core/Route'

Route.put('/user/email', async ({ request, auth }) => {
  const apiKey = request.header('X-API-Key')
  // Vulnerable: key is retrieved but not tied to the user making the change
  const user = await User.findBy('api_key', apiKey)
  if (!user) return response.unauthorized()

  // If session cookie is also present and auth uses it implicitly,
  // a forged request from a malicious site can succeed if key is leaked or predictable
  await user.merge({ email: request.input('email') }).save()
  return response.ok()
})
In this pattern, if an attacker tricks a user’s browser into submitting a forged request to this endpoint (for example via an image tag or form on a malicious site), the browser sends the user’s session cookie. If AdonisJS’s auth inadvertently falls back to session-based identity, the request is authorized even though the API key alone should be the gatekeeper. Additionally, if the API key is embedded in JavaScript or leaked via logs, a third-party site can forge requests on behalf of the victim with the same key, leading to unauthorized actions such as changing email or password.

The risk is compounded when endpoints do not validate the Origin or Referer headers, and when the API key is treated as a bearer credential without binding to a scope or per-request nonce. AdonisJS applications that expose mutation endpoints via APIs consumed by browsers must ensure that API keys are the sole source of authority for the request and that no session-based authorization fallback exists. Security checks should confirm that the key maps to a specific resource and that the request context (including referrer and content type) is validated to reduce CSRF exposure.

Api Keys-Specific Remediation in Adonisjs

Remediation centers on ensuring API keys are the definitive credential, removing reliance on cookies for authorization, and validating request provenance. Below are concrete steps and code examples for AdonisJS.

1. Enforce API key-only authentication for state-changing routes

Do not allow session cookies to authorize API requests. Configure your route to use an authentication guard that checks only the API key header and does not fall back to sessions.

// start/hooks.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export const auth = {
  onlyApiKey: (ctx: HttpContextContract) => {
    const apiKey = ctx.request.header('X-API-Key')
    if (!apiKey) {
      ctx.response.status = 401
      throw new Error('API key missing')
    }
    const user = User.findBy('api_key', apiKey)
    if (!user) {
      ctx.response.status = 403
      throw new Error('Invalid API key')
    }
    ctx.auth.user = user
  },
}

// routes.ts
import Route from '@ioc:Adonis/Core/Route'
import { auth } from '~/start/hooks'

Route.put('/user/email', auth.onlyApiKey, async ({ request, auth }) => {
  const user = auth.user!
  await user.merge({ email: request.input('email') }).save()
  return response.ok()
})

2. Bind API keys to specific scopes and validate per-request

Ensure each API key is associated with a scope or owner, and that the key matches the resource being modified. This limits the impact of a leaked key and prevents cross-user CSRF-like actions.

// Example: scoped key validation
Route.put('/user/:userId/email', async ({ params, request, response }) => {
  const apiKey = request.header('X-API-Key')
  const keyRecord = await ApiKey.findBy('key', apiKey)
  if (!keyRecord || keyRecord.userId !== params.userId) {
    return response.forbidden()
  }
  // Proceed with update knowing the key is bound to the userId
  const user = await User.findOrFail(params.userId)
  await user.merge({ email: request.input('email') }).save()
  return response.ok()
})

3. Add anti-CSRF headers and strict origin checks

Even for API-only routes, require an X-Requested-With header or an Origin check to block cross-origin forged requests. This complements API key validation and raises the bar for attackers who cannot read the origin due to CORS policies.

Route.put('/user/email', async ({ request, response }) => {
  const apiKey = request.header('X-API-Key')
  const origin = request.header('Origin')
  const xRequestedWith = request.header('X-Requested-With')

  if (!apiKey || !origin || origin !== 'https://trusted.example.com') {
    return response.badRequest({ error: 'Invalid request headers' })
  }
  if (xRequestedWith !== 'XMLHttpRequest') {
    return response.forbidden({ error: 'CSRF protection: missing X-Requested-With' })
  }

  const user = await User.findBy('api_key', apiKey)
  if (!user) return response.unauthorized()

  await user.merge({ email: request.input('email') }).save()
  return response.ok()
})

4. Rotate keys and audit usage

Provide mechanisms to rotate API keys and log usage with user-agent and IP context. This helps detect anomalies that may indicate leaked keys or automated CSRF attempts.

// Rotate key endpoint example
Route.post('/user/rotate-key', auth.onlyApiKey, async ({ auth, response }) => {
  const newKey = crypto.randomBytes(32).toString('hex')
  await ApiKey.query().where('userId', auth.user!.id).update({ key: newKey })
  return response.ok({ apiKey: newKey })
})

Frequently Asked Questions

Does middleBrick detect CSRF vulnerabilities in API-only AdonisJS endpoints?
middleBrick scans unauthenticated attack surfaces and can identify missing anti-CSRF controls and improper API key usage patterns. Findings include whether authentication relies on headers alone and whether origin/referrer validation is absent, with remediation guidance to bind keys to resources and enforce strict header checks.
Can API keys in AdonisJS be used without any CSRF risk?
Yes, if API keys are the sole credential, are validated per request, are bound to a specific resource/scope, and endpoints reject cookies and cross-origin requests without explicit checks. middleBrick’s scans help verify these conditions by testing header presence, origin handling, and fallback behavior.