Prerequisites
- Basic understanding of programming concepts 📝
- Python installation (3.8+) 🐍
- VS Code or preferred IDE 💻
What you'll learn
- Understand the concept fundamentals 🎯
- Apply the concept in real projects 🏗️
- Debug common issues 🐛
- Write clean, Pythonic code ✨
📘 CORS: Cross-Origin Requests
🎯 Introduction
Have you ever tried to make your Python web app talk to another website and got a mysterious error? 🤔 That’s probably CORS saying “Hold up! Let me check if this is allowed!” Today, we’re going to demystify CORS and learn how to handle cross-origin requests like a pro! 🚀
CORS (Cross-Origin Resource Sharing) is like a security guard at a concert 🎵 - it checks if you have the right ticket (permissions) before letting you in. It’s a crucial concept for modern web development, and by the end of this tutorial, you’ll be handling cross-origin requests with confidence! 💪
📚 Understanding CORS
What is CORS? 🤷♂️
CORS is a browser security feature that controls which websites can access resources from other websites. Think of it like this:
- Your website = Your house 🏠
- Another website = Your neighbor’s house 🏘️
- CORS = The fence between houses with gates 🚪
Without CORS, any website could reach into another website’s data - chaos! 😱
The Same-Origin Policy 🛡️
By default, browsers follow the “Same-Origin Policy”:
# Same origin - these can talk freely! ✅
website_a = "https://mysite.com/api/users"
website_b = "https://mysite.com/api/products"
# Different origins - CORS needed! ⚠️
my_site = "https://mysite.com"
other_site = "https://othersite.com"
🔧 Basic Syntax and Usage
Let’s start with a simple Flask example to handle CORS:
from flask import Flask, jsonify
from flask_cors import CORS
# Create our app 🎨
app = Flask(__name__)
# Enable CORS - the magic happens here! ✨
CORS(app)
@app.route('/api/greeting')
def get_greeting():
return jsonify({
'message': 'Hello from Python! 👋',
'cors': 'enabled'
})
if __name__ == '__main__':
app.run(debug=True)
Installing Required Packages 📦
pip install flask flask-cors
💡 Practical Examples
Example 1: Building a Weather API Service 🌤️
Let’s create a weather API that other websites can use:
from flask import Flask, jsonify, request
from flask_cors import CORS, cross_origin
import random
app = Flask(__name__)
# Configure CORS with specific settings
cors = CORS(app, resources={
r"/api/*": {
"origins": ["http://localhost:3000", "https://myweatherapp.com"],
"methods": ["GET", "POST"],
"allow_headers": ["Content-Type", "Authorization"]
}
})
# Our weather data 🌡️
weather_data = {
'New York': {'temp': 72, 'condition': 'Sunny ☀️'},
'London': {'temp': 59, 'condition': 'Rainy 🌧️'},
'Tokyo': {'temp': 68, 'condition': 'Cloudy ☁️'},
'Sydney': {'temp': 77, 'condition': 'Partly Cloudy 🌤️'}
}
@app.route('/api/weather/<city>')
def get_weather(city):
# Check if we have data for this city 🏙️
if city in weather_data:
return jsonify({
'city': city,
'weather': weather_data[city],
'timestamp': 'Just now! ⏰'
})
else:
return jsonify({'error': 'City not found! 😕'}), 404
@app.route('/api/weather', methods=['POST'])
@cross_origin(origins=['http://localhost:3000']) # Specific route CORS
def add_weather():
data = request.get_json()
city = data.get('city')
temp = data.get('temperature')
condition = data.get('condition')
# Add new weather data 📊
weather_data[city] = {'temp': temp, 'condition': condition}
return jsonify({
'message': f'Weather data added for {city}! 🎉',
'data': weather_data[city]
}), 201
Example 2: Creating a Quote API with Custom CORS Headers 📚
from flask import Flask, jsonify, make_response
from flask_cors import CORS
import random
from datetime import datetime
app = Flask(__name__)
# Custom CORS configuration
app.config['CORS_HEADERS'] = 'Content-Type'
# Quotes database 💭
quotes = [
{'text': 'Code is poetry! 🎨', 'author': 'Python Developer'},
{'text': 'Debug with joy! 🐛', 'author': 'Happy Coder'},
{'text': 'Keep calm and import this 🐍', 'author': 'Pythonista'},
{'text': 'Errors are just features in disguise! 🎭', 'author': 'Optimist Dev'}
]
@app.route('/api/quote/random')
def get_random_quote():
quote = random.choice(quotes)
# Create response with custom headers 📮
response = make_response(jsonify({
'quote': quote,
'timestamp': datetime.now().isoformat(),
'api_version': '1.0'
}))
# Add custom CORS headers manually
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
response.headers['Access-Control-Max-Age'] = '3600'
return response
@app.route('/api/quotes/all')
def get_all_quotes():
return jsonify({
'quotes': quotes,
'count': len(quotes),
'message': 'Enjoy these inspiring quotes! 🌟'
})
# Handle preflight requests 🛫
@app.before_request
def handle_preflight():
if request.method == "OPTIONS":
response = make_response()
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
return response
Example 3: Building a Secure API with Authentication 🔐
from flask import Flask, jsonify, request
from flask_cors import CORS
from functools import wraps
import jwt
import datetime
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here-🔑'
# Configure CORS for authenticated requests
CORS(app, supports_credentials=True, resources={
r"/api/*": {
"origins": ["http://localhost:3000"],
"methods": ["GET", "POST", "PUT", "DELETE"],
"allow_headers": ["Content-Type", "Authorization"],
"expose_headers": ["Authorization"]
}
})
# Mock user database 👥
users = {
'[email protected]': {'password': 'pass123', 'name': 'Alice'},
'[email protected]': {'password': 'pass456', 'name': 'Bob'}
}
# Authentication decorator 🎖️
def token_required(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization')
if not token:
return jsonify({'message': 'Token missing! 🚫'}), 401
try:
# Remove 'Bearer ' prefix
token = token.replace('Bearer ', '')
data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
current_user = data['user']
except:
return jsonify({'message': 'Invalid token! ❌'}), 401
return f(current_user, *args, **kwargs)
return decorated
@app.route('/api/login', methods=['POST'])
def login():
data = request.get_json()
email = data.get('email')
password = data.get('password')
# Check credentials 🔍
if email in users and users[email]['password'] == password:
# Generate token 🎟️
token = jwt.encode({
'user': email,
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)
}, app.config['SECRET_KEY'], algorithm='HS256')
return jsonify({
'token': token,
'message': f'Welcome back, {users[email]["name"]}! 🎉'
})
return jsonify({'message': 'Invalid credentials! 😞'}), 401
@app.route('/api/profile')
@token_required
def get_profile(current_user):
return jsonify({
'email': current_user,
'name': users[current_user]['name'],
'message': 'Your profile data! 📋'
})
@app.route('/api/protected-data')
@token_required
def get_protected_data(current_user):
return jsonify({
'data': 'This is super secret data! 🤫',
'user': current_user,
'timestamp': datetime.datetime.now().isoformat()
})
🚀 Advanced Concepts
Custom CORS Middleware 🛠️
from flask import Flask, request, make_response
from functools import wraps
def custom_cors_middleware(allowed_origins=None, allowed_methods=None):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# Get the origin of the request
origin = request.headers.get('Origin')
# Create the response
response = make_response(f(*args, **kwargs))
# Check if origin is allowed 🔍
if allowed_origins:
if origin in allowed_origins or '*' in allowed_origins:
response.headers['Access-Control-Allow-Origin'] = origin
else:
response.headers['Access-Control-Allow-Origin'] = '*'
# Set allowed methods
if allowed_methods:
response.headers['Access-Control-Allow-Methods'] = ', '.join(allowed_methods)
# Set other CORS headers
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
response.headers['Access-Control-Allow-Credentials'] = 'true'
return response
return decorated_function
return decorator
# Usage example 🎯
app = Flask(__name__)
@app.route('/api/custom-cors')
@custom_cors_middleware(
allowed_origins=['http://localhost:3000', 'https://myapp.com'],
allowed_methods=['GET', 'POST']
)
def custom_endpoint():
return jsonify({
'message': 'Custom CORS middleware in action! 🎪',
'cors': 'Customized just for you!'
})
Dynamic CORS Configuration 🔄
from flask import Flask, jsonify, request
from flask_cors import CORS
import os
app = Flask(__name__)
# Dynamic CORS based on environment 🌍
def get_cors_origins():
env = os.getenv('ENVIRONMENT', 'development')
if env == 'production':
return ['https://myapp.com', 'https://app.myapp.com']
elif env == 'staging':
return ['https://staging.myapp.com']
else:
return ['http://localhost:3000', 'http://localhost:8080']
# Apply dynamic CORS
CORS(app, origins=get_cors_origins())
# CORS configuration endpoint 📊
@app.route('/api/cors-config')
def get_cors_config():
return jsonify({
'environment': os.getenv('ENVIRONMENT', 'development'),
'allowed_origins': get_cors_origins(),
'message': 'Current CORS configuration! 🔧'
})
⚠️ Common Pitfalls and Solutions
❌ Wrong: Allowing all origins in production
# Don't do this in production! 😱
CORS(app, origins='*') # Too permissive!
✅ Right: Specific origin whitelist
# Much better! 🛡️
CORS(app, origins=[
'https://myapp.com',
'https://app.myapp.com',
'https://admin.myapp.com'
])
❌ Wrong: Forgetting preflight requests
# This won't handle OPTIONS requests properly 😕
@app.route('/api/data', methods=['POST'])
def post_data():
return jsonify({'status': 'ok'})
✅ Right: Handling preflight requests
# Handle both POST and OPTIONS! 🎯
@app.route('/api/data', methods=['POST', 'OPTIONS'])
def post_data():
if request.method == 'OPTIONS':
# Handle preflight
response = make_response()
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Methods'] = 'POST'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
return response
# Handle actual request
return jsonify({'status': 'ok'})
🛠️ Best Practices
1. Use Environment-Specific Configuration 🌍
import os
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
# Environment-based CORS configuration
cors_config = {
'development': {
'origins': ['http://localhost:3000'],
'supports_credentials': True
},
'production': {
'origins': ['https://myapp.com'],
'supports_credentials': True,
'send_wildcard': False
}
}
env = os.getenv('FLASK_ENV', 'development')
CORS(app, **cors_config.get(env, cors_config['development']))
2. Implement Request Validation 🔍
from flask import request, abort
def validate_origin(f):
@wraps(f)
def decorated_function(*args, **kwargs):
origin = request.headers.get('Origin')
allowed_origins = ['https://myapp.com', 'https://trusted-site.com']
if origin not in allowed_origins:
abort(403, 'Origin not allowed! 🚫')
return f(*args, **kwargs)
return decorated_function
@app.route('/api/sensitive-data')
@validate_origin
def get_sensitive_data():
return jsonify({'data': 'Super sensitive info! 🔐'})
3. Log CORS Requests 📝
import logging
from flask import request
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@app.before_request
def log_cors_request():
origin = request.headers.get('Origin', 'No origin')
method = request.method
path = request.path
logger.info(f'CORS Request: {method} {path} from {origin} 📡')
@app.after_request
def log_cors_response(response):
if 'Access-Control-Allow-Origin' in response.headers:
logger.info(f'CORS Response: Allowed origin {response.headers["Access-Control-Allow-Origin"]} ✅')
return response
🧪 Hands-On Exercise
Time to put your CORS knowledge to the test! 🎯
Challenge: Create a Recipe API that:
- Allows specific origins to access recipes
- Requires authentication for adding new recipes
- Implements custom CORS headers
- Handles preflight requests properly
Try it yourself first! 💪
💡 Click here for the solution
from flask import Flask, jsonify, request, make_response
from flask_cors import CORS
from functools import wraps
import jwt
import datetime
app = Flask(__name__)
app.config['SECRET_KEY'] = 'super-secret-recipe-key-🗝️'
# Configure CORS
CORS(app, resources={
r"/api/recipes/*": {
"origins": ["http://localhost:3000", "https://myrecipeapp.com"],
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_headers": ["Content-Type", "Authorization"],
"supports_credentials": True
}
})
# Recipe database 🍳
recipes = [
{
'id': 1,
'name': 'Python Pancakes 🥞',
'ingredients': ['flour', 'eggs', 'milk', 'love'],
'difficulty': 'easy'
},
{
'id': 2,
'name': 'Django Donuts 🍩',
'ingredients': ['flour', 'sugar', 'framework magic'],
'difficulty': 'medium'
}
]
# Simple auth decorator
def auth_required(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization')
if not token:
return jsonify({'message': 'No token provided! 🔐'}), 401
try:
token = token.replace('Bearer ', '')
data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
except:
return jsonify({'message': 'Invalid token! ❌'}), 401
return f(*args, **kwargs)
return decorated
# Handle preflight requests globally
@app.before_request
def handle_preflight():
if request.method == "OPTIONS":
response = make_response()
origin = request.headers.get('Origin')
# Check if origin is allowed
allowed_origins = ["http://localhost:3000", "https://myrecipeapp.com"]
if origin in allowed_origins:
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Access-Control-Max-Age'] = '3600'
return response
@app.route('/api/recipes', methods=['GET'])
def get_recipes():
# Add custom headers to response
response = jsonify({
'recipes': recipes,
'count': len(recipes),
'message': 'Bon appétit! 🍽️'
})
# Custom CORS headers
response.headers['X-Recipe-API-Version'] = '1.0'
response.headers['X-Total-Recipes'] = str(len(recipes))
return response
@app.route('/api/recipes/<int:recipe_id>', methods=['GET'])
def get_recipe(recipe_id):
recipe = next((r for r in recipes if r['id'] == recipe_id), None)
if recipe:
return jsonify({
'recipe': recipe,
'message': 'Enjoy cooking! 👨🍳'
})
return jsonify({'error': 'Recipe not found! 😞'}), 404
@app.route('/api/recipes', methods=['POST'])
@auth_required
def add_recipe():
data = request.get_json()
# Create new recipe
new_recipe = {
'id': len(recipes) + 1,
'name': data.get('name'),
'ingredients': data.get('ingredients', []),
'difficulty': data.get('difficulty', 'medium')
}
recipes.append(new_recipe)
return jsonify({
'recipe': new_recipe,
'message': 'Recipe added successfully! 🎉'
}), 201
@app.route('/api/auth/login', methods=['POST'])
def login():
# Simple login for demo
data = request.get_json()
if data.get('username') == 'chef' and data.get('password') == 'cook123':
token = jwt.encode({
'user': 'chef',
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}, app.config['SECRET_KEY'], algorithm='HS256')
return jsonify({
'token': token,
'message': 'Welcome to the kitchen, Chef! 👨🍳'
})
return jsonify({'error': 'Invalid credentials! 🚫'}), 401
if __name__ == '__main__':
app.run(debug=True, port=5000)
🎓 Key Takeaways
You’ve mastered CORS in Python! Here’s what you learned:
- CORS Basics 🛡️ - Understanding cross-origin requests and why they matter
- Flask-CORS 🔧 - Using the Flask-CORS extension for easy configuration
- Custom Headers 📋 - Adding and managing custom CORS headers
- Authentication 🔐 - Securing CORS endpoints with tokens
- Best Practices ⭐ - Environment-specific configs and security measures
Remember:
- CORS is about security, not annoyance! 🛡️
- Always be specific with allowed origins in production 🎯
- Test your CORS configuration thoroughly 🧪
- Keep your API documentation updated 📚
🤝 Next Steps
Congratulations on conquering CORS! 🎉 You’re now ready to:
- Build Secure APIs 🔒 - Create APIs that other websites can safely use
- Integrate Third-Party Services 🤝 - Connect your app with external APIs
- Implement Microservices 🏗️ - Build distributed systems with proper CORS handling
- Deploy to Production 🚀 - Configure CORS for real-world applications
In our next tutorial, we’ll explore GraphQL Alternative to REST - taking your API game to the next level! Get ready to learn a modern approach to building flexible APIs! 🎯
Keep coding, keep learning, and remember - CORS is your friend, not your enemy! 🌟
Happy coding! 🐍✨