Skip to main content
Inventario implements multiple layers of security following Django best practices. This guide covers the security features and configuration requirements.

SECRET_KEY Management

The SECRET_KEY is Django’s most critical security setting. It’s used for:
  • Cryptographic signing of sessions
  • CSRF token generation
  • Password reset tokens
  • Signed cookies and data

Configuration

SECRET_KEY = os.environ.get('SECRET_KEY', 'django-insecure-641xw29uuar#$5&nj!kxjozc+#2#=f6s+4lmziq&_8hnh$#@6$')
The default key is marked as insecure and must never be used in production. Generate a new key:
python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'

Best Practices

  • Never commit SECRET_KEY to version control
  • Rotate regularly (requires re-issuing sessions)
  • Store securely in environment variables only
  • Use different keys for different environments
  • Keep secret - anyone with the key can forge signatures
If SECRET_KEY is compromised:
  1. Generate a new key immediately
  2. Update environment variables in all environments
  3. Restart all application instances
  4. All users will be logged out (sessions invalidated)
  5. All password reset tokens will be invalidated

CSRF Protection

Cross-Site Request Forgery (CSRF) protection is enabled by default and configured for production use.

Middleware

MIDDLEWARE = [
    # ...
    'django.middleware.csrf.CsrfViewMiddleware',
    # ...
]
CSRF_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SECURE = not DEBUG  # True in production
Settings breakdown:
  • CSRF_COOKIE_SAMESITE = 'Lax': Allows CSRF cookie on top-level navigation
  • CSRF_COOKIE_SECURE = True: Requires HTTPS for CSRF cookie (production only)

Trusted Origins

For HTTPS deployments, configure trusted origins:
CSRF_TRUSTED_ORIGINS = os.environ.get(
    'CSRF_TRUSTED_ORIGINS',
    'http://127.0.0.1:8000'
).split(',')
Environment variable:
CSRF_TRUSTED_ORIGINS=https://myapp.railway.app,https://www.example.com
Must include the full URL with https:// protocol. Without this, POST requests will fail with CSRF errors.

CSRF Token Usage

In templates, include CSRF token in all forms:
<form method="post">
    {% csrf_token %}
    <!-- form fields -->
