Skip to content

Fix: Flask CORS Not Working

FixDevs ·

Quick Answer

How to fix CORS errors in Flask — installing flask-cors correctly, handling preflight OPTIONS requests, configuring origins with credentials, route-specific CORS, and debugging missing headers.

The Error

A browser request to your Flask API fails with a CORS error:

Access to fetch at 'http://localhost:5000/api/data' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on
the requested resource.

Or after installing flask-cors, CORS still doesn’t work for some routes:

Access to XMLHttpRequest at 'http://localhost:5000/api/users' from origin 'http://localhost:3000'
has been blocked by CORS policy: Response to preflight request doesn't pass access control
check: It does not have HTTP ok status.

Or with credentials:

The value of the 'Access-Control-Allow-Origin' header in the response must not be
the wildcard '*' when the request's credentials mode is 'include'.

Why This Happens

Flask doesn’t add CORS headers by default. The flask-cors extension handles this, but misconfiguration is common:

  • flask-cors not installed or not initializedCORS(app) was not called, so no headers are added.
  • CORS(app) called after route definitions — Flask extension registration order matters for some setups.
  • Preflight OPTIONS not handled — for requests with custom headers or methods like PUT/DELETE, browsers send an OPTIONS request first. If Flask returns 405 for OPTIONS, the actual request is never sent.
  • Wildcard origin with credentialsorigins='*' and supports_credentials=True together are invalid per the CORS spec. When sending cookies, you must specify exact origins.
  • Blueprint-level CORS misconfiguredCORS(app) doesn’t always cover blueprints added after initialization without explicit configuration.
  • Route returns an error (4xx/5xx) — if the route handler throws before returning, Flask might return a response without CORS headers, so the browser sees both the status error and the CORS error.

Fix 1: Install and Initialize flask-cors Correctly

pip install flask-cors
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

# Initialize CORS before registering routes
CORS(app)

@app.route('/api/data')
def get_data():
    return {'data': 'hello'}

if __name__ == '__main__':
    app.run(debug=True)

CORS(app) with no arguments allows all origins (*) for all routes. Test this first, then restrict to specific origins in production.

Verify the headers are present:

curl -I -X OPTIONS http://localhost:5000/api/data \
  -H "Origin: http://localhost:3000" \
  -H "Access-Control-Request-Method: GET"

The response should include Access-Control-Allow-Origin: *.

Fix 2: Restrict to Specific Origins

In production, never use *. Specify the exact allowed origins:

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

# Single origin
CORS(app, origins=['https://app.example.com'])

# Multiple origins
CORS(app, origins=[
    'https://app.example.com',
    'https://staging.example.com',
    'http://localhost:3000',  # Dev only
])

Or use environment-based configuration:

import os
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

allowed_origins = os.environ.get(
    'CORS_ORIGINS',
    'http://localhost:3000'
).split(',')

CORS(app, origins=allowed_origins)

Set CORS_ORIGINS=https://app.example.com in production and CORS_ORIGINS=http://localhost:3000 in development.

Fix 3: Handle Credentials (Cookies and Authorization Headers)

When the browser sends credentials: 'include' (for cookies) or an Authorization header, you must:

  1. Set supports_credentials=True
  2. Specify exact origins (not *)
CORS(app,
     origins=['https://app.example.com', 'http://localhost:3000'],
     supports_credentials=True)

This sets both Access-Control-Allow-Origin: https://app.example.com (the actual request origin) and Access-Control-Allow-Credentials: true.

Client-side (JavaScript):

// With cookies
fetch('http://localhost:5000/api/profile', {
  credentials: 'include',
});

// With Authorization header
fetch('http://localhost:5000/api/profile', {
  headers: { Authorization: `Bearer ${token}` },
});

Warning: Do not use origins='*' with supports_credentials=True — this combination violates the CORS spec and browsers will reject the response. Always specify exact origins when using credentials.

Fix 4: Configure Route-Specific CORS

Apply different CORS settings to different routes:

from flask import Flask
from flask_cors import CORS, cross_origin

app = Flask(__name__)

# Global CORS (no credentials, all origins)
CORS(app)

# Route-level override using decorator
@app.route('/api/public')
@cross_origin()  # Allows all origins for this route
def public_endpoint():
    return {'data': 'public'}

