+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 366 of 541

📘 CORS: Cross-Origin Requests

Master cors: cross-origin requests in Python with practical examples, best practices, and real-world applications 🚀

🚀Intermediate
25 min read

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:

  1. Allows specific origins to access recipes
  2. Requires authentication for adding new recipes
  3. Implements custom CORS headers
  4. 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:

  1. CORS Basics 🛡️ - Understanding cross-origin requests and why they matter
  2. Flask-CORS 🔧 - Using the Flask-CORS extension for easy configuration
  3. Custom Headers 📋 - Adding and managing custom CORS headers
  4. Authentication 🔐 - Securing CORS endpoints with tokens
  5. 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:

  1. Build Secure APIs 🔒 - Create APIs that other websites can safely use
  2. Integrate Third-Party Services 🤝 - Connect your app with external APIs
  3. Implement Microservices 🏗️ - Build distributed systems with proper CORS handling
  4. 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! 🐍✨