Back to Blog
Python

Python Flask Authentication and CORS Setup

python flask authentication and cors: Configure Flask authentication with CORS so browser clients can send Bearer tokens or cookies without preflight failures or secur...

FlaskCORSAuthenticationJWTWeb Security
Diagram showing a browser sending an authenticated request to a Flask server with a CORS bridge allowing the cross-origin connection.

python flask authentication and cors requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

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 perfectly from curl or Postman but returns 401 or is silently blocked in the browser.

The reason is that 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 python flask authentication and cors to work together, 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. The simplest configuration allows 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-cors intercepts these OPTIONS requests automatically and returns the CORS headers without invoking the route handler.

A common mistake is protecting the OPTIONS method with the same authentication decorator. If the auth decorator rejects OPTIONS requests, the preflight fails and the browser never sends the real request. The decorator should 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

flask-cors handles the preflight response itself, but if a custom auth decorator runs before the extension's after-request hook, the OPTIONS request may still reach the decorator. Skipping OPTIONS in the decorator avoids that failure mode.

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 decorator 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.

python flask authentication and cors: Practical Usage and Co | RYUSLOG DEV