Fix CORS configuration to allow requests from Vercel frontend
This commit is contained in:
parent
2b5afe103b
commit
1f546e5189
40
README.md
40
README.md
@ -188,7 +188,7 @@ The application uses SQLite as the database. The database file is created at `/a
|
|||||||
|
|
||||||
## CORS Configuration
|
## CORS Configuration
|
||||||
|
|
||||||
The API has CORS (Cross-Origin Resource Sharing) enabled with the following configuration:
|
The API has robust CORS (Cross-Origin Resource Sharing) enabled with the following configuration:
|
||||||
|
|
||||||
- Allowed origins:
|
- Allowed origins:
|
||||||
- http://localhost
|
- http://localhost
|
||||||
@ -200,17 +200,43 @@ The API has CORS (Cross-Origin Resource Sharing) enabled with the following conf
|
|||||||
- * (wildcard for development)
|
- * (wildcard for development)
|
||||||
|
|
||||||
- Allowed methods: GET, POST, PUT, DELETE, OPTIONS, PATCH
|
- Allowed methods: GET, POST, PUT, DELETE, OPTIONS, PATCH
|
||||||
- Allowed headers: Content-Type, Authorization, Accept, Origin, X-Requested-With, X-CSRF-Token, Access-Control-Allow-Credentials
|
- Allowed headers:
|
||||||
- Exposed headers: Content-Length, Content-Type
|
- Authorization
|
||||||
- Credentials support: Enabled
|
- Content-Type
|
||||||
- Max age for preflight requests: 600 seconds (10 minutes)
|
- Accept
|
||||||
|
- Accept-Language
|
||||||
|
- Content-Language
|
||||||
|
- Content-Length
|
||||||
|
- Origin
|
||||||
|
- X-Requested-With
|
||||||
|
- X-CSRF-Token
|
||||||
|
- Access-Control-Allow-Origin
|
||||||
|
- Access-Control-Allow-Credentials
|
||||||
|
- X-HTTP-Method-Override
|
||||||
|
|
||||||
|
- Exposed headers: Content-Length, Content-Type, Authorization
|
||||||
|
- Credentials support: Enabled (supports JWT authentication)
|
||||||
|
- Max age for preflight requests: 3600 seconds (1 hour)
|
||||||
|
|
||||||
### Custom CORS Handling
|
### Custom CORS Handling
|
||||||
|
|
||||||
This application uses both FastAPI's built-in CORSMiddleware and a custom CORS middleware that provides enhanced handling of preflight OPTIONS requests. The custom middleware also supports wildcard pattern matching for origins (e.g., https://*.vercel.app) to better support deployment platforms.
|
This application implements a custom CORS middleware that properly handles preflight OPTIONS requests for all endpoints, including authentication routes. The middleware includes:
|
||||||
|
|
||||||
|
1. Direct handling of OPTIONS requests for all endpoints
|
||||||
|
2. Proper header handling for preflight responses
|
||||||
|
3. Explicit support for POST requests with JSON content-type
|
||||||
|
4. Full support for Authorization headers for authenticated endpoints
|
||||||
|
5. Pattern matching for wildcard domains (e.g., *.vercel.app)
|
||||||
|
|
||||||
|
### CORS Test Endpoint
|
||||||
|
|
||||||
|
The API includes a special endpoint for testing CORS functionality:
|
||||||
|
|
||||||
|
- `OPTIONS /api/v1/cors-test` - Test preflight requests
|
||||||
|
- `POST /api/v1/cors-test` - Test POST requests with JSON body
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
| Variable | Description | Default |
|
| Variable | Description | Default |
|
||||||
|----------|-------------|---------|
|
|----------|-------------|---------|
|
||||||
| USE_CUSTOM_CORS_ONLY | Whether to use only the custom CORS middleware | False |
|
| USE_CUSTOM_CORS_ONLY | Whether to use only the custom CORS middleware | True |
|
@ -41,7 +41,7 @@ class Settings(BaseSettings):
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Whether to use only the custom CORS middleware
|
# Whether to use only the custom CORS middleware
|
||||||
USE_CUSTOM_CORS_ONLY: bool = False
|
USE_CUSTOM_CORS_ONLY: bool = True
|
||||||
|
|
||||||
# Security settings
|
# Security settings
|
||||||
PASSWORD_HASH_ROUNDS: int = 12
|
PASSWORD_HASH_ROUNDS: int = 12
|
||||||
|
82
main.py
82
main.py
@ -19,45 +19,69 @@ class CustomCORSMiddleware(BaseHTTPMiddleware):
|
|||||||
if origin in settings.CORS_ORIGINS:
|
if origin in settings.CORS_ORIGINS:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Wildcard match
|
# Wildcard match - if "*" is in the allowed origins list
|
||||||
if "*" in settings.CORS_ORIGINS:
|
if "*" in settings.CORS_ORIGINS:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check for pattern matching (e.g., https://*.vercel.app)
|
# Check for pattern matching (e.g., https://*.vercel.app)
|
||||||
for allowed_origin in settings.CORS_ORIGINS:
|
for allowed_origin in settings.CORS_ORIGINS:
|
||||||
if "*" in allowed_origin:
|
if "*" in allowed_origin and not allowed_origin == "*":
|
||||||
pattern = allowed_origin.replace("*", "")
|
pattern_parts = allowed_origin.split("*")
|
||||||
if origin.startswith(pattern.split("*")[0]) and origin.endswith(pattern.split("*")[-1]):
|
if len(pattern_parts) == 2:
|
||||||
return True
|
if origin.startswith(pattern_parts[0]) and origin.endswith(pattern_parts[1]):
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def dispatch(self, request: Request, call_next):
|
async def dispatch(self, request: Request, call_next):
|
||||||
origin = request.headers.get("origin", "")
|
origin = request.headers.get("origin", "")
|
||||||
|
|
||||||
|
# Always respond to OPTIONS requests directly for preflight handling
|
||||||
if request.method == "OPTIONS":
|
if request.method == "OPTIONS":
|
||||||
# Handle preflight requests
|
# Create a new response for preflight
|
||||||
response = Response()
|
response = Response(status_code=204) # No content needed for preflight
|
||||||
|
|
||||||
# Check if the origin is allowed
|
# If no origin or not allowed, return 204 with minimal headers
|
||||||
if self.is_origin_allowed(origin):
|
# This will not block the request but won't allow CORS either
|
||||||
response.headers["Access-Control-Allow-Origin"] = origin
|
if not origin or not self.is_origin_allowed(origin):
|
||||||
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS, PATCH"
|
return response
|
||||||
response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization, Accept, Origin, X-Requested-With, X-CSRF-Token, Access-Control-Allow-Credentials"
|
|
||||||
response.headers["Access-Control-Allow-Credentials"] = "true"
|
# If origin is allowed, set the full CORS headers
|
||||||
response.headers["Access-Control-Max-Age"] = "600"
|
response.headers["Access-Control-Allow-Origin"] = origin
|
||||||
response.status_code = 200
|
|
||||||
|
# Include all possible headers that might be used by the frontend
|
||||||
|
# Make sure Content-Type is included to support application/json
|
||||||
|
response.headers["Access-Control-Allow-Headers"] = (
|
||||||
|
"Authorization, Content-Type, Accept, Accept-Language, " +
|
||||||
|
"Content-Language, Content-Length, Origin, X-Requested-With, " +
|
||||||
|
"X-CSRF-Token, Access-Control-Allow-Origin, Access-Control-Allow-Credentials, " +
|
||||||
|
"X-Requested-With, X-HTTP-Method-Override"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Expose headers that frontend might need to access
|
||||||
|
response.headers["Access-Control-Expose-Headers"] = (
|
||||||
|
"Content-Length, Content-Type, Authorization"
|
||||||
|
)
|
||||||
|
|
||||||
|
response.headers["Access-Control-Allow-Credentials"] = "true"
|
||||||
|
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS, PATCH"
|
||||||
|
response.headers["Access-Control-Max-Age"] = "3600" # 1 hour cache
|
||||||
|
response.status_code = 200 # OK for successful preflight
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
# Process regular requests
|
# For regular requests, process normally then add CORS headers
|
||||||
response = await call_next(request)
|
response = await call_next(request)
|
||||||
|
|
||||||
# Set CORS headers for the response
|
# Add CORS headers to all responses if origin is allowed
|
||||||
if self.is_origin_allowed(origin):
|
if self.is_origin_allowed(origin):
|
||||||
|
# Set required CORS headers
|
||||||
response.headers["Access-Control-Allow-Origin"] = origin
|
response.headers["Access-Control-Allow-Origin"] = origin
|
||||||
response.headers["Access-Control-Allow-Credentials"] = "true"
|
response.headers["Access-Control-Allow-Credentials"] = "true"
|
||||||
|
|
||||||
|
# Add Vary header to indicate caching should consider Origin
|
||||||
|
response.headers["Vary"] = "Origin"
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
@ -92,5 +116,29 @@ app.include_router(api_router)
|
|||||||
async def health_check():
|
async def health_check():
|
||||||
return {"status": "healthy"}
|
return {"status": "healthy"}
|
||||||
|
|
||||||
|
# CORS test endpoint
|
||||||
|
@app.options("/api/v1/cors-test", tags=["cors"])
|
||||||
|
async def cors_preflight_test():
|
||||||
|
"""Test endpoint for CORS preflight requests."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
@app.post("/api/v1/cors-test", tags=["cors"])
|
||||||
|
async def cors_test(request: Request):
|
||||||
|
"""Test endpoint for CORS POST requests with JSON."""
|
||||||
|
try:
|
||||||
|
body = await request.json()
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "CORS is working correctly for POST requests with JSON",
|
||||||
|
"received_data": body,
|
||||||
|
"headers": dict(request.headers)
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": f"Error: {str(e)}",
|
||||||
|
"headers": dict(request.headers)
|
||||||
|
}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|
Loading…
x
Reference in New Issue
Block a user