Files
attune/web/CORS-TROUBLESHOOTING.md
2026-02-04 17:46:30 -06:00

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?

  1. Avoids CORS issues - Requests appear to come from the same origin
  2. Simpler configuration - No need to configure CORS for every local development scenario
  3. 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 headers
  • CREDENTIALS = "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:

  1. Verify OpenAPI.BASE = "" in web/src/lib/api-config.ts
  2. Don't set VITE_API_BASE_URL environment variable locally

Issue 2: "CORS policy: credentials mode is 'include'"

Cause: WITH_CREDENTIALS mismatch between client and server

Solution:

  1. Ensure OpenAPI.WITH_CREDENTIALS = true (frontend)
  2. Ensure CORS layer has .allow_credentials(true) (backend)
  3. 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 CorsLayer automatically handles OPTIONS requests
  • Verify Method::OPTIONS is 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:

  1. Set production CORS origins:
# config.production.yaml
server:
  cors_origins:
    - "https://app.example.com"
    - "https://www.example.com"
  1. Use environment variables:
export ATTUNE__SERVER__CORS_ORIGINS='["https://app.example.com"]'
  1. 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

  1. Start backend services:
./scripts/start_services_test.sh
  1. Start frontend dev server:
cd web
npm run dev
  1. Access UI:
    • Open browser to http://localhost:3000
    • All API requests automatically proxied
    • No CORS issues!

Alternative: Direct API Access

If you need to access API directly (e.g., testing with curl/Postman):

  1. Set environment variable:
export ATTUNE__SERVER__CORS_ORIGINS='["http://localhost:3000","*"]'
  1. 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 = "" in web/src/lib/api-config.ts?
  • Vite proxy configured for /api and /auth?
  • Backend API running on localhost:8080?
  • Not setting VITE_API_BASE_URL environment variable?
  • Browser console shows requests to localhost:3000, not 8080?

Additional Resources

Summary

For local development:

  • Use Vite proxy (default configuration)
  • Set OpenAPI.BASE = ""
  • Frontend requests go to localhost:3000, proxied to 8080

For production:

  • Use reverse proxy (nginx/caddy/traefik)
  • OR configure explicit CORS origins in config.production.yaml
  • Never use wildcard * origins with credentials