Flask & FastAPI Security Guide 2026: HTTPS, CORS, Pydantic & Rate Limiting
Flask security starts with a blank slate — Flask's philosophy is to stay out of your way, which means security is your responsibility from the first line of code. FastAPI is more opinionated about validation, but its security configuration still requires deliberate setup. This guide covers both frameworks comprehensively.
Before starting, run a free scan at ZeriFlow to get an instant view of your current live security posture across 80+ checks.
1. Flask-Talisman: Security Headers in One Extension
Flask-Talisman wraps Flask's response pipeline and sets HTTP security headers including Content Security Policy and HTTPS enforcement.
pip install flask-talismanfrom flask import Flask
from flask_talisman import Talisman
app = Flask(__name__)
csp = {
'default-src': "'self'",
'script-src': "'self'",
'style-src': ["'self'", 'https://fonts.googleapis.com'],
'font-src': ["'self'", 'https://fonts.gstatic.com'],
'img-src': ["'self'", 'data:', 'https:'],
}
Talisman(
app,
force_https=True,
strict_transport_security=True,
strict_transport_security_max_age=31536000,
strict_transport_security_include_subdomains=True,
strict_transport_security_preload=True,
content_security_policy=csp,
frame_options='DENY',
referrer_policy='strict-origin-when-cross-origin',
feature_policy={
'geolocation': "'none'",
'microphone': "'none'",
'camera': "'none'",
},
)For FastAPI, use starlette-csrf and a custom middleware:
from fastapi import FastAPI
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
app = FastAPI()
app.add_middleware(HTTPSRedirectMiddleware)
app.add_middleware(TrustedHostMiddleware, allowed_hosts=['yourdomain.com', '*.yourdomain.com'])2. CORS Configuration
Flask-CORS:
pip install flask-corsfrom flask_cors import CORS
CORS(
app,
origins=['https://yourdomain.com', 'https://app.yourdomain.com'],
methods=['GET', 'POST', 'PUT', 'DELETE'],
allow_headers=['Content-Type', 'Authorization'],
supports_credentials=True,
max_age=86400,
)FastAPI built-in CORS:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=['https://yourdomain.com'],
allow_credentials=True,
allow_methods=['*'],
allow_headers=['Authorization', 'Content-Type'],
max_age=86400,
)Never set allow_origins=['*'] on an authenticated API. Wildcard origins bypass the same-origin policy and allow any website to read your API responses in the user's browser.
3. Pydantic Validation in FastAPI
FastAPI uses Pydantic models for request validation. This is one of FastAPI's strongest security features — all input is validated and typed before your handler runs.
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, EmailStr, field_validator
import re
class UserCreate(BaseModel):
name: str
email: EmailStr
password: str
@field_validator('name')
@classmethod
def name_must_be_clean(cls, v: str) -> str:
if len(v) < 2 or len(v) > 100:
raise ValueError('Name must be 2-100 characters')
if not re.match(r'^[a-zA-Z\s\-]+$', v):
raise ValueError('Name contains invalid characters')
return v.strip()
@field_validator('password')
@classmethod
def password_strength(cls, v: str) -> str:
if len(v) < 12:
raise ValueError('Password must be at least 12 characters')
if not re.search(r'[A-Z]', v):
raise ValueError('Password must contain uppercase letter')
if not re.search(r'[0-9]', v):
raise ValueError('Password must contain a digit')
return v
@app.post('/users', status_code=status.HTTP_201_CREATED)
async def create_user(payload: UserCreate):
# payload is fully validated at this point
hashed = hash_password(payload.password)
# ...For Flask, use marshmallow or pydantic directly:
from pydantic import BaseModel, EmailStr, ValidationError
class UserSchema(BaseModel):
name: str
email: EmailStr
@app.route('/users', methods=['POST'])
def create_user():
try:
data = UserSchema(**request.get_json())
except ValidationError as e:
return {'error': e.errors()}, 400
# data.name and data.email are validated4. Flask Session Security
Flask's default cookie-based session uses a signed but readable cookie. For sensitive applications, use server-side sessions.
pip install Flask-Session redisfrom flask import Flask
from flask_session import Session
import redis
app = Flask(__name__)
app.config.update(
SECRET_KEY = os.environ['SECRET_KEY'],
SESSION_TYPE = 'redis',
SESSION_REDIS = redis.from_url(os.environ['REDIS_URL']),
SESSION_COOKIE_SECURE = True,
SESSION_COOKIE_HTTPONLY = True,
SESSION_COOKIE_SAMESITE = 'Lax',
PERMANENT_SESSION_LIFETIME = 1800, # 30 minutes
)
Session(app)Always regenerate the session on login:
from flask import session
session.clear()
session.permanent = True
session['user_id'] = user.id5. Rate Limiting with Flask-Limiter
pip install Flask-Limiterfrom flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app=app,
key_func=get_remote_address,
default_limits=['200 per day', '50 per hour'],
storage_uri=os.environ['REDIS_URL'],
)
@app.route('/auth/login', methods=['POST'])
@limiter.limit('10 per 15 minutes')
def login():
# ...
passFor FastAPI, use slowapi:
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.post('/auth/login')
@limiter.limit('10/15minutes')
async def login(request: Request, credentials: LoginSchema):
# ...
pass6. Environment Hardening and Secret Management
# Never hardcode secrets
# Bad:
app.secret_key = 'mysecretkey'
# Good:
import os
app.secret_key = os.environ['SECRET_KEY']
# Use python-dotenv for local development only
from dotenv import load_dotenv
load_dotenv() # Loads .env in development, ignored in production when env vars are setUse python-decouple for typed environment variables:
from decouple import config, Csv
SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())Run ZeriFlow after every production deployment to confirm .env files and debug endpoints are not publicly accessible.
FAQ
### Q: Is Flask or FastAPI more secure by default? A: FastAPI provides better security defaults through mandatory Pydantic validation and async-first architecture. Flask is more flexible but requires more manual security setup. Both can be made equally secure with the right extensions.
### Q: How do I prevent SQL injection in Flask with SQLAlchemy?
A: Use SQLAlchemy's ORM or its parameterized text() construct. Never format user input into SQL strings. The ORM handles parameterization automatically for all standard queries.
### Q: Should I use Flask-Login for authentication?
A: Flask-Login handles the session management layer (current_user, login_required) but not password hashing or form validation. Combine it with bcrypt or argon2-cffi for passwords and WTForms or Pydantic for validation.
### Q: How do I handle file uploads securely in Flask?
A: Use werkzeug.utils.secure_filename(), validate MIME type (not just extension), restrict allowed types, set a max content length via MAX_CONTENT_LENGTH, and store files outside the webroot.
### Q: Does FastAPI automatically handle CSRF?
A: No. FastAPI is primarily an API framework — CSRF is relevant when browser cookies are used for authentication. If you use cookie-based sessions, add starlette-csrf middleware. If you use Bearer tokens in headers, CSRF is not applicable.
Conclusion
Flask and FastAPI security in 2026 means assembling the right pieces: Talisman or custom middleware for headers, strict CORS, Pydantic or marshmallow for validation, Redis-backed rate limiting, and server-side sessions for Flask. FastAPI's Pydantic integration is a genuine security advantage — leverage it on every endpoint.
Validate your configuration from the outside with ZeriFlow's free scanner — it checks your headers, TLS grade, and exposed endpoints in seconds, giving you the same view an attacker would have before launching an attack.