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
|
||||
|
||||
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:
|
||||
- http://localhost
|
||||
@ -200,17 +200,43 @@ The API has CORS (Cross-Origin Resource Sharing) enabled with the following conf
|
||||
- * (wildcard for development)
|
||||
|
||||
- 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
|
||||
- Exposed headers: Content-Length, Content-Type
|
||||
- Credentials support: Enabled
|
||||
- Max age for preflight requests: 600 seconds (10 minutes)
|
||||
- Allowed 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-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
|
||||
|
||||
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
|
||||
|
||||
| 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
|
||||
USE_CUSTOM_CORS_ONLY: bool = False
|
||||
USE_CUSTOM_CORS_ONLY: bool = True
|
||||
|
||||
# Security settings
|
||||
PASSWORD_HASH_ROUNDS: int = 12
|
||||
|
84
main.py
84
main.py
@ -19,45 +19,69 @@ class CustomCORSMiddleware(BaseHTTPMiddleware):
|
||||
if origin in settings.CORS_ORIGINS:
|
||||
return True
|
||||
|
||||
# Wildcard match
|
||||
# Wildcard match - if "*" is in the allowed origins list
|
||||
if "*" in settings.CORS_ORIGINS:
|
||||
return True
|
||||
|
||||
# Check for pattern matching (e.g., https://*.vercel.app)
|
||||
for allowed_origin in settings.CORS_ORIGINS:
|
||||
if "*" in allowed_origin:
|
||||
pattern = allowed_origin.replace("*", "")
|
||||
if origin.startswith(pattern.split("*")[0]) and origin.endswith(pattern.split("*")[-1]):
|
||||
return True
|
||||
if "*" in allowed_origin and not allowed_origin == "*":
|
||||
pattern_parts = allowed_origin.split("*")
|
||||
if len(pattern_parts) == 2:
|
||||
if origin.startswith(pattern_parts[0]) and origin.endswith(pattern_parts[1]):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
origin = request.headers.get("origin", "")
|
||||
|
||||
# Always respond to OPTIONS requests directly for preflight handling
|
||||
if request.method == "OPTIONS":
|
||||
# Handle preflight requests
|
||||
response = Response()
|
||||
# Create a new response for preflight
|
||||
response = Response(status_code=204) # No content needed for preflight
|
||||
|
||||
# If no origin or not allowed, return 204 with minimal headers
|
||||
# This will not block the request but won't allow CORS either
|
||||
if not origin or not self.is_origin_allowed(origin):
|
||||
return response
|
||||
|
||||
# If origin is allowed, set the full CORS headers
|
||||
response.headers["Access-Control-Allow-Origin"] = origin
|
||||
|
||||
# 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
|
||||
|
||||
# Check if the origin is allowed
|
||||
if self.is_origin_allowed(origin):
|
||||
response.headers["Access-Control-Allow-Origin"] = origin
|
||||
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS, PATCH"
|
||||
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"
|
||||
response.headers["Access-Control-Max-Age"] = "600"
|
||||
response.status_code = 200
|
||||
|
||||
return response
|
||||
|
||||
# Process regular requests
|
||||
# For regular requests, process normally then add CORS headers
|
||||
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):
|
||||
# Set required CORS headers
|
||||
response.headers["Access-Control-Allow-Origin"] = origin
|
||||
response.headers["Access-Control-Allow-Credentials"] = "true"
|
||||
|
||||
# Add Vary header to indicate caching should consider Origin
|
||||
response.headers["Vary"] = "Origin"
|
||||
|
||||
return response
|
||||
|
||||
app = FastAPI(
|
||||
@ -92,5 +116,29 @@ app.include_router(api_router)
|
||||
async def health_check():
|
||||
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__":
|
||||
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|
Loading…
x
Reference in New Issue
Block a user