Cross Site Request Forgery in Adonisjs with Mutual Tls
Cross Site Request Forgery in Adonisjs with Mutual Tls
Cross Site Request Forgery (CSRF) in AdonisJS when Mutual TLS (mTLS) is used involves a nuanced interaction between transport-layer client authentication and application-layer authorization. AdonisJS does not provide built-in CSRF protection for APIs because APIs typically rely on token-based authentication (e.g., bearer tokens) rather than cookie-based sessions. However, when mTLS is enforced at the edge or load balancer and the application uses session-based auth (e.g., via cookies), CSRF can still manifest if requests rely on cookies for identity while lacking anti-CSRF tokens.
With mTLS, the client presents a certificate during the TLS handshake, and the server validates it. This strongly authenticates the client to the transport layer. If AdonisJS routes assume that a valid mTLS certificate alone is sufficient for authorization, the application might inadvertently treat requests from any browser that possesses a valid client certificate as legitimate, even if the request originates from a malicious site. For example, a banking API endpoint that uses mTLS for client authentication and also accepts session cookies for legacy web clients could be vulnerable: a malicious site could embed a form that triggers a state-changing request (e.g., fund transfer) from the user’s browser. If the user’s browser has a valid client certificate and an active session, the request would succeed because AdonisJS sees a valid mTLS cert and an authenticated session, but the request was not intentionally initiated by the user.
In practice, mTLS ensures the client is who they claim to be at the connection level, but it does not bind the request to a specific origin. Browsers will automatically include client certificates when the server requests them, and they will also include session cookies if the domain and path match. This combination means an attacker can craft a request that arrives with both a valid mTLS certificate (if the attacker can trick the user into connecting via a network that enforces mTLS, which is rare) and valid cookies. More commonly, the risk arises in internal or hybrid setups where mTLS is used for service-to-service communication and AdonisJS endpoints are exposed to browsers. For instance, an AdonisJS service acting as an API gateway might validate mTLS from a frontend service while also serving a web UI that uses cookies. If the API does not verify the Origin or Referer headers and does not require CSRF tokens for state-changing methods, an attacker could trick a user into submitting a request that the gateway honors.
The OWASP API Security Top 10 categorizes this as a Broken Object Level Authorization (BOLA) risk when authorization relies solely on identifiers without verifying intent. Even with mTLS, each request must be evaluated for proper authorization and origin validation. AdonisJS applications should treat mTLS as a strong client authentication mechanism but not as a replacement for CSRF protections in browser-based contexts. For APIs consumed by browsers, use anti-CSRF tokens or SameSite cookie attributes. For purely mTLS-authenticated APIs consumed by non-browser clients, ensure strict origin validation and avoid relying on cookies.
Mutual Tls-Specific Remediation in Adonisjs
To securely implement mTLS in AdonisJS and mitigate CSRF-like risks, you must combine transport-level client validation with explicit per-request authorization. Below are concrete code examples showing how to configure mTLS in AdonisJS using the built-in HTTP server and how to validate client certificates within your routes.
First, configure the AdonisJS server to request and validate client certificates. In your start/server.ts (or the relevant server entry point), set up the HTTPS server with certificate verification:
import { HttpServer } from '@poppinss/dev-utils'
import { Application } from '@adonisjs/core/types'
export default class ServerProvider {
constructor(protected app: Application) {}
public register() {
this.app.container.singleton('server', () => {
const server = new HttpServer(this.app)
server.ssl = {
key: '/path/to/server.key',
cert: '/path/to/server.crt',
ca: '/path/to/ca.crt',
requestCert: true,
rejectUnauthorized: true,
}
return server
})
}
}
This configuration ensures that the server requests a client certificate and rejects connections where the certificate is not signed by the trusted CA. The requestCert and rejectUnauthorized options enforce strict mTLS.
Next, in your route handlers, validate the client certificate to ensure it meets your authorization criteria. You can access the certificate from the request socket:
import { HttpContextContract } from '@adonisjs/core/http'
export default async function handleSecureRequest({ request }: HttpContextContract) {
const socket = request.socket as any
const cert = socket.getPeerCertificate()
if (!cert || Object.keys(cert).length === 0) {
throw new Error('Client certificate required')
}
// Validate certificate fields (e.g., subject, serial, or custom extensions)
const fingerprint = cert.fingerprint
const allowedFingerprints = new Set(['aa:bb:cc:...', 'dd:ee:ff:...'])
if (!allowedFingerprints.has(fingerprint)) {
throw new Error('Unauthorized client certificate')
}
// Proceed with business logic
return { message: 'Authenticated via mTLS', fingerprint }
}
This approach decouples transport authentication from application authorization. Even with a valid certificate, you still verify it against an allowlist and perform additional checks (e.g., certificate extensions or OCSP). To prevent CSRF in browser contexts where cookies are also used, combine this with anti-CSRF tokens or set SameSite=Strict on cookies. For service-to-service APIs using mTLS, ensure that each service validates the peer certificate and that requests include additional identifiers (e.g., an API key in a header) to avoid accidental authorization based solely on certificate presence.