Files
snippet-pastebin/docs/CORS-GUIDE.md
2026-01-17 21:28:10 +00:00

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

  1. Open frontend in browser: https://frontend.example.com
  2. Open DevTools (F12) → Network tab
  3. Create a new snippet or load snippets
  4. 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:

  1. Verify CORS_ALLOWED_ORIGINS is set correctly in backend
  2. Check backend logs for errors
  3. Ensure Flask-CORS is installed: pip install flask-cors
  4. Restart backend after environment variable changes

Error: "CORS policy: Origin 'https://frontend.example.com' not allowed"

Cause: Frontend origin not in allowed list

Solutions:

  1. Check CORS_ALLOWED_ORIGINS includes exact frontend URL
  2. Ensure no trailing slash: https://frontend.example.com not https://frontend.example.com/
  3. Verify HTTPS vs HTTP matches exactly
  4. Check for typos in domain name

Error: "CORS policy: Request header 'content-type' not allowed"

Cause: Backend not allowing required headers

Solutions:

  1. Verify backend allows Content-Type header
  2. Check Flask-CORS configuration in app.py
  3. Ensure allow_headers includes Content-Type

Error: Mixed Content (HTTP/HTTPS)

Cause: Frontend uses HTTPS but backend uses HTTP (or vice versa)

Solutions:

  1. Ensure both frontend and backend use HTTPS in production
  2. Update VITE_FLASK_BACKEND_URL to use https://
  3. Enable HTTPS in CapRover for both apps
  4. 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:

  1. Set specific origins instead of *
  2. Or disable credentials if not needed

Deployment Scenarios

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:

  1. Cloudflare Dashboard → SSL/TLS → Overview
  2. Select "Full (strict)"
  3. Ensures both Cloudflare-to-origin and client-to-Cloudflare use SSL

Cloudflare Always Use HTTPS

  1. SSL/TLS → Edge Certificates
  2. Enable "Always Use HTTPS"
  3. 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

  1. Open DevTools (F12)
  2. Network tab → Enable "Preserve log"
  3. Filter by "Fetch/XHR"
  4. Look for OPTIONS and GET/POST requests
  5. Check Response Headers for Access-Control-Allow-Origin
  6. 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:

  1. Which origins are allowed
  2. Whether all origins are still needed
  3. CORS-related errors in logs
  4. Unauthorized access attempts

Additional Resources