From 8ac8c8c7bd05d6f9ec0e7df2e3c5dabd53c4146f Mon Sep 17 00:00:00 2001 From: NexPB Date: Thu, 9 Jul 2026 08:07:58 +0900 Subject: [PATCH] fix(inertia): send csrf token with visits Inertia v3 replaced axios with its own XHR client, which does not send Phoenix's CSRF token. Any non-GET Inertia visit would 403 on protect_from_forgery. Attach the token from the csrf-token meta tag via http.onRequest, ported from the same fix in NexPB/cloak. Co-Authored-By: Claude Fable 5 --- assets/e2e/demo.spec.ts | 15 +++++++++++++++ assets/src/app.tsx | 3 +++ assets/src/lib/http.ts | 19 +++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 assets/src/lib/http.ts diff --git a/assets/e2e/demo.spec.ts b/assets/e2e/demo.spec.ts index aeca6b6..c471e86 100644 --- a/assets/e2e/demo.spec.ts +++ b/assets/e2e/demo.spec.ts @@ -73,4 +73,19 @@ test.describe('Demo page navigation', () => { // Inertia subsequent navigations are XHR (fetch), not document requests expect(requests.length).toBe(0) }) + + test('Inertia visits carry the Phoenix CSRF token header', async ({ + page, + }) => { + await page.goto('/demo/a') + + const inertiaRequest = page.waitForRequest( + req => req.url().endsWith('/demo/b') && req.resourceType() !== 'document', + ) + + await page.getByRole('button', { name: RE_GO_TO_B }).click() + + const request = await inertiaRequest + expect(request.headers()['x-csrf-token']).toBeTruthy() + }) }) diff --git a/assets/src/app.tsx b/assets/src/app.tsx index fae0bdc..667e3de 100644 --- a/assets/src/app.tsx +++ b/assets/src/app.tsx @@ -2,9 +2,12 @@ import type { ComponentType } from 'react' import { createInertiaApp } from '@inertiajs/react' import { createRoot } from 'react-dom/client' +import { installHttpHeaderInterceptor } from './lib/http' import '../css/app.css' import './theme' +installHttpHeaderInterceptor() + createInertiaApp({ resolve: (name: string) => { const pages = import.meta.glob<{ default: ComponentType }>( diff --git a/assets/src/lib/http.ts b/assets/src/lib/http.ts new file mode 100644 index 0000000..fba89e7 --- /dev/null +++ b/assets/src/lib/http.ts @@ -0,0 +1,19 @@ +// Inertia v3 ships its own XHR client (no more axios). We attach Phoenix's +// CSRF token via the client's request interceptor so every non-GET Inertia +// visit passes protect_from_forgery. +import { http } from '@inertiajs/react' + +export function installHttpHeaderInterceptor(): void { + http.onRequest((config) => { + const csrfToken = document + .querySelector('meta[name="csrf-token"]') + ?.content + + if (csrfToken) { + config.headers = config.headers ?? {} + config.headers['x-csrf-token'] = csrfToken + } + + return config + }) +}