CORS & Cross-Origin Requests
Reviewed & published by Brayan K
CORS is a browser security mechanism that decides whether JavaScript on one website is allowed to read a response from an API hosted on a different origin.
Part of the free Flask course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll learn what the Access-Control-Allow-* headers do, how preflight OPTIONS requests work, and how to enable CORS in Flask — both by hand and with the Flask-CORS extension.
What You'll Learn in This Lesson
- What an origin is and why the browser blocks cross-origin reads
- The Access-Control-Allow-Origin header
- Allowing methods and headers
- How preflight OPTIONS requests work
- Handling credentials with CORS
- Enabling CORS with the Flask-CORS one-liner
1 The Allow-Origin Header
An origin is the combination of scheme, host, and port — https://app.example.com. By default, the browser's same-origin policy stops JavaScript loaded from one origin from reading a response served by another. To permit it, the server adds an Access-Control-Allow-Origin header naming the allowed origin (or * for any).
You can add that header yourself with an after_request hook that runs after every view. The runnable example sets the header and inspects it with the test client.
# app.py — add the CORS header by hand (runnable via test_client)
from flask import Flask, jsonify
app = Flask(__name__)
@app.after_request
def add_cors_headers(response):
# this is exactly what Flask-CORS does for you
response.headers["Access-Control-Allow-Origin"] = "*"
return response
@app.route("/api/data")
def data():
return jsonify(message="hello from the API")
with app.test_client() as client:
r = client.get("/api/data")
print("Allow-Origin:", r.headers.get("Access-Control-Allow-Origin")) # *
print("body:", r.data.decode().strip())
# ✅ Expected output:
# Allow-Origin: *
# body: {"message":"hello from the API"}Output: Allow-Origin: * and the JSON body. With that header present, a browser will let cross-origin JavaScript read the response.
2 Methods, Headers & the Preflight Request
A simple GET only needs Allow-Origin. But for requests with custom headers (like Authorization) or methods like PUT and DELETE, the browser first sends a preflight OPTIONS request asking permission. The server must answer with Access-Control-Allow-Methods and Access-Control-Allow-Headers.
The example adds all three headers and shows a preflight OPTIONS call returning them, so the browser would then allow the real request.
# app.py — answer a preflight OPTIONS request (runnable)
from flask import Flask, jsonify
app = Flask(__name__)
@app.after_request
def add_cors_headers(response):
response.headers["Access-Control-Allow-Origin"] = "https://app.example.com"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
return response
# allow OPTIONS so the preflight reaches a view
@app.route("/api/items", methods=["GET", "POST", "OPTIONS"])
def items():
return jsonify(ok=True)
with app.test_client() as client:
pre = client.options("/api/items") # the browser's preflight
print("preflight Allow-Methods:", pre.headers.get("Access-Control-Allow-Methods"))
print("preflight Allow-Headers:", pre.headers.get("Access-Control-Allow-Headers"))
print("Allow-Origin:", pre.headers.get("Access-Control-Allow-Origin"))
# ✅ Expected output:
# preflight Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
# preflight Allow-Headers: Content-Type, Authorization
# Allow-Origin: https://app.example.comOutput: the allowed methods, the allowed headers, and the specific origin — the trio the browser checks before sending the real PUT, POST, or DELETE.
3 The Flask-CORS One-Liner
Writing the after_request hook by hand teaches you what is happening, but in production you reach for the Flask-CORS extension. It handles all the headers, the preflight, per-route configuration, and credentials with a single line. Here is the production setup, shown read-only because it relies on the flask_cors package.
🎯 Your Turn: Fill in the Blanks
Complete the CORS hook. Replace each ___ to allow a specific origin and the POST method.
# 🎯 YOUR TURN: replace each ___ to enable CORS
from flask import Flask, jsonify
app = Flask(__name__)
@app.___request # which hook runs after every view?
def add_cors(response):
response.headers["Access-Control-Allow-___"] = "https://my-frontend.com" # which origin?
response.headers["Access-Control-Allow-Methods"] = "GET, ___" # also allow POST
return response
@app.route("/api/ping")
def ping():
return jsonify(pong=True)
with app.test_client() as c:
r = c.get("/api/ping")
print("origin:", r.headers.get("Access-Control-Allow-Origin")) # https://my-frontend.com
print("methods:", r.headers.get("Access-Control-Allow-Methods")) # GET, POSTCommon Errors & Quick Reference
❌ "No 'Access-Control-Allow-Origin' header is present"
The browser blocked the read because the server sent no Allow-Origin header. Fix it on the server — add the header or enable Flask-CORS — not in the front-end.
❌ Preflight fails on a PUT or DELETE
Your server did not answer the OPTIONS preflight with Allow-Methods/Allow-Headers. Allow OPTIONS and return those headers, or let Flask-CORS do it.
❌ Credentials with a wildcard origin
You cannot use Allow-Origin: * together with credentials. Name a specific origin and add Allow-Credentials: true.
| Task | Header |
|---|---|
| Allow an origin | Access-Control-Allow-Origin |
| Allow methods | Access-Control-Allow-Methods |
| Allow headers | Access-Control-Allow-Headers |
| Allow cookies | Access-Control-Allow-Credentials |
🏆 Mini Challenge: Origin Allowlist
Echo back the request's Origin only if it is on an allowlist.
- Read the incoming Origin request header
- If it is in your allowed set, reflect it in Allow-Origin
- Otherwise send no CORS header at all
# 🏆 Mini Challenge: reflect only trusted origins
from flask import Flask, request, jsonify
app = Flask(__name__)
ALLOWED = {"https://app.example.com", "https://admin.example.com"}
@app.after_request
def cors(response):
origin = request.headers.get("Origin")
if origin in ALLOWED:
response.headers["Access-Control-Allow-Origin"] = origin # reflect trusted origin
return response
@app.route("/api/data")
def data():
return jsonify(ok=True)
with app.test_client() as c:
good = c.get("/api/data", headers={"Origin": "https://app.example.com"})
print("trusted ->", good.headers.get("Access-Control-Allow-Origin"))
bad = c.get("/api/data", headers={"Origin": "https://evil.com"})
print("untrusted ->", bad.headers.get("Access-Control-Allow-Origin"))❓ Frequently Asked Questions
Lesson complete — your API plays nicely with front-ends!
You understand origins and the same-origin policy, the Access-Control-Allow-* headers, preflight OPTIONS requests, credentials, and the Flask-CORS one-liner.
Practice quiz
What does CORS stand for?
- Cross-Origin Resource Sharing
- Central Origin Routing System
- Cookie Origin Request Security
- Cross-Origin Response Standard
Answer: Cross-Origin Resource Sharing. CORS is Cross-Origin Resource Sharing, a browser mechanism for cross-origin requests.
What three parts make up an origin?
- host, path, query
- scheme, host, and port
- domain and cookie only
- method and URL
Answer: scheme, host, and port. An origin is scheme + host + port, e.g. https://app.example.com.
Which header tells the browser a cross-origin read is allowed?
- Allow-Cross-Origin
- X-CORS-Allow
- Access-Control-Allow-Origin
- Origin-Permitted
Answer: Access-Control-Allow-Origin. Access-Control-Allow-Origin names the origin (or *) that may read the response.
Where do you fix a CORS error?
- in the browser settings
- in the front-end fetch call
- by disabling JavaScript
- on the server
Answer: on the server. The browser enforces CORS, but the server grants permission via the Allow headers.
What HTTP method does a preflight request use?
- OPTIONS
- GET
- HEAD
- TRACE
Answer: OPTIONS. The browser sends an OPTIONS preflight before non-simple requests.
When does the browser send a preflight request?
- on every GET
- for custom headers or methods like PUT/DELETE
- only over HTTP
- never for JSON
Answer: for custom headers or methods like PUT/DELETE. Non-simple requests (custom headers, PUT/DELETE) trigger a preflight.
Which headers must a preflight response include for a PUT?
- only Allow-Origin
- Content-Type only
- Allow-Methods and Allow-Headers
- Set-Cookie
Answer: Allow-Methods and Allow-Headers. The preflight answer needs Access-Control-Allow-Methods and Allow-Headers.
Why can't you use Allow-Origin: * with credentials?
- The wildcard is rejected with credentials
- It is too slow
- Credentials require GET
- The port must match
Answer: The wildcard is rejected with credentials. With credentials you must name a specific origin; * is rejected by the browser.
Which header must be true to allow cookies cross-origin?
- Access-Control-Allow-Cookies
- Allow-Origin-Credentials
- Access-Control-Allow-Credentials
- Set-Credentials
Answer: Access-Control-Allow-Credentials. Access-Control-Allow-Credentials: true permits credentialed requests.
How do you enable CORS for a whole app with Flask-CORS?
- app.cors()
- CORS(app)
- enable_cors(app)
- app.config['CORS'] = True
Answer: CORS(app). Importing CORS from flask_cors and calling CORS(app) enables it everywhere.
Continue this course
- Previous: Pagination
- Next: API Authentication with JWT — Issue and verify JSON Web Tokens to protect your API endpoints
- Quick reference: Flask cheat sheet
Frequently asked questions
What is CORS and why does it exist?
CORS (Cross-Origin Resource Sharing) is a browser security mechanism. By default the same-origin policy blocks JavaScript on one origin (scheme + host + port) from reading responses from a different origin. CORS lets a server opt in by sending Access-Control-Allow-* headers that tell the browser the cross-origin request is permitted.
Why is CORS a server setting if the browser enforces it?
The browser is the enforcer, but the server is the one that grants permission. The browser will only expose a cross-origin response to JavaScript if the server's response includes the right Access-Control-Allow-Origin header. So you fix CORS errors on the server, not in the front-end.
What is a preflight request?
For non-simple requests (custom headers, methods like PUT or DELETE, or JSON bodies), the browser first sends an OPTIONS request — the preflight — asking whether the real request is allowed. The server answers with Access-Control-Allow-Methods and Access-Control-Allow-Headers. Only if that passes does the browser send the actual request.
How do credentials work with CORS?
If the front-end sends cookies or auth headers with credentials: 'include', the server must respond with Access-Control-Allow-Credentials: true AND a specific origin (not the * wildcard). Using * together with credentials is rejected by the browser for security.