7.1 KiB
CORS Troubleshooting Guide
This guide explains how CORS is configured in Attune and how to troubleshoot common issues.
Architecture Overview
Attune uses a proxy-based architecture for local development:
Browser (localhost:3000) → Vite Dev Server → Proxy → API Server (localhost:8080)
Why Use a Proxy?
- Avoids CORS issues - Requests appear to come from the same origin
- Simpler configuration - No need to configure CORS for every local development scenario
- Production-ready - In production, use a reverse proxy (nginx/caddy) the same way
Current Configuration
Frontend (Vite Proxy)
File: web/vite.config.ts
server: {
port: 3000,
proxy: {
"/api": {
target: "http://localhost:8080",
changeOrigin: true,
},
"/auth": {
target: "http://localhost:8080",
changeOrigin: true,
},
},
}
What this does:
- Requests to
http://localhost:3000/api/*→http://localhost:8080/api/* - Requests to
http://localhost:3000/auth/*→http://localhost:8080/auth/*
Frontend API Client
File: web/src/lib/api-config.ts
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "";
OpenAPI.BASE = API_BASE_URL;
OpenAPI.WITH_CREDENTIALS = true;
OpenAPI.CREDENTIALS = "include";
Key settings:
BASE = ""- Makes requests relative (uses proxy)WITH_CREDENTIALS = true- Sends cookies/auth headersCREDENTIALS = "include"- Include credentials in cross-origin requests
Backend CORS Configuration
File: crates/api/src/middleware/cors.rs
Default allowed origins (when no custom origins configured):
http://localhost:3000
http://localhost:5173
http://localhost:8080
http://127.0.0.1:3000
http://127.0.0.1:5173
http://127.0.0.1:8080
Allowed methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
Allowed headers: Authorization, Content-Type, Accept
Credentials: Enabled (allow_credentials(true))
Common Issues & Solutions
Issue 1: "CORS policy: No 'Access-Control-Allow-Origin' header"
Cause: Frontend making direct requests to http://localhost:8080 instead of using proxy
Solution:
// ❌ Wrong - bypasses proxy
const response = await fetch('http://localhost:8080/auth/login', ...);
// ✅ Correct - uses proxy
const response = await fetch('/auth/login', ...);
Check:
- Verify
OpenAPI.BASE = ""inweb/src/lib/api-config.ts - Don't set
VITE_API_BASE_URLenvironment variable locally
Issue 2: "CORS policy: credentials mode is 'include'"
Cause: WITH_CREDENTIALS mismatch between client and server
Solution:
- Ensure
OpenAPI.WITH_CREDENTIALS = true(frontend) - Ensure CORS layer has
.allow_credentials(true)(backend) - Cannot use
allow_origin(Any)with credentials - must specify origins
Issue 3: OPTIONS preflight request failing
Cause: Browser sends OPTIONS request before actual request, server doesn't handle it
Solution:
- Axum's
CorsLayerautomatically handles OPTIONS requests - Verify
Method::OPTIONSis in.allow_methods() - Check server logs for OPTIONS requests
Issue 4: Custom frontend port not working
Cause: Your dev server runs on a different port (e.g., 5173 instead of 3000)
Solution:
Option A: Update Vite config to use port 3000:
server: {
port: 3000,
// ...
}
Option B: Add your port to backend CORS origins:
# config.development.yaml
server:
cors_origins:
- "http://localhost:5173"
- "http://localhost:3000"
Or set environment variable:
export ATTUNE__SERVER__CORS_ORIGINS='["http://localhost:5173"]'
Issue 5: Production deployment CORS errors
Cause: Production frontend domain not in allowed origins
Solution:
- Set production CORS origins:
# config.production.yaml
server:
cors_origins:
- "https://app.example.com"
- "https://www.example.com"
- Use environment variables:
export ATTUNE__SERVER__CORS_ORIGINS='["https://app.example.com"]'
- Or use a reverse proxy (recommended):
- Frontend and backend served from same domain
- No CORS needed!
Testing CORS Configuration
Test 1: Verify Proxy is Working
# Start dev server
cd web
npm run dev
# In another terminal, test proxy
curl -v http://localhost:3000/auth/login
Should show request proxied to backend.
Test 2: Check Allowed Origins
# Test CORS preflight
curl -X OPTIONS http://localhost:8080/auth/login \
-H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: POST" \
-v
Look for these headers in response:
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
Test 3: Actual Login Request
curl -X POST http://localhost:8080/auth/login \
-H "Origin: http://localhost:3000" \
-H "Content-Type: application/json" \
-d '{"login":"admin","password":"admin"}' \
-v
Check for Access-Control-Allow-Origin in response.
Development Workflow
Recommended Setup
- Start backend services:
./scripts/start_services_test.sh
- Start frontend dev server:
cd web
npm run dev
- Access UI:
- Open browser to
http://localhost:3000 - All API requests automatically proxied
- No CORS issues!
- Open browser to
Alternative: Direct API Access
If you need to access API directly (e.g., testing with curl/Postman):
- Set environment variable:
export ATTUNE__SERVER__CORS_ORIGINS='["http://localhost:3000","*"]'
- Restart API service
⚠️ Warning: Never use "*" in production!
Environment Variables Reference
# Allow all origins (DEVELOPMENT ONLY!)
export ATTUNE__SERVER__CORS_ORIGINS='["*"]'
# Allow specific origins
export ATTUNE__SERVER__CORS_ORIGINS='["http://localhost:3000","http://localhost:5173"]'
# Frontend: Use direct API access (bypasses proxy)
export VITE_API_BASE_URL='http://localhost:8080'
# Frontend: Use proxy (default, recommended)
# Don't set VITE_API_BASE_URL, or set to empty string
export VITE_API_BASE_URL=''
Quick Fix Checklist
If you're getting CORS errors, check these in order:
- Frontend dev server running on
localhost:3000? OpenAPI.BASE = ""inweb/src/lib/api-config.ts?- Vite proxy configured for
/apiand/auth? - Backend API running on
localhost:8080? - Not setting
VITE_API_BASE_URLenvironment variable? - Browser console shows requests to
localhost:3000, not8080?
Additional Resources
Summary
For local development:
- Use Vite proxy (default configuration)
- Set
OpenAPI.BASE = "" - Frontend requests go to
localhost:3000, proxied to8080
For production:
- Use reverse proxy (nginx/caddy/traefik)
- OR configure explicit CORS origins in
config.production.yaml - Never use wildcard
*origins with credentials