diff --git a/client/index.html b/client/index.html
index b895e24..1351ca3 100644
--- a/client/index.html
+++ b/client/index.html
@@ -3,7 +3,6 @@
-
NeuraMemory AI
diff --git a/client/src/App.tsx b/client/src/App.tsx
index 9188900..6bc8186 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -21,7 +21,13 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
.catch(() => setStatus('unauth'));
}, []);
- if (status === 'loading') return null;
+ if (status === 'loading') {
+ return (
+
+ );
+ }
if (status === 'unauth') return ;
return <>{children}>;
}
diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts
index b10b8fc..7d6df25 100644
--- a/client/src/lib/api.ts
+++ b/client/src/lib/api.ts
@@ -4,3 +4,20 @@ export const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
withCredentials: true,
});
+
+// Automatically attach the CSRF token from cookie to state-changing requests.
+// The server sets `csrf_token` cookie on first load (readable by JS since httpOnly=false).
+api.interceptors.request.use((config) => {
+ const method = config.method?.toUpperCase();
+ const isStateChanging = ['POST', 'PUT', 'DELETE', 'PATCH'].includes(method ?? '');
+ if (isStateChanging) {
+ const csrfToken = document.cookie
+ .split('; ')
+ .find((row) => row.startsWith('csrf_token='))
+ ?.split('=')?.[1];
+ if (csrfToken) {
+ config.headers['x-csrf-token'] = csrfToken;
+ }
+ }
+ return config;
+});
diff --git a/server/src/index.ts b/server/src/index.ts
index 9e27e8f..f579fd2 100644
--- a/server/src/index.ts
+++ b/server/src/index.ts
@@ -43,7 +43,7 @@ app.use(
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
- allowedHeaders: ['Content-Type', 'Authorization', 'x-api-key', 'mcp-session-id'],
+ allowedHeaders: ['Content-Type', 'Authorization', 'x-api-key', 'mcp-session-id', 'x-csrf-token'],
}),
);