mirror of
https://github.com/johndoe6345789/snippet-pastebin.git
synced 2026-04-24 13:34:55 +00:00
410 lines
10 KiB
Markdown
410 lines
10 KiB
Markdown
# 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)
|
|
|
|
```bash
|
|
CORS_ALLOWED_ORIGINS=*
|
|
```
|
|
|
|
**Warning:** Only use `*` in development. This allows ANY website to access your backend API.
|
|
|
|
#### Production (Specific Origins)
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```python
|
|
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:
|
|
|
|
```bash
|
|
curl https://backend.example.com/health
|
|
```
|
|
|
|
Expected response:
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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
|
|
|
|
### Scenario 1: Separate Domains (Recommended for Production)
|
|
|
|
```
|
|
Frontend: https://frontend.example.com
|
|
Backend: https://backend.example.com
|
|
```
|
|
|
|
**Frontend Config:**
|
|
```bash
|
|
VITE_FLASK_BACKEND_URL=https://backend.example.com
|
|
```
|
|
|
|
**Backend Config:**
|
|
```bash
|
|
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:**
|
|
```bash
|
|
VITE_FLASK_BACKEND_URL=/api
|
|
```
|
|
|
|
**Backend Config:**
|
|
```bash
|
|
CORS_ALLOWED_ORIGINS=* # Not needed if proxied through nginx
|
|
```
|
|
|
|
**Nginx Config:** (already configured in `nginx.conf`)
|
|
```nginx
|
|
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):**
|
|
```bash
|
|
VITE_FLASK_BACKEND_URL=https://api.example.com
|
|
```
|
|
|
|
**Backend Config:**
|
|
```bash
|
|
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`:
|
|
|
|
```bash
|
|
#!/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:
|
|
```bash
|
|
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:
|
|
|
|
```typescript
|
|
// 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`:
|
|
|
|
```python
|
|
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:
|
|
|
|
```bash
|
|
# 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
|
|
|
|
- [MDN CORS Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
|
|
- [Flask-CORS Documentation](https://flask-cors.readthedocs.io/)
|
|
- [Cloudflare SSL/TLS Documentation](https://developers.cloudflare.com/ssl/)
|
|
- [CapRover Environment Variables](https://caprover.com/docs/app-configuration.html)
|