Flask CORS Configuration
Cross-Origin Resource Sharing (CORS) is a crucial security mechanism that restricts how resources on a web page can be requested from another domain. In this tutorial, we'll explore how to configure CORS in Flask applications to ensure your APIs are accessible only to authorized domains.
What is CORS?
CORS (Cross-Origin Resource Sharing) is a security feature implemented by browsers that restricts web pages from making requests to a different domain than the one that served the original page. This is known as the "Same-Origin Policy" and helps prevent malicious websites from accessing sensitive data on other sites.
However, there are legitimate scenarios where you want to allow cross-origin requests, such as:
- Building a public API that serves multiple domains
- Creating a microservices architecture where services communicate across domains
- Developing a frontend separate from your API backend
Why Configure CORS in Flask?
By default, Flask doesn't implement CORS protection. This means your Flask API could potentially be accessed by any website, which poses security risks. Configuring CORS in Flask allows you to:
- Specify which domains can access your API
- Control which HTTP methods are allowed (GET, POST, etc.)
- Manage whether credentials (cookies, HTTP authentication) can be included in requests
- Set how long browsers should cache CORS responses
Using Flask-CORS Extension
The easiest way to implement CORS in Flask is using the flask-cors
extension. Let's see how to install and use it:
Installation
pip install flask-cors
Basic Usage
Here's a simple example of implementing CORS in a Flask application:
from flask import Flask, jsonify
from flask_cors import CORS
app = Flask(__name__)
# Enable CORS for all routes
CORS(app)
@app.route('/api/data')
def get_data():
return jsonify({"message": "This endpoint can be accessed from any origin"})
if __name__ == '__main__':
app.run(debug=True)
When you run this application and make a request from a different domain, the browser will receive the appropriate CORS headers that allow the request to succeed.
Configuring CORS Options
In real-world applications, you'll want finer control over CORS settings. Here are common configurations:
Restricting to Specific Origins
from flask import Flask, jsonify
from flask_cors import CORS
app = Flask(__name__)
# Allow CORS only for specific origins
CORS(app, resources={
r"/api/*": {"origins": ["https://example.com", "https://subdomain.example.com"]}
})
@app.route('/api/protected-data')
def get_protected_data():
return jsonify({"message": "This endpoint can only be accessed from allowed origins"})
if __name__ == '__main__':
app.run(debug=True)
Configuring Specific Routes
You can apply different CORS settings to different routes:
from flask import Flask, jsonify
from flask_cors import CORS
app = Flask(__name__)
# Public API - allow all origins
CORS(app, resources={r"/api/public/*": {"origins": "*"}})
# Private API - restrict to specific origins
CORS(app, resources={r"/api/private/*": {"origins": "https://example.com"}})
# Internal API - no CORS (same-origin only)
# No CORS configuration for /api/internal/*
@app.route('/api/public/data')
def get_public_data():
return jsonify({"message": "Public API - accessible from anywhere"})
@app.route('/api/private/data')
def get_private_data():
return jsonify({"message": "Private API - restricted access"})
@app.route('/api/internal/data')
def get_internal_data():
return jsonify({"message": "Internal API - same origin only"})
if __name__ == '__main__':
app.run(debug=True)
Advanced CORS Configuration
For more complex scenarios, you can set additional CORS options:
from flask import Flask, jsonify
from flask_cors import CORS
app = Flask(__name__)
# Advanced CORS configuration
CORS(app, resources={r"/api/*": {
"origins": ["https://example.com"], # Allowed origins
"methods": ["GET", "POST", "PUT"], # Allowed methods
"allow_headers": ["Content-Type", "Authorization"], # Allowed headers
"max_age": 3600, # Cache CORS response for 1 hour
"supports_credentials": True # Allow cookies to be sent
}})
@app.route('/api/user', methods=['GET', 'POST', 'PUT'])
def user_endpoint():
return jsonify({"message": "User endpoint with custom CORS configuration"})
if __name__ == '__main__':
app.run(debug=True)
Implementing CORS Per-Route with Decorators
You can also apply CORS settings on a per-route basis using decorators:
from flask import Flask, jsonify
from flask_cors import cross_origin
app = Flask(__name__)
@app.route('/api/default')
def default_endpoint():
# Uses global CORS settings (or none if not specified)
return jsonify({"message": "Default CORS settings"})
@app.route('/api/custom')
@cross_origin(origins=["https://special-client.com"], methods=["GET"])
def custom_cors_endpoint():
# This route has its own CORS settings
return jsonify({"message": "Custom CORS settings for this route only"})
if __name__ == '__main__':
app.run(debug=True)
Testing CORS Configuration
To test your CORS configuration, you can use tools like curl
or create a simple HTML page with JavaScript fetch:
<!DOCTYPE html>
<html>
<head>
<title>CORS Test</title>
</head>
<body>
<h1>Testing CORS Configuration</h1>
<button onclick="testCORS()">Test API Access</button>
<div id="result"></div>
<script>
function testCORS() {
fetch('http://localhost:5000/api/data', {
method: 'GET',
credentials: 'include' // Include cookies if needed
})
.then(response => response.json())
.then(data => {
document.getElementById('result').innerText =
'Success! Response: ' + JSON.stringify(data);
})
.catch(error => {
document.getElementById('result').innerText =
'Error: ' + error.message;
});
}
</script>
</body>
</html>
Save this as a file and open it in your browser. When you click the button, you'll see whether your CORS configuration allows the request.
Real-World Example: Secure API with CORS
Here's a more complete example of a Flask API with proper CORS configuration:
from flask import Flask, jsonify, request
from flask_cors import CORS
import os
app = Flask(__name__)
# Environment-based configuration
if os.environ.get('FLASK_ENV') == 'development':
# During development, allow requests from localhost
CORS(app, resources={r"/api/*": {
"origins": ["http://localhost:3000", "http://127.0.0.1:3000"]
}})
else:
# In production, restrict to specific domains
CORS(app, resources={r"/api/*": {
"origins": ["https://production-frontend.example.com"],
"methods": ["GET", "POST", "PUT", "DELETE"],
"allow_headers": ["Content-Type", "Authorization"],
"supports_credentials": True
}})
# Public endpoints accessible by anyone
CORS(app, resources={r"/api/public/*": {"origins": "*"}})
@app.route('/api/user/profile', methods=['GET'])
def get_user_profile():
# Protected endpoint - only accessible from allowed origins
# Authentication logic would go here
user_id = request.args.get('id', 'default')
return jsonify({
"id": user_id,
"name": "John Doe",
"email": "[email protected]"
})
@app.route('/api/public/status', methods=['GET'])
def get_api_status():
# Public endpoint - accessible from anywhere
return jsonify({
"status": "operational",
"version": "1.0.0"
})
if __name__ == '__main__':
app.run(debug=True)
Security Considerations
When configuring CORS, keep these security best practices in mind:
- Avoid using
origins: "*"
in production unless you're building a truly public API - Only allow necessary HTTP methods for each endpoint
- Be cautious with
supports_credentials: True
as it allows cookies to be sent cross-origin - Regularly review and update your CORS configuration as your application evolves
- Use environment variables to manage different CORS settings in development vs. production
Common CORS Issues and Solutions
Preflight Requests
For complex requests (with custom headers, non-simple methods), browsers send a preflight OPTIONS request:
@app.route('/api/data', methods=['GET', 'POST', 'OPTIONS'])
@cross_origin(origins=["https://example.com"])
def handle_data():
# The OPTIONS method is handled automatically by flask-cors
if request.method == 'GET':
return jsonify({"message": "Data retrieved"})
elif request.method == 'POST':
return jsonify({"message": "Data created"})
Missing Headers
If you're still experiencing CORS errors, check that all necessary headers are being set:
from flask import Flask, jsonify, make_response
app = Flask(__name__)
@app.route('/api/manual-cors')
def manual_cors():
response = make_response(jsonify({"message": "Manual CORS response"}))
response.headers.add('Access-Control-Allow-Origin', 'https://example.com')
response.headers.add('Access-Control-Allow-Methods', 'GET')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type')
return response
Summary
Configuring CORS properly in your Flask application is essential for building secure web services. In this tutorial, we've covered:
- What CORS is and why it's important
- How to install and use the
flask-cors
extension - Configuring CORS for specific origins, routes, and methods
- Using decorators for per-route CORS settings
- Testing CORS configurations
- Security best practices for CORS implementation
By implementing proper CORS settings, you can ensure your Flask APIs are accessible to legitimate clients while maintaining security against cross-site scripting attacks.
Additional Resources
Exercises
- Create a Flask application with two endpoints: one accessible from any origin and another restricted to a single domain.
- Modify a Flask API to allow CORS only for specific HTTP methods (GET and POST, but not PUT or DELETE).
- Configure a Flask application with different CORS settings for development and production environments.
- Build a simple frontend that tests accessing your CORS-protected API from a different domain.
- Implement proper error handling for CORS preflight requests that fail validation.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)