10 KiB
CORS Configuration & Testing Guide
This guide covers Cross-Origin Resource Sharing (CORS) configuration for CodeSnippet when deployed with separate frontend and backend domains.
Understanding CORS
CORS is a security feature that controls which domains can make requests to your backend API. When your frontend (https://frontend.example.com) makes requests to your backend (https://backend.example.com), the browser enforces CORS policies.
Backend CORS Configuration
Environment Variable: CORS_ALLOWED_ORIGINS
The backend Flask application uses the CORS_ALLOWED_ORIGINS environment variable to control which origins can access the API.
Development (Allow All Origins)
CORS_ALLOWED_ORIGINS=*
Warning: Only use * in development. This allows ANY website to access your backend API.
Production (Specific Origins)
# Single origin
CORS_ALLOWED_ORIGINS=https://frontend.example.com
# Multiple origins (comma-separated)
CORS_ALLOWED_ORIGINS=https://frontend.example.com,https://app.example.com,https://staging.example.com
Important: Do NOT include trailing slashes in URLs.
How It Works
The Flask backend (backend/app.py) reads this environment variable and configures CORS accordingly:
ALLOWED_ORIGINS = os.environ.get('CORS_ALLOWED_ORIGINS', '*')
if ALLOWED_ORIGINS == '*':
CORS(app, origins='*', ...) # Development mode
else:
origins_list = [origin.strip() for origin in ALLOWED_ORIGINS.split(',')]
CORS(app, origins=origins_list, ...) # Production mode
CORS Headers Returned
When properly configured, the backend returns these headers:
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Testing CORS Configuration
Test 1: Health Check (No CORS)
Simple GET request to verify backend is running:
curl https://backend.example.com/health
Expected response:
{
"status": "healthy",
"timestamp": "2024-01-01T12:00:00"
}
Test 2: Preflight Request (OPTIONS)
Browsers send an OPTIONS request before the actual request to check CORS permissions:
curl -X OPTIONS https://backend.example.com/api/snippets \
-H "Origin: https://frontend.example.com" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: Content-Type" \
-v
Expected headers in response:
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: https://frontend.example.com
< Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
< Access-Control-Allow-Headers: Content-Type, Authorization
Test 3: Actual API Request with Origin
Test a real API request with Origin header:
curl https://backend.example.com/api/snippets \
-H "Origin: https://frontend.example.com" \
-v
Should return Access-Control-Allow-Origin header and snippet data.
Test 4: Wrong Origin (Should Fail)
Test that unauthorized origins are blocked:
curl https://backend.example.com/api/snippets \
-H "Origin: https://malicious-site.com" \
-v
Expected: No Access-Control-Allow-Origin header (or browser would block).
Test 5: Browser DevTools Test
- Open frontend in browser:
https://frontend.example.com - Open DevTools (F12) → Network tab
- Create a new snippet or load snippets
- Check the API request:
- Should show status 200 OK
- Response headers should include
Access-Control-Allow-Origin - No CORS errors in Console
Common CORS Errors & Solutions
Error: "CORS policy: No 'Access-Control-Allow-Origin' header"
Cause: Backend is not returning CORS headers
Solutions:
- Verify
CORS_ALLOWED_ORIGINSis set correctly in backend - Check backend logs for errors
- Ensure Flask-CORS is installed:
pip install flask-cors - Restart backend after environment variable changes
Error: "CORS policy: Origin 'https://frontend.example.com' not allowed"
Cause: Frontend origin not in allowed list
Solutions:
- Check
CORS_ALLOWED_ORIGINSincludes exact frontend URL - Ensure no trailing slash:
https://frontend.example.comnothttps://frontend.example.com/ - Verify HTTPS vs HTTP matches exactly
- Check for typos in domain name
Error: "CORS policy: Request header 'content-type' not allowed"
Cause: Backend not allowing required headers
Solutions:
- Verify backend allows
Content-Typeheader - Check Flask-CORS configuration in
app.py - Ensure
allow_headersincludesContent-Type
Error: Mixed Content (HTTP/HTTPS)
Cause: Frontend uses HTTPS but backend uses HTTP (or vice versa)
Solutions:
- Ensure both frontend and backend use HTTPS in production
- Update
VITE_FLASK_BACKEND_URLto usehttps:// - Enable HTTPS in CapRover for both apps
- Verify Cloudflare SSL/TLS mode is "Full (strict)"
Error: "CORS policy: Credential is not supported if origin is '*'"
Cause: Using CORS_ALLOWED_ORIGINS=* with supports_credentials=True
Solutions:
- Set specific origins instead of
* - Or disable credentials if not needed
Deployment Scenarios
Scenario 1: Separate Domains (Recommended for Production)
Frontend: https://frontend.example.com
Backend: https://backend.example.com
Frontend Config:
VITE_FLASK_BACKEND_URL=https://backend.example.com
Backend Config:
CORS_ALLOWED_ORIGINS=https://frontend.example.com
Pros: Clean separation, independent scaling Cons: Requires CORS configuration
Scenario 2: Single Domain with Proxy
Frontend: https://app.example.com
Backend: https://app.example.com/api (proxied)
Frontend Config:
VITE_FLASK_BACKEND_URL=/api
Backend Config:
CORS_ALLOWED_ORIGINS=* # Not needed if proxied through nginx
Nginx Config: (already configured in nginx.conf)
location /api {
proxy_pass http://backend:5000;
}
Pros: No CORS issues (same-origin), simpler configuration Cons: Tight coupling, single domain
Scenario 3: Multiple Frontends
Frontend 1: https://app.example.com
Frontend 2: https://staging.example.com
Backend: https://api.example.com
Frontend Config (both):
VITE_FLASK_BACKEND_URL=https://api.example.com
Backend Config:
CORS_ALLOWED_ORIGINS=https://app.example.com,https://staging.example.com
Cloudflare-Specific Configuration
Cloudflare SSL/TLS Mode
Set to "Full (strict)" to ensure end-to-end encryption:
- Cloudflare Dashboard → SSL/TLS → Overview
- Select "Full (strict)"
- Ensures both Cloudflare-to-origin and client-to-Cloudflare use SSL
Cloudflare Always Use HTTPS
- SSL/TLS → Edge Certificates
- Enable "Always Use HTTPS"
- Automatically redirects HTTP to HTTPS
Cloudflare Transform Rules (Optional)
Add security headers using Transform Rules:
Header: Strict-Transport-Security
Value: max-age=31536000; includeSubDomains
Header: X-Content-Type-Options
Value: nosniff
Header: X-Frame-Options
Value: DENY
Automated CORS Testing Script
Save this as test-cors.sh:
#!/bin/bash
FRONTEND_URL="https://frontend.example.com"
BACKEND_URL="https://backend.example.com"
echo "Testing CORS Configuration..."
echo "================================"
echo -e "\n1. Testing Health Endpoint..."
curl -s "$BACKEND_URL/health" | jq .
echo -e "\n2. Testing OPTIONS Preflight..."
curl -X OPTIONS "$BACKEND_URL/api/snippets" \
-H "Origin: $FRONTEND_URL" \
-H "Access-Control-Request-Method: GET" \
-i -s | grep -i "access-control"
echo -e "\n3. Testing GET with Origin..."
curl -s "$BACKEND_URL/api/snippets" \
-H "Origin: $FRONTEND_URL" \
-i | grep -i "access-control"
echo -e "\n4. Testing POST with Origin..."
curl -X POST "$BACKEND_URL/api/snippets" \
-H "Origin: $FRONTEND_URL" \
-H "Content-Type: application/json" \
-d '{"id":"test","title":"Test","code":"test","language":"JavaScript","createdAt":"2024-01-01T00:00:00","updatedAt":"2024-01-01T00:00:00"}' \
-i -s | grep -i "access-control"
echo -e "\nCORS tests complete!"
Make it executable and run:
chmod +x test-cors.sh
./test-cors.sh
Frontend Storage Config Helper
The frontend automatically handles backend configuration through the getStorageConfig() function:
Automatic Configuration
If VITE_FLASK_BACKEND_URL is set, the app automatically uses Flask backend:
// src/lib/storage.ts
function getDefaultConfig(): StorageConfig {
const flaskUrl = import.meta.env.VITE_FLASK_BACKEND_URL
if (flaskUrl) {
return { backend: 'flask', flaskUrl: flaskUrl }
}
return { backend: 'indexeddb' }
}
Manual Configuration
Users can also manually configure in Settings page (if no env var is set).
Debugging Tips
Enable Verbose Logging
Add logging to backend app.py:
import logging
logging.basicConfig(level=logging.DEBUG)
@app.after_request
def after_request(response):
app.logger.debug(f"Response headers: {response.headers}")
return response
Browser DevTools
- Open DevTools (F12)
- Network tab → Enable "Preserve log"
- Filter by "Fetch/XHR"
- Look for OPTIONS and GET/POST requests
- Check Response Headers for
Access-Control-Allow-Origin - Check Console for CORS error messages
CapRover Logs
View real-time backend logs:
# Via CapRover CLI
caprover logs codesnippet-backend --lines 100 --follow
# Via Dashboard
Apps → codesnippet-backend → Logs
Security Best Practices
Production CORS Checklist
- Set specific origins (not
*) - Use HTTPS for all URLs
- Enable Cloudflare proxy (orange cloud)
- Set Cloudflare SSL mode to "Full (strict)"
- Remove trailing slashes from origin URLs
- Test with automated script
- Monitor for CORS errors in production logs
- Document allowed origins
Regular Audits
Periodically review:
- Which origins are allowed
- Whether all origins are still needed
- CORS-related errors in logs
- Unauthorized access attempts