Cross Site Request Forgery in Adonisjs with Jwt Tokens
Cross Site Request Forgery in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Cross Site Request Forgery (CSRF) is an attack that tricks a victim into submitting an unwanted request on a web application where they are authenticated. AdonisJS, by default, provides CSRF protection for browser-based sessions using synchronized tokens (e.g., via the csrf middleware and hidden form fields). However, when APIs are protected using JWT tokens and the endpoints are designed for stateless, bearer-token authentication, the CSRF risk profile changes and can be exposed in specific configurations.
In AdonisJS, JWT tokens are typically issued after login and sent by clients in the Authorization: Bearer <token> header. Because this authentication mechanism does not rely on cookies, the browser’s automatic inclusion of credentials (such as session cookies) is not involved. As a result, classic CSRF attacks that exploit cookie-based session authentication are less likely to succeed against pure JWT-protected routes. However, the vulnerability arises when an application mixes cookie-based sessions and JWT authentication, or when frontend JavaScript that holds a JWT is vulnerable to malicious scripts that can trigger authenticated requests via XMLHttpRequest or fetch. If the application exposes endpoints that accept both cookie-based sessions and JWT tokens, or if the frontend is compromised via Cross-Site Scripting (XSS), an attacker can forge requests that the server processes as authenticated actions.
Consider an AdonisJS application that issues a JWT upon login and stores it in localStorage. A developer may create a route that accepts both the JWT in the Authorization header and a session cookie for convenience. If the server does not enforce a strict authentication scheme and the frontend makes requests using credentials (e.g., with credentials: 'include' in fetch), an attacker can craft a malicious page that performs actions on behalf of the user. For example, an endpoint like POST /api/transfer that relies on weak origin checks or missing anti-CSRF tokens can be invoked by a malicious site if the browser includes cookies alongside the JavaScript-injected Authorization header manipulation via a compromised page context.
Additionally, if the application serves authenticated API responses to unauthorized origins without proper CORS and CSRF defenses, the combination of JWT usage and permissive CORS settings can expose unsafe request handling. Attackers may leverage social engineering to get a user to visit a malicious site that triggers authenticated API calls. While JWT itself does not carry the CSRF risk when used strictly as a bearer token in headers, implementation pitfalls such as mixing authentication mechanisms, storing tokens insecurely, and missing origin validation can create conditions where CSRF-like attacks are feasible.
To illustrate, an insecure AdonisJS route might look like this, where the server relies solely on JWT verification but does not validate the request origin or enforce additional CSRF protections for state-changing operations:
// routes.ts
Route.post('/api/transfer', async ({ request, auth }) => {
const user = await auth.authenticate()
const { amount, toAccount } = request.body()
// Process transfer without origin or anti-CSRF token checks
return { status: 'ok' }
}).middleware(['auth:jwt'])
In this scenario, if the frontend application is compromised via XSS or the endpoint is inadvertently exposed to cross-origin requests without proper CORS and CSRF safeguards, an attacker may be able to induce the victim’s browser to send authenticated requests that the server processes as valid. Therefore, the specific combination of AdonisJS and JWT tokens requires careful consideration of authentication mixing, CORS policy, and anti-CSRF measures for any routes that accept state-changing operations from browser contexts.
Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes
Remediation for CSRF concerns in AdonisJS when using JWT tokens focuses on strict authentication handling, origin validation, and anti-CSRF measures for any endpoints that operate in browser contexts. The following code examples demonstrate concrete fixes.
1. Enforce JWT-only authentication and avoid mixing cookies. Ensure that API routes relying on JWT do not also depend on session cookies. Use a dedicated middleware that verifies the JWT from the Authorization header and rejects requests that include session-based cookies for those routes.
// middleware/ensure-jwt-only.ts
import { Exception } from '@poppinss/utils'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class EnsureJwtOnlyMiddleware {
public async handle({ request, auth }: HttpContextContract, next: () => Promise<void>) {
const hasSessionCookie = request.hasCookie('auth_session')
const authorization = request.headers().authorization
if (hasSessionCookie || !authorization?.startsWith('Bearer ')) {
throw new Exception('Unauthenticated', 401)
}
await auth.use('jwt').authenticate()
return next()
}
}
Register this middleware for sensitive routes and apply it in start/routes.ts:
// routes.ts
Route.post('/api/transfer', async ({ request, auth }) => {
const user = await auth.use('jwt').authenticate()
const { amount, toAccount } = request.body()
// Process transfer with user from JWT
return { status: 'ok' }
}).middleware(['ensure-jwt-only'])
2. Add anti-CSRF token validation for browser-originated requests. For endpoints that may be called from browser JavaScript, include a custom header validation that requires a CSRF token, and ensure the token is tied to the user's session or a per-request nonce. This example uses a simple header-based approach:
// middleware/validate-csrf.ts
import { Exception } from '@poppinss/utils'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class ValidateCsrfMiddleware {
public async handle({ request, auth }: HttpContextContract, next: () => Promise<void>) {
const user = await auth.use('jwt').authenticate()
if (!user) return next()
const csrfToken = request.header('x-csrf-token')
const expected = request.session?.get('csrf_token')
if (!csrfToken || csrfToken !== expected) {
throw new Exception('Invalid CSRF token', 403)
}
return next()
}
}
In practice, generate and store a CSRF token in the user’s session upon login and require it in a custom header for state-changing requests. For pure API clients that do not use browser sessions, you may skip this middleware.
3. Configure strict CORS policies. Ensure that CORS settings do not allow unauthorized origins to send credentials or tokens. In AdonisJS, configure CORS to be restrictive for API routes:
// start/cors.ts
export const cors = {
enabled: true,
origin: ['https://your-trusted-frontend.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
headers: ['Authorization', 'Content-Type'],
credentials: false, // do not allow cookies for cross-origin requests
allowHeaders: ['Authorization', 'Content-Type', 'x-csrf-token'],
}
By setting credentials: false, you prevent browsers from automatically including cookies or authorization headers cross-origin, reducing the CSRF surface when JWTs are used in headers.
4. Avoid storing sensitive tokens in insecure storage. Remind frontend developers not to store JWTs in localStorage when the application is vulnerable to XSS. Prefer httpOnly cookies for session tokens when feasible, or ensure strict Content Security Policy (CSP) and input sanitization to mitigate XSS. For AdonisJS APIs that issue JWTs, set secure, httpOnly cookies only when using cookie-based sessions, otherwise transmit tokens via headers and ensure the frontend handles them securely.
These remediation steps—enforcing JWT-only endpoints, validating anti-CSRF tokens for browser contexts, tightening CORS, and securing token storage—reduce the likelihood of CSRF-like issues when using JWT tokens in AdonisJS applications.