Skip to main content

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:
secure/app.py
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')
1

Install python-dotenv

pip install python-dotenv==1.0.0
2

Create .env file

# .env
SECRET_KEY=your-secret-key-here
DATABASE_URL=postgresql://user:pass@localhost/db
DEBUG=False
3

Load in application

from dotenv import load_dotenv
import os

load_dotenv()
secret_key = os.getenv('SECRET_KEY')
4

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

secure/app.py
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

secure/database.py
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

DEBUG=True
Enable detailed error pages and auto-reload

Production

DEBUG=False
Disable debug mode to prevent information disclosure

Proper Debug Configuration

secure/app.py
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
1

SECURE flag

Send cookies over HTTPS only:
SESSION_COOKIE_SECURE = True
2

HTTPONLY flag

Prevent JavaScript access to cookies:
SESSION_COOKIE_HTTPONLY = True
3

SAMESITE flag

Protect against CSRF attacks:
SESSION_COOKIE_SAMESITE = 'Lax'
4

Session timeout

Limit session lifetime:
PERMANENT_SESSION_LIFETIME = timedelta(hours=24)

CSRF Configuration

Flask-WTF CSRF Protection

secure/app.py
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.

CSRF Token in Forms

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

secure/requirements.txt
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

1

Environment variables

Store all sensitive configuration in environment variables:
  • SECRET_KEY
  • DATABASE_URL
  • API keys
  • Third-party credentials
2

Disable debug mode

DEBUG = False  # Production
3

Secure cookies

SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
4

CSRF protection

csrf = CSRFProtect(app)
5

Production server

Use Gunicorn or uWSGI, not Flask development server
6

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

Azure

Use Azure Key Vault

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

Build docs developers (and LLMs) love