</form>
For AJAX requests, include CSRF token in headers:
fetch('/api/endpoint/', {
    method: 'POST',
    headers: {
        'X-CSRFToken': getCookie('csrftoken'),
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
});

Session Security

Session cookies are secured based on the DEBUG setting.
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_SECURE = not DEBUG  # True in production
Settings breakdown:
  • SESSION_COOKIE_SAMESITE = 'Lax': Prevents CSRF while allowing normal navigation
  • SESSION_COOKIE_SECURE = True: Requires HTTPS for session cookies (production)

Session Management

MIDDLEWARE = [
    # ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    # ...
]
Django’s session framework:
  • Stores session data server-side (database)
  • Only session ID is stored in cookie
  • Session ID is cryptographically signed with SECRET_KEY
  • Sessions expire on browser close by default

Custom Middleware

'applications.cuentas.middleware.ForzarCambioPasswordMiddleware'
Custom middleware to enforce password changes. Users flagged for password reset are redirected to the password change page.

Password Validation

Inventario uses custom password validators for enhanced security.
AUTH_PASSWORD_VALIDATORS = [
    {'NAME': 'applications.cuentas.validators.LongitudMinimaValidator'},
    {'NAME': 'applications.cuentas.validators.ContraseñaComunValidator'},
    {'NAME': 'applications.cuentas.validators.ContraseñaNumericaValidator'},
]

Custom Validators

  1. LongitudMinimaValidator: Enforces minimum password length
  2. ContraseñaComunValidator: Rejects common passwords
  3. ContraseñaNumericaValidator: Prevents fully numeric passwords
These validators are located in applications/cuentas/validators.py.

Password Storage

Django uses PBKDF2 algorithm with SHA256 hash by default:
  • Passwords are never stored in plain text
  • Uses key stretching (320,000 iterations as of Django 5.2)
  • Salted to prevent rainbow table attacks
  • Automatically upgraded when algorithms improve

HTTPS Configuration

Security Middleware

MIDDLEWARE = [
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.security.SecurityMiddleware',  # First after locale
    'whitenoise.middleware.WhiteNoiseMiddleware',
    # ...
]
SecurityMiddleware adds security headers and HTTPS redirects.

HTTPS-Only Cookies

In production (DEBUG=False):
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
These settings ensure cookies are only sent over HTTPS, preventing interception.

Protocol Configuration

ACCOUNT_DEFAULT_HTTP_PROTOCOL = 'https'
Forces HTTPS for allauth (OAuth) redirect URIs.

Clickjacking Protection

MIDDLEWARE = [
    # ...
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
Prevents the application from being embedded in iframes, protecting against clickjacking attacks. Default behavior: Sets X-Frame-Options: DENY header.

Authentication Security

Custom User Model

AUTH_USER_MODEL = 'cuentas.Usuario'
Inventario uses a custom user model for enhanced control over authentication.

Authentication Backends

AUTHENTICATION_BACKENDS = (
    'allauth.account.auth_backends.AuthenticationBackend',
    'django.contrib.auth.backends.ModelBackend',
)
Supports:
  1. OAuth via django-allauth: Google Sign-In
  2. Model backend: Traditional username/password

OAuth Configuration

Google OAuth 2.0 settings:
SOCIALACCOUNT_PROVIDERS = {
    'google': {
        'SCOPE': ['profile', 'email'],
        'AUTH_PARAMS': {'access_type': 'online'},
        'VERIFIED_EMAIL': True,
        'APP': {
            'client_id': os.environ.get('GOOGLE_CLIENT_ID', ''),
            'secret': os.environ.get('GOOGLE_CLIENT_SECRET', ''),
            'key': ''
        }
    }
}
Secure OAuth credentials:
  • Store GOOGLE_CLIENT_SECRET in environment variables only
  • Never commit OAuth secrets to version control
  • Configure authorized redirect URIs in Google Cloud Console
  • Use separate OAuth credentials for development and production

Email Verification

ACCOUNT_EMAIL_VERIFICATION = 'none'
ACCOUNT_EMAIL_REQUIRED = True
Email is required but verification is disabled. Consider enabling verification in production:
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'  # Require email verification

Database Security

Connection String Security

DATABASES = {
    'default': dj_database_url.config(
        default=f'sqlite:///{BASE_DIR / "db.sqlite3"}',
        conn_max_age=600
    )
}
Database credentials are passed via DATABASE_URL environment variable:
DATABASE_URL=postgresql://user:password@host:5432/dbname
Never hardcode database credentials in settings.py. Always use environment variables.

Connection Pooling

conn_max_age=600: Keeps database connections open for 10 minutes, improving performance while limiting connection count.

API Key Security

Inventario integrates with external services requiring API keys.

Secure Storage

All API keys are stored in environment variables:
OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY', '')
RESEND_API_KEY = os.environ.get('RESEND_API_KEY', '')
TWILIO_AUTH_TOKEN = os.environ.get('TWILIO_AUTH_TOKEN', '')

Rotation Strategy

  1. Regular rotation: Rotate API keys every 90 days
  2. Immediate rotation if compromised
  3. Monitor usage: Check for unusual API usage patterns
  4. Scope limits: Use minimal required permissions
API keys with empty defaults ('') will fail silently. Monitor logs for authentication errors when features are enabled.

Production Security Checklist

Before deploying to production:

Critical Settings

  • DEBUG=False (never run production with DEBUG=True)
  • Strong SECRET_KEY generated and secured
  • ALLOWED_HOSTS configured with exact domains
  • CSRF_TRUSTED_ORIGINS set with HTTPS URLs
  • SESSION_COOKIE_SECURE=True (automatic when DEBUG=False)
  • CSRF_COOKIE_SECURE=True (automatic when DEBUG=False)

Authentication

  • Google OAuth credentials configured
  • OAuth redirect URIs registered in Google Cloud Console
  • Consider enabling email verification (ACCOUNT_EMAIL_VERIFICATION='mandatory')
  • Password validators tested and documented for users

Infrastructure

  • HTTPS enabled and enforced
  • Database credentials secured in environment variables
  • PostgreSQL configured (not SQLite)
  • All API keys stored in environment variables
  • .env file added to .gitignore

Monitoring

  • Error tracking configured (Sentry, etc.)
  • Log authentication failures
  • Monitor API usage and rate limits
  • Set up alerts for security events

Updates

  • Dependencies up to date (check requirements.txt)
  • Security patches applied
  • Django security releases monitored

Security Headers

Consider adding these security headers via SecurityMiddleware settings:
# Recommended additions to settings.py
SECURE_HSTS_SECONDS = 31536000  # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_SSL_REDIRECT = not DEBUG
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
These settings:
  • Force HTTPS connections (HSTS)
  • Prevent XSS attacks
  • Prevent MIME type sniffing
  • Redirect HTTP to HTTPS

Incident Response

If a security incident occurs:
  1. Immediate: Rotate SECRET_KEY and affected API keys
  2. Investigate: Check logs for unauthorized access
  3. Notify: Inform affected users if data was compromised
  4. Patch: Fix the vulnerability
  5. Deploy: Push updates to production immediately
  6. Document: Record the incident and response
  7. Review: Update security practices to prevent recurrence

Build docs developers (and LLMs) love