CORS on Flask

⚠️ Security Warning: Using Access-Control-Allow-Origin: * allows any website to access your resources. Always specify exact origins in production.

The recommended approach is to use the Flask-CORS Python package, which handles all CORS edge cases automatically.

Installation

$ pip install -U flask-cors

Method 1: Specific Origin (Recommended)

Allow a single trusted origin:

# app.py
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app, origins=["https://example.com"])

@app.route("/api/data")
def get_data():
    return {"message": "CORS enabled"}

if __name__ == "__main__":
    app.run()

Method 2: Multiple Origins

Allow multiple specific origins (issue #146):

# app.py
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

# Allow multiple specific origins
cors = CORS(app, origins=[
    "https://example.com",
    "https://app.example.com",
    "https://admin.example.com"
])

@app.route("/api/data")
def get_data():
    return {"message": "CORS enabled"}

Method 3: With Credentials

When using cookies or authentication:

# app.py
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

# Enable credentials (cookies, authorization headers)
cors = CORS(
    app,
    origins=["https://example.com"],
    supports_credentials=True
)

@app.route("/api/secure")
def secure_endpoint():
    return {"message": "Authenticated request"}

Method 4: Route-Specific CORS

Apply CORS to specific routes only:

# app.py
from flask import Flask
from flask_cors import CORS, cross_origin

app = Flask(__name__)

# Public endpoint with CORS
@app.route("/api/public")
@cross_origin(origins=["*"])
def public_api():
    return {"message": "Public endpoint"}

# Private endpoint with restricted CORS
@app.route("/api/private")
@cross_origin(
    origins=["https://example.com"],
    supports_credentials=True
)
def private_api():
    return {"message": "Private endpoint"}

# No CORS
@app.route("/internal")
def internal_api():
    return {"message": "Internal only"}

Method 5: Advanced Configuration

Customize all CORS options:

# app.py
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

cors_config = {
    "origins": ["https://example.com", "https://app.example.com"],
    "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    "allow_headers": ["Content-Type", "Authorization", "X-Requested-With"],
    "expose_headers": ["Content-Length", "X-Custom-Header"],
    "supports_credentials": True,
    "max_age": 86400
}

CORS(app, resources={r"/api/*": cors_config})

@app.route("/api/data")
def get_data():
    return {"message": "CORS enabled with custom config"}

Method 6: Dynamic Origin Validation

Validate origins programmatically:

# app.py
from flask import Flask, request
from flask_cors import CORS

app = Flask(__name__)

def check_origin(origin):
    """Custom origin validation logic"""
    allowed_origins = [
        "https://example.com",
        "https://app.example.com"
    ]
    return origin in allowed_origins

CORS(
    app,
    origins=check_origin,
    supports_credentials=True
)

@app.route("/api/data")
def get_data():
    return {"message": "CORS with dynamic validation"}

Method 7: Manual Implementation (Without Package)

If you prefer not to use Flask-CORS, implement manually:

# app.py
from flask import Flask, request, make_response

app = Flask(__name__)

ALLOWED_ORIGINS = [
    "https://example.com",
    "https://app.example.com"
]

@app.after_request
def add_cors_headers(response):
    origin = request.headers.get('Origin')

    if origin in ALLOWED_ORIGINS:
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Access-Control-Allow-Credentials'] = 'true'
        response.headers['Vary'] = 'Origin'

    return response

@app.route("/api/data", methods=['GET', 'POST', 'OPTIONS'])
def api_data():
    # Handle preflight
    if request.method == 'OPTIONS':
        response = make_response('', 204)
        response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
        response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
        response.headers['Access-Control-Max-Age'] = '86400'
        return response

    return {"message": "CORS enabled"}

Running the Development Server

Run the development server:

$ flask run
# or
$ python app.py

Note: The Flask-CORS package automatically handles preflight OPTIONS requests, validates origins, and includes the Vary: Origin header when needed. For full details read the Flask-CORS package documentation.

Production Tip: Never use CORS(app) without parameters in production. Always explicitly specify allowed origins to prevent security vulnerabilities.

Testing Your CORS Configuration

For comprehensive testing instructions including curl commands, browser DevTools usage, and troubleshooting common CORS errors, see the CORS Testing Guide.

Who’s behind this

Monsur Hossain and Michael Hausenblas

Contribute

The content on this site stays fresh thanks to help from users like you! If you have suggestions or would like to contribute, fork us on GitHub.

Buy the book

Save 39% on CORS in Action with promotional code hossainco at manning.com/hossain