All POST/PUT/DELETE/PATCH requests will fail with 403 "CSRF token required"
The frontend currently makes dozens of mutation requests without CSRF tokens:
- Login/logout (App.tsx)
- Password changes (ChangePasswordModal.tsx)
- User creation/deletion (UsersTab.tsx)
- Settings updates (SettingsTab.tsx)
- Traceroute interval (SettingsContext.tsx)
- Telemetry favorites (TelemetryGraphs.tsx, Dashboard.tsx)
- Auto-acknowledge toggle (AutoAcknowledgeSection.tsx)
- Auto-announce toggle (AutoAnnounceSection.tsx)
- Auto-traceroute settings (AutoTracerouteSection.tsx)
- Send messages (services/api.ts - sendMessage)
- Request traceroutes (services/api.ts - requestTraceroute)
- Configuration updates (ConfigurationTab.tsx)
- Reboot device (services/api.ts - rebootDevice)
- Delete nodes (services/api.ts - deleteNode)
- Clear old messages/telemetry/traceroutes (services/api.ts)
Total Impact: ~30+ different API endpoints will break
File: src/App.tsx
Add to the useEffect that checks authentication:
useEffect(() => {
const initializeAuth = async () => {
try {
// Fetch CSRF token
const csrfResponse = await fetch('/api/csrf-token', {
credentials: 'include'
});
const { csrfToken } = await csrfResponse.json();
// Store token (in state, context, or localStorage)
sessionStorage.setItem('csrfToken', csrfToken);
// Then check auth status
const response = await fetch('/api/auth/status', {
credentials: 'include'
});
// ... rest of auth check
} catch (error) {
console.error('Failed to initialize:', error);
}
};
initializeAuth();
}, []);Option A: Create a wrapper function in src/services/api.ts:
// Add at top of api.ts
const getCsrfToken = () => sessionStorage.getItem('csrfToken') || '';
// Create wrapper for authenticated fetch
async function fetchWithCsrf(url: string, options: RequestInit = {}) {
const csrfToken = getCsrfToken();
return fetch(url, {
...options,
credentials: 'include',
headers: {
...options.headers,
'X-CSRF-Token': csrfToken,
},
});
}Option B: Create an Axios instance with interceptor:
import axios from 'axios';
const api = axios.create({
baseURL: '/api',
withCredentials: true,
});
api.interceptors.request.use((config) => {
const csrfToken = sessionStorage.getItem('csrfToken');
if (csrfToken) {
config.headers['X-CSRF-Token'] = csrfToken;
}
return config;
});Add error handling for 403 CSRF errors:
// In fetch wrapper or Axios interceptor
if (response.status === 403 && error.message.includes('CSRF')) {
// Refresh token
const csrfResponse = await fetch('/api/csrf-token', {
credentials: 'include'
});
const { csrfToken } = await csrfResponse.json();
sessionStorage.setItem('csrfToken', csrfToken);
// Retry original request
return fetchWithCsrf(url, options);
}-
GET Requests: All read operations work fine
- View dashboard
- View nodes
- View messages/telemetry
- View settings
- SSE streams
-
Authentication Status Check:
/api/auth/statusis exempt from CSRF -
Rate Limiting:
- Development: 10,000 req/15min (effectively unlimited)
- Production: 1,000 req/15min (~1 req/sec) - adequate for real-time app
-
CORS: Properly configured with whitelist
-
Security Headers: All working without breaking functionality
- Development: No HSTS, no upgrade-insecure-requests (HTTP works)
- Production: Full HSTS, upgrade-insecure-requests (HTTPS required)
- Implement CSRF token fetching in App initialization
- Create
fetchWithCsrfwrapper or Axios instance - Update all POST/PUT/DELETE/PATCH requests to use wrapper
- Add CSRF error handling with retry logic
- Test all mutation operations
Files to Update:
src/App.tsx- CSRF initializationsrc/services/api.ts- Add wrapper function- All components making mutations (see list above)
Environment Variables:
# REQUIRED in production - app will fail to start without this
SESSION_SECRET=$(openssl rand -hex 32)
# Required for CORS
ALLOWED_ORIGINS=https://your-domain.com
# Required for secure cookies
COOKIE_SECURE=true
NODE_ENV=production
# If behind reverse proxy (nginx, Caddy, etc.)
TRUST_PROXY=1- Build frontend with CSRF support
- Deploy to dev environment
- Test ALL mutation operations:
- Login/logout
- Send message
- Update settings
- Toggle automations
- Configure device
- Delete nodes
- Request traceroute
- Change password
- Set all required environment variables
- Deploy backend with security fixes
- Deploy frontend with CSRF support
- Verify HTTPS is working
- Test critical workflows
Frontend CSRF Integration: 4-6 hours
- 1 hour: Set up CSRF token management
- 2-3 hours: Update all fetch calls
- 1-2 hours: Testing and debugging
Backend Testing: 1-2 hours
- Verify all endpoints work with CSRF
- Test rate limiting under load
- Verify security headers
Total: 1 working day for complete integration
If issues occur in production:
-
Quick Fix: Temporarily disable CSRF protection
// In src/server/server.ts // Comment out CSRF middleware // app.use(csrfTokenMiddleware);
-
Better Fix: Make CSRF optional via environment variable
if (process.env.ENABLE_CSRF !== 'false') { app.use(csrfTokenMiddleware); apiRouter.get('/csrf-token', csrfTokenEndpoint); }
-
Proper Fix: Complete frontend integration (recommended)
Rate Limiting:
- ✅ Allows SSE/polling
- ✅ Prevents brute force
- ✅ Won't block legitimate users
⚠️ Could allow low-rate DoS
CSRF Protection:
- ✅ Prevents cross-site attacks
- ✅ Session-based (no cookies to manage)
⚠️ Requires frontend integration
CORS:
- ✅ Whitelist-based
- ✅ Configurable per environment
⚠️ Must configure ALLOWED_ORIGINS
-
Deploy backend WITHOUT frontend changes to staging first
- Test what breaks
- Verify rate limits are acceptable
- Check security headers
-
Add CSRF token support to frontend in parallel
- Can develop/test against staging
- Deploy when ready
-
Consider phased rollout
- Deploy security headers first (non-breaking)
- Deploy rate limiting second (mostly non-breaking)
- Deploy CSRF last (breaking, requires frontend)
- Application loads over HTTP
- No SSL protocol errors
- Can send messages without rate limiting
- SSE streams work continuously
- Telemetry polling works
- Application loads over HTTPS
- All mutation operations work with CSRF
- Rate limits are acceptable under normal use
- Security headers present (verify with securityheaders.com)
- CORS allows only intended origins
Watch for:
-
403 CSRF errors in logs
- Indicates missing CSRF integration
- Check
logger.warnmessages in csrf.ts
-
429 Rate Limit errors
- May need to adjust production limits
- Check if legitimate users being blocked
-
CORS errors
- Verify ALLOWED_ORIGINS is correct
- Check for blocked origins in logs
-
Session issues
- Verify SESSION_SECRET is set and stable
- Check cookie settings match environment
After deployment, update:
- README.md: Add CSRF token requirement
- API Documentation: Document X-CSRF-Token header requirement
- Development Guide: How to work with CSRF in dev
- Deployment Guide: Environment variable requirements
Bottom Line: The backend is production-ready with all security fixes. The frontend needs CSRF token integration (1 day of work) before deploying to production. Everything else works as expected.