@app.route('/api/private')
@cross_origin(origins=['https://app.example.com'], supports_credentials=True)
def private_endpoint():
    return {'data': 'private'}

# Disable CORS for a specific route (internal use only)
@app.route('/internal/health')
@cross_origin(origins=[])
def health_check():
    return {'status': 'ok'}

Or configure per-path with resources:

CORS(app, resources={
    r'/api/public/*': {'origins': '*'},
    r'/api/private/*': {
        'origins': ['https://app.example.com'],
        'supports_credentials': True,
    },
    r'/internal/*': {'origins': []},
})

Fix 5: Fix Blueprint CORS

When using Flask Blueprints, apply CORS directly to the blueprint or ensure the app-level CORS is initialized before blueprints are registered:

# blueprints/users.py
from flask import Blueprint
from flask_cors import CORS

users_bp = Blueprint('users', __name__)

# Apply CORS to the blueprint directly
CORS(users_bp, origins=['https://app.example.com'])

@users_bp.route('/users')
def get_users():
    return {'users': []}
# app.py
from flask import Flask
from flask_cors import CORS
from blueprints.users import users_bp

app = Flask(__name__)
CORS(app)  # App-level CORS

app.register_blueprint(users_bp, url_prefix='/api')

If both app-level and blueprint-level CORS are configured, the blueprint’s config takes precedence for its routes.

Fix 6: Handle CORS Headers on Error Responses

When your Flask route raises an exception, Flask’s default error handlers return responses without CORS headers — the browser blocks them, hiding the real error from the frontend:

from flask import Flask, jsonify
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

# Add CORS headers to error responses
@app.after_request
def after_request(response):
    response.headers['Access-Control-Allow-Origin'] = '*'
    response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
    return response

@app.errorhandler(404)
def not_found(e):
    return jsonify({'error': 'Not found'}), 404

@app.errorhandler(500)
def server_error(e):
    return jsonify({'error': 'Internal server error'}), 500

Using after_request ensures CORS headers are always added, even to error responses. This lets the browser actually display the error status code rather than showing only “CORS blocked.”

Pro Tip: When debugging CORS issues, check two things in the Network tab: (1) the OPTIONS preflight request — does it return 200 with the right headers? (2) the actual GET/POST — does it include Access-Control-Allow-Origin? If the preflight passes but the main request is blocked, the issue is with the actual response headers.

Fix 7: Configure Allowed Headers and Methods

If your request includes custom headers (e.g., Authorization, X-API-Key, X-Custom-Header), they must be listed in allow_headers:

CORS(app,
     origins=['https://app.example.com'],
     allow_headers=['Content-Type', 'Authorization', 'X-API-Key'],
     methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
     max_age=3600)  # Cache preflight for 1 hour

For manual header handling without flask-cors:

from flask import Flask, request, jsonify

app = Flask(__name__)

def add_cors_headers(response):
    response.headers['Access-Control-Allow-Origin'] = 'https://app.example.com'
    response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
    return response

app.after_request(add_cors_headers)

# Handle preflight manually
@app.before_request
def handle_preflight():
    if request.method == 'OPTIONS':
        response = app.make_default_options_response()
        add_cors_headers(response)
        return response

Still Not Working?

Verify flask-cors is actually installed in your virtual environment:

pip show flask-cors
# If not found: pip install flask-cors

Check import — common mistake is importing from the wrong package:

# Wrong
from flask.cors import CORS

# Correct
from flask_cors import CORS

Test without the browser to isolate whether it’s a CORS issue or an application error:

# Direct test — no browser CORS check
curl http://localhost:5000/api/data

# Simulated CORS request
curl -H "Origin: http://localhost:3000" \
     -H "Access-Control-Request-Method: GET" \
     -H "Access-Control-Request-Headers: Authorization" \
     -X OPTIONS \
     -v http://localhost:5000/api/data

If curl works but the browser doesn’t, it’s definitely CORS. If curl also fails, it’s an application error unrelated to CORS.

Check for middleware stripping headers — if Flask runs behind nginx or a reverse proxy, the proxy might strip Access-Control-* headers or not forward the Origin header. Check nginx configuration for proxy_pass_header or add_header directives.

For related Flask issues, see Fix: Flask 404 Not Found and Fix: Express CORS Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles