Overview
Secure configuration is essential for protecting your application in production. This guide demonstrates best practices for managing sensitive configuration data based on the secure implementation.
Environment Variables
Never hardcode sensitive values in your code. Use environment variables instead.
Using python-dotenv
The secure implementation uses python-dotenv to manage environment variables:
from flask import Flask
from dotenv import load_dotenv
import os
# Load environment variables from .env file
load_dotenv()
app = Flask( __name__ )
# Use environment variables for sensitive configuration
app.secret_key = os.getenv( 'SECRET_KEY' )
Install python-dotenv
pip install python-dotenv== 1.0.0
Create .env file
# .env
SECRET_KEY = your-secret-key-here
DATABASE_URL = postgresql://user:pass@localhost/db
DEBUG = False
Load in application
from dotenv import load_dotenv
import os
load_dotenv()
secret_key = os.getenv( 'SECRET_KEY' )
Add .env to .gitignore
echo ".env" >> .gitignore
Never commit .env files to version control. Add them to .gitignore immediately.
Secret Key Management
Generating Strong Secret Keys
Generate cryptographically secure secret keys:
python -c "import secrets; print(secrets.token_hex(32))"
Output example:
3f7a3b2c8e1d4f9a6b5c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1
Using Secret Keys in Flask
import os
from dotenv import load_dotenv
load_dotenv()
app = Flask( __name__ )
# Load secret key from environment
app.secret_key = os.getenv( 'SECRET_KEY' )
if not app.secret_key:
raise ValueError ( 'SECRET_KEY environment variable must be set' )
Good Practice app.secret_key = os.getenv( 'SECRET_KEY' )
Load from environment variables
Bad Practice app.secret_key = 'my-secret-key'
Hardcoded in source code
Database Configuration
Secure Database Connections
import sqlite3
import os
def get_db_path ():
# Get database path from environment or use default
db_path = os.getenv( 'DATABASE_PATH' )
if db_path:
return db_path
return os.path.join(os.path.dirname( __file__ ), 'users.db' )
def create_connection ():
try :
conn = sqlite3.connect(get_db_path())
conn.row_factory = sqlite3.Row
return conn
except Exception as e:
print ( f "Error: { e } " )
return None
For production databases like PostgreSQL or MySQL, store full connection strings in environment variables.
Production Database Configuration
import os
from sqlalchemy import create_engine
# Load database URL from environment
database_url = os.getenv( 'DATABASE_URL' )
if not database_url:
raise ValueError ( 'DATABASE_URL environment variable must be set' )
engine = create_engine(database_url,
pool_size = 10 ,
max_overflow = 20 ,
pool_pre_ping = True # Verify connections before use
)
Debug Mode Configuration
Development Enable detailed error pages and auto-reload
Production Disable debug mode to prevent information disclosure
Proper Debug Configuration
if __name__ == '__main__' :
import os
# Get port from environment
port = int (os.environ.get( 'PORT' , 5001 ))
# IMPORTANT: Debug should be False in production
app.run( host = '0.0.0.0' , port = port, debug = False )
Never run with debug=True in production as it can expose sensitive information and allow code execution.
Configuration by Environment
Using Config Classes
import os
class Config :
"""Base configuration"""
SECRET_KEY = os.getenv( 'SECRET_KEY' )
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig ( Config ):
"""Development configuration"""
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'
class ProductionConfig ( Config ):
"""Production configuration"""
DEBUG = False
SQLALCHEMY_DATABASE_URI = os.getenv( 'DATABASE_URL' )
# Require HTTPS
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
class TestingConfig ( Config ):
"""Testing configuration"""
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
# Select configuration based on environment
config = {
'development' : DevelopmentConfig,
'production' : ProductionConfig,
'testing' : TestingConfig,
'default' : DevelopmentConfig
}
Using Configuration
from flask import Flask
import os
app = Flask( __name__ )
# Load config based on environment
env = os.getenv( 'FLASK_ENV' , 'development' )
app.config.from_object(config[env])
Session Configuration
Secure Session Settings
from flask import Flask
import os
from datetime import timedelta
app = Flask( __name__ )
app.secret_key = os.getenv( 'SECRET_KEY' )
# Session configuration
app.config[ 'SESSION_COOKIE_SECURE' ] = True # HTTPS only
app.config[ 'SESSION_COOKIE_HTTPONLY' ] = True # No JavaScript access
app.config[ 'SESSION_COOKIE_SAMESITE' ] = 'Lax' # CSRF protection
app.config[ 'PERMANENT_SESSION_LIFETIME' ] = timedelta( hours = 24 ) # Session timeout
SECURE flag
Send cookies over HTTPS only: SESSION_COOKIE_SECURE = True
HTTPONLY flag
Prevent JavaScript access to cookies: SESSION_COOKIE_HTTPONLY = True
SAMESITE flag
Protect against CSRF attacks: SESSION_COOKIE_SAMESITE = 'Lax'
Session timeout
Limit session lifetime: PERMANENT_SESSION_LIFETIME = timedelta( hours = 24 )
CSRF Configuration
Flask-WTF CSRF Protection
from flask import Flask
from flask_wtf.csrf import CSRFProtect
import os
app = Flask( __name__ )
app.secret_key = os.getenv( 'SECRET_KEY' )
# Enable CSRF protection
csrf = CSRFProtect(app)
Flask-WTF automatically validates CSRF tokens for all POST requests when CSRFProtect is enabled.
secure/templates/login.html
< form method = "POST" >
<!-- CSRF token is required -->
< input type = "hidden" name = "csrf_token" value = "{{ csrf_token() }}" >
< div class = "form-group" >
< label > Usuario: </ label >
< input type = "text" name = "username" required >
</ div >
< button type = "submit" > Login </ button >
</ form >
Production Server Configuration
Using Gunicorn
Flask == 3.0 .0
python - dotenv == 1.0 .0
gunicorn == 21.2 .0
Werkzeug == 3.0 .0
Flask - WTF == 1.2 .1
# Run with Gunicorn in production
gunicorn --bind 0.0.0.0:8000 --workers 4 app:app
Use a production WSGI server like Gunicorn or uWSGI instead of Flask’s built-in development server.
Gunicorn Configuration
# gunicorn_config.py
import os
bind = f "0.0.0.0: { os.getenv( 'PORT' , 8000 ) } "
workers = int (os.getenv( 'WORKERS' , 4 ))
worker_class = 'sync'
worker_connections = 1000
max_requests = 1000
max_requests_jitter = 50
timeout = 30
keepalive = 2
# Logging
accesslog = '-'
errorlog = '-'
loglevel = 'info'
Logging Configuration
Secure Logging Practices
import logging
import os
# Configure logging
log_level = os.getenv( 'LOG_LEVEL' , 'INFO' )
logging.basicConfig(
level = getattr (logging, log_level),
format = ' %(asctime)s - %(name)s - %(levelname)s - %(message)s '
)
logger = logging.getLogger( __name__ )
# Log security events
@app.route ( '/login' , methods = [ 'POST' ])
def login ():
username = request.form.get( 'username' )
# Log authentication attempts (without passwords!)
logger.info( f 'Login attempt for user: { username } ' )
if authenticate(username, password):
logger.info( f 'Successful login for user: { username } ' )
else :
logger.warning( f 'Failed login attempt for user: { username } ' )
Do Log
Login attempts
Authorization failures
Configuration changes
Security events
Don't Log
Passwords
Session tokens
Credit card numbers
Personal data
Configuration Checklist
Environment variables
Store all sensitive configuration in environment variables:
SECRET_KEY
DATABASE_URL
API keys
Third-party credentials
Disable debug mode
DEBUG = False # Production
Secure cookies
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
Production server
Use Gunicorn or uWSGI, not Flask development server
Secure logging
Log security events but never log sensitive data
Environment Variable Template
Create a .env.example file to document required configuration:
# .env.example
# Copy this file to .env and fill in your values
# Flask Configuration
SECRET_KEY = generate-with-secrets-token-hex-32
FLASK_ENV = production
DEBUG = False
# Database
DATABASE_URL = postgresql://user:password@localhost/dbname
# Server
PORT = 8000
WORKERS = 4
# Logging
LOG_LEVEL = INFO
# Session
SESSION_LIFETIME_HOURS = 24
Commit .env.example to version control, but never commit .env with actual secrets.
Docker Configuration
Pass environment variables to Docker containers securely:
# docker-compose.yml
version : '3.8'
services :
web :
build : .
ports :
- "8000:8000"
environment :
- SECRET_KEY=${SECRET_KEY}
- DATABASE_URL=${DATABASE_URL}
- FLASK_ENV=production
env_file :
- .env
Cloud Deployment Configuration
Heroku heroku config:set SECRET_KEY=your-key
heroku config:set DATABASE_URL=postgres://...
AWS Use AWS Systems Manager Parameter Store or Secrets Manager
Google Cloud Use Google Secret Manager
Validation at Startup
Validate required configuration on application startup:
import os
from flask import Flask
app = Flask( __name__ )
# Required environment variables
REQUIRED_ENV_VARS = [
'SECRET_KEY' ,
'DATABASE_URL' ,
]
# Validate configuration
for var in REQUIRED_ENV_VARS :
if not os.getenv(var):
raise ValueError ( f 'Required environment variable { var } is not set' )
app.secret_key = os.getenv( 'SECRET_KEY' )
Fail fast on startup if required configuration is missing rather than failing during runtime.
Next Steps
Security Headers Configure HTTP security headers
Deployment Guide Learn how to deploy your secure application