Python Flask Authentication and CORS Setup
Configure Flask authentication with CORS so browser clients can send Bearer tokens or cookies without preflight failures or security gaps.
To combine Flask authentication with CORS, configure the server so the browser is allowed to send the credentials used by your authentication scheme. When a browser-based client calls a Flask API from a different origin, the browser enforces the same-origin policy. If the Flask app uses authentication - whether a JWT in an Authorization header or a session cookie - the CORS configuration must explicitly allow the browser to send those credentials. A common failure is a Flask endpoint that works from curl or Postman but is silently blocked in the browser.
CORS is enforced by the browser, not the server. The server still processes the request, but the browser blocks the response unless the server returns the correct CORS headers. For an authenticated Flask API, the server must allow the requesting origin, the HTTP method, and the headers that carry credentials.
Minimal Flask App with a Protected Endpoint
Start with a small Flask app that exposes a protected route. The exact authentication mechanism matters less than the fact that the route requires credentials.
from flask import Flask, jsonify, request from functools import wraps app = Flask(__name__) def require_token(f): @wraps(f) def wrapper(*args, **kwargs): auth = request.headers.get("Authorization", "") if not auth.startswith("Bearer "): return jsonify({"error": "missing token"}), 401 # In production, validate the token signature and expiry here. return f(*args, **kwargs) return wrapper @app.get("/api/me") @require_token def me(): return jsonify({"user": "alice"})
This route reads the Authorization header and rejects requests without a Bearer token. The implementation is intentionally minimal: a real deployment would verify the token signature, check expiry, and load the user from a database or token claims.
Enabling CORS with flask-cors
The flask-cors extension is the standard way to add CORS headers to a Flask app. Install it with pip install flask-cors, then use the simplest configuration to allow all origins:
from flask_cors import CORS CORS(app)
That works for public endpoints, but it is too permissive for an authenticated API. When the browser sends an Authorization header, it triggers a CORS preflight request. The browser first sends an OPTIONS request asking whether the actual request is allowed. If the server does not respond with the correct Access-Control-Allow-Headers value, the browser blocks the real request even though the server would have accepted it.
Allowing the Authorization Header and Specific Origins
The preflight request for a Bearer token includes Authorization in the Access-Control-Request-Headers header. The server must echo that header back in its response:
CORS(app, resources={r"/api/*": { "origins": ["https://app.example.com"], "allow_headers": ["Authorization", "Content-Type"] }})
Restricting origins to the actual frontend domain matters. Allowing a wildcard origin with credentials is invalid in browsers: when credentials are involved, Access-Control-Allow-Origin cannot be *. The server must return the specific requesting origin instead.
Using Cookies Instead of Authorization Headers
Some Flask authentication setups use cookies rather than Authorization headers. Flask-Login and Flask sessions are common examples. Cookies require a different CORS configuration because the browser must include credentials with the request.
CORS(app, supports_credentials=True, origins=["https://app.example.com"])
With supports_credentials=True, flask-cors sets Access-Control-Allow-Credentials to true, and the browser sends the session cookie with cross-origin requests. Without this flag, the browser omits the cookie and the server sees an unauthenticated request.
There is a tradeoff between the two approaches. Cookies are sent automatically by the browser, which makes CSRF attacks a real concern. A JWT in an Authorization header is not automatically attached to requests and is less exposed to CSRF, but the token must be stored in JavaScript-accessible storage, which brings its own XSS risk.
Preflight Requests and the OPTIONS Method
When a request uses a non-simple method like PUT or DELETE, or a non-simple header like Authorization, the browser sends an OPTIONS preflight before the actual request. Flask's automatic OPTIONS handling returns the preflight response, and flask-cors adds the CORS headers to it; the route handler for the real endpoint is not invoked.
A common source of failures is authenticating the preflight request itself. If the authentication check rejects the OPTIONS request with 401, the browser will not send the real request. Make the authentication check skip OPTIONS requests:
def require_token(f): @wraps(f) def wrapper(*args, **kwargs): if request.method == "OPTIONS": return f(*args, **kwargs) # token validation continues here auth = request.headers.get("Authorization", "") if not auth.startswith("Bearer "): return jsonify({"error": "missing token"}), 401 return f(*args, **kwargs) return wrapper
If you apply require_token to individual routes using @app.get only, Flask's automatic OPTIONS handling usually means the decorator never runs for the preflight. The skip is still harmless and becomes necessary if OPTIONS is an explicit route method or you use a before_request auth hook.
Debugging CORS Failures in an Authenticated Flask API
When a browser request fails but curl works, inspect the browser's network tab. The preflight response should include:
- Access-Control-Allow-Origin matching the requesting origin
- Access-Control-Allow-Headers including Authorization
- Access-Control-Allow-Credentials: true when cookies are used
A 401 on the OPTIONS request usually means the auth check rejected the preflight. A missing Access-Control-Allow-Origin means the origin was not matched by the CORS configuration. A missing Access-Control-Allow-Headers means the header list did not include Authorization.
The order of middleware matters. flask-cors adds its headers in an after-request hook. If another extension or custom middleware overrides the response headers, it can strip the CORS headers. Register CORS last, or ensure no other hook overwrites the response.
Security Considerations for the CORS Configuration
The most common production mistake is calling CORS(app) with default settings, which allows every origin. For an authenticated API, that means any website can make authenticated requests if the browser sends credentials. With cookies, this is a direct CSRF vector. With Authorization headers, the token is not automatically attached, so the risk is lower, but an overly permissive CORS policy still allows any site to read responses if it somehow obtains a token.
Set explicit origins, do not use wildcards with credentials, and use supports_credentials=True only when cookies are actually part of the authentication flow. If the API uses Bearer tokens exclusively, leave supports_credentials off. Also consider that the CORS configuration applies per-route via the resources parameter, so public endpoints like /api/login can use a broader policy while protected routes stay locked to the frontend origin.