django-cf is a package that integrates Django with Cloudflare products
Project description
django-cf
django-cf is a Python package that seamlessly integrates your Django applications with various Cloudflare services. Utilize the power of Cloudflare's global network for your Django projects.
Installation
pip install django-cf
Note on Cloudflare Workers Plan: This package requires a Cloudflare Workers Paid Plan for production use. Django loads many modules which can exceed the CPU time limits included in free accounts. However, if you're interested in experimenting on the free account, feel free to try it out! If you manage to get it working on the free plan, please open an issue describing your setup so other users can benefit from your solution.
Features
This package provides integration with the following Cloudflare products:
- Cloudflare Workers: Run your Django application on Cloudflare's serverless compute platform.
- Cloudflare D1: Use Cloudflare's serverless SQL database as a backend for your Django application.
- Cloudflare Durable Objects: Leverage stateful objects for applications requiring persistent state within Cloudflare Workers.
- Cloudflare R2: Use Cloudflare's object storage as a Django storage backend for file uploads and static assets.
- Cloudflare Access Authentication: Seamless authentication middleware for Cloudflare Access protected applications.
Table of Contents
Database Backends
Cloudflare D1 Integration
Use Cloudflare D1, a serverless SQL database, as your Django application's database.
Important:
- Transactions are disabled for all D1 database engines. Every query is committed immediately.
- The D1 backend has some limitations compared to traditional SQLite or other SQL databases. Many advanced ORM features or direct SQL functions (especially those used in Django Admin) might not be fully supported. Refer to the "Limitations" section.
Configuration:
-
wrangler.jsonc(orwrangler.toml):{ "d1_databases": [ { "binding": "DB", "database_name": "my-django-db", "database_id": "your-d1-database-id" } ] } -
Worker Entrypoint (
src/index.py):from workers import WorkerEntrypoint from django_cf import DjangoCF from app.wsgi import application class Default(DjangoCF, WorkerEntrypoint): async def get_app(self): return application
-
Django Settings (
settings.py):DATABASES = { 'default': { 'ENGINE': 'django_cf.db.backends.d1', # 'CLOUDFLARE_BINDING' should match the binding name in your wrangler.jsonc 'CLOUDFLARE_BINDING': 'DB', } }
For a complete working example with full configuration and management endpoints, see the D1 template.
Cloudflare Durable Objects Integration
Utilize Durable Objects for stateful data persistence directly within your Cloudflare Workers. This is useful for applications requiring low-latency access to state associated with specific objects or instances.
Important:
- Transactions are disabled for Durable Objects. All queries are committed immediately, and rollbacks are not available.
- Durable Objects offer a unique model for state. Understand its consistency and scalability characteristics before implementing.
Configuration:
-
wrangler.jsonc(orwrangler.toml): Define your Durable Object class and binding.{ "durable_objects": { "bindings": [ { "name": "DO_STORAGE", "class_name": "DjangoDO" } ] }, "migrations": [ { "tag": "v1", "new_sqlite_classes": ["DjangoDO"] } ] } -
Worker Entrypoint (
src/index.py):from workers import DurableObject, WorkerEntrypoint from django_cf import DjangoCFDurableObject from app.wsgi import application class DjangoDO(DjangoCFDurableObject, DurableObject): def get_app(self): return application class Default(WorkerEntrypoint): async def fetch(self, request): # Route requests to a DO instance id = self.env.DO_STORAGE.idFromName("singleton_instance") obj = self.env.DO_STORAGE.get(id) return await obj.fetch(request)
-
Django Settings (
settings.py):DATABASES = { 'default': { 'ENGINE': 'django_cf.db.backends.do', } }
For a complete working example with full configuration and management endpoints, see the Durable Objects template.
Storage Backends
Cloudflare R2 Storage
Use Cloudflare R2, a global object storage service, as your Django storage backend for handling file uploads, media files, and static assets.
Features:
- Serverless Object Storage: Store files in Cloudflare's globally distributed object storage.
- S3-Compatible API: R2 uses an S3-compatible API accessible through Workers bindings.
- No Egress Fees: R2 has no egress fees, making it cost-effective for serving files.
- Integrated with Workers: Direct access to R2 buckets through Worker bindings.
Configuration:
-
wrangler.jsonc(orwrangler.toml):{ "r2_buckets": [ { "binding": "BUCKET", "bucket_name": "my-django-bucket" } ] } -
Django Settings (
settings.py):MEDIA_URL = 'https://pub-xxxxx.r2.dev/' STORAGES = { "default": { "BACKEND": "django_cf.storage.R2Storage", "OPTIONS": { # The binding name must match the one in wrangler.jsonc "binding": "BUCKET", # Optional: prefix for all files "location": "media", # Optional: allow overwriting existing files (default: False) "allow_overwrite": False, } }, "staticfiles": { "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", }, }
Usage Example:
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
# Save a file
content = ContentFile(b"Hello, R2!")
path = default_storage.save("hello.txt", content)
# Read a file
with default_storage.open("hello.txt", "rb") as f:
content = f.read()
# Check if file exists
exists = default_storage.exists("hello.txt")
# Delete a file
default_storage.delete("hello.txt")
# List files in a directory
directories, files = default_storage.listdir("uploads/")
Configuration Options:
binding(required): The R2 bucket binding name from yourwrangler.jsonclocation(optional): A prefix path for all files stored in R2 (default: empty string)allow_overwrite(optional): Controls file overwrite behavior:True: Files with the same name will be overwrittenFalse(default): If a file exists, a new filename will be generated by appending_1,_2, etc. before the file extension
Public URL Access:
R2Storage uses Django's MEDIA_URL setting to construct file URLs. You have two options:
Option 1: Public R2 bucket with direct URLs
Make your R2 bucket public and use the full R2 URL as MEDIA_URL:
MEDIA_URL = 'https://pub-xxxxx.r2.dev/'
STORAGES = {
"default": {
"BACKEND": "django_cf.storage.R2Storage",
"OPTIONS": {
"binding": "BUCKET",
"location": "media",
}
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
}
⚠️ Warning: Making your R2 bucket public will make all files in that bucket publicly accessible to anyone. Only use this approach if you store exclusively public files in the bucket.
Option 2: Custom media serve view with access control
Implement a custom Django view to serve files from R2 and control access:
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('media/<path:file_path>', views.serve_media, name='serve_media'),
]
# views.py
from django.http import FileResponse, HttpResponseForbidden
from django.core.files.storage import default_storage
def serve_media(request, file_path):
# Add your access control logic here
if not request.user.is_authenticated:
return HttpResponseForbidden()
# Check if user has permission to access this file
# (implement your own permission logic)
try:
file = default_storage.open(file_path, 'rb')
return FileResponse(file, content_type='application/octet-stream')
except FileNotFoundError:
return HttpResponseForbidden()
Then configure MEDIA_URL to point to your view:
MEDIA_URL = '/media/'
STORAGES = {
"default": {
"BACKEND": "django_cf.storage.R2Storage",
"OPTIONS": {
"binding": "BUCKET",
"location": "media",
}
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
}
This approach allows you to implement custom access control logic and serve both public and private files securely.
When you reference a file in a template (e.g., {{ object.image.url }}), Django will generate a URL based on MEDIA_URL and the file's path in R2.
Important Notes:
- R2 Storage is designed to work within Cloudflare Workers and requires Worker bindings.
- You must configure
MEDIA_URLin your Django settings for theurl()method to work. - Your web server needs to be configured to serve files from R2 at the
MEDIA_URLpath (e.g., using a reverse proxy or Cloudflare Workers route). - All file operations use the Workers R2 API, which provides low-latency access to your objects.
Middleware
Cloudflare Access Authentication
Seamless authentication middleware for Cloudflare Access protected applications. The middleware is implemented using only Python standard library modules - no external dependencies required!
This middleware works on both self-hosted Django applications and Django hosted on Cloudflare Workers.
Quick Start
1. Add to INSTALLED_APPS
# settings.py
INSTALLED_APPS = [
# ... your other apps
'django_cf',
]
2. Configure Cloudflare Access Middleware
Add the middleware to your Django settings:
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', # Must be before CloudflareAccessMiddleware
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django_cf.middleware.CloudflareAccessMiddleware',
# ... other middleware (should be placed appropriately in your middleware stack)
]
3. Configure Cloudflare Access Settings
Add the required settings to your Django configuration:
# settings.py
# Required settings (at least one must be provided)
CLOUDFLARE_ACCESS_AUD = 'your-application-audience-tag' # Optional if team name is provided
CLOUDFLARE_ACCESS_TEAM_NAME = 'yourteam' # Optional if AUD is provided
# Optional settings
CLOUDFLARE_ACCESS_EXEMPT_PATHS = [
'/health/',
'/api/public/',
'/admin/login/', # If you want to keep Django admin login
]
CLOUDFLARE_ACCESS_CACHE_TIMEOUT = 3600 # Cache timeout for public keys (seconds)
Required Settings (At Least One)
You must provide at least one of the following settings:
CLOUDFLARE_ACCESS_AUD
Your Cloudflare Access Application Audience (AUD) tag. You can find this in your Cloudflare Zero Trust dashboard:
- Go to Access → Applications
- Select your application
- Copy the "Application Audience (AUD) Tag"
When to use: Provide this if you want strict audience validation or if you have multiple applications with the same team.
CLOUDFLARE_ACCESS_TEAM_NAME
Your Cloudflare team name (the subdomain part of your team domain). For example, if your team domain is mycompany.cloudflareaccess.com, then your team name is mycompany.
When to use: Provide this if you want simpler configuration or if you only have one application per team.
Configuration Options
You can configure the middleware in three ways:
-
Both AUD and Team Name (Recommended for production):
CLOUDFLARE_ACCESS_AUD = 'your-application-audience-tag' CLOUDFLARE_ACCESS_TEAM_NAME = 'yourteam'
-
Only AUD (Team name extracted from JWT):
CLOUDFLARE_ACCESS_AUD = 'your-application-audience-tag'
-
Only Team Name (AUD validated but not enforced):
CLOUDFLARE_ACCESS_TEAM_NAME = 'yourteam'
Optional Settings
CLOUDFLARE_ACCESS_EXEMPT_PATHS
Default: []
List of URL paths that should be exempt from Cloudflare Access authentication. Useful for:
- Health check endpoints
- Public API endpoints
- Webhooks
- Static file serving (if not handled by your web server)
Example:
CLOUDFLARE_ACCESS_EXEMPT_PATHS = [
'/health/',
'/api/public/',
'/webhooks/',
'/static/', # If serving static files through Django
]
CLOUDFLARE_ACCESS_CACHE_TIMEOUT
Default: 3600 (1 hour)
How long to cache Cloudflare's public keys (in seconds). Cloudflare rotates these keys periodically, so caching reduces API calls while ensuring fresh keys are fetched when needed.
How It Works
Authentication Flow
- Request Processing: For each incoming request, the middleware checks if the path is exempt from authentication
- JWT Extraction: Extracts the JWT token from:
CF-Access-Jwt-Assertionheader (preferred)CF_Authorizationcookie (fallback)
- Team Discovery: If team name is not configured, extracts it from the JWT's issuer claim
- Public Key Retrieval: Fetches Cloudflare's public keys from the team's certs endpoint
- Token Validation: Validates the JWT against Cloudflare's public keys:
- Validates token signature and expiration
- Validates audience (AUD) if configured
- User Management:
- Extracts email and name from JWT claims
- Creates new Django user if doesn't exist
- Updates existing user's name if changed
- Logs the user into Django session
- Response: Proceeds with the request if authentication succeeds, returns 401 if it fails
User Creation
When a user authenticates for the first time:
- Username is set to the user's email address
- Email is extracted from JWT
emailclaim - Name is split into first_name and last_name from JWT
nameclaim - User is created with
is_active=True
For existing users:
- Name is updated if it has changed in Cloudflare
- User is automatically logged in
Security Considerations
Token Validation
- All JWT tokens are validated against Cloudflare's public keys
- Tokens are checked for expiration
- Audience (AUD) is validated to ensure tokens are for your application
Caching
- Public keys are cached to reduce API calls to Cloudflare
- Cache keys are scoped to your team name
- Failed key fetches are logged but don't crash the application
Error Handling
- Authentication failures return 401 responses
- Server errors return 500 responses
- All errors are logged for debugging
Logging
The middleware uses Django's logging system with logger name django_cf.middleware. To see debug information:
# settings.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django_cf.middleware': {
'handlers': ['console'],
'level': 'INFO',
'propagate': True,
},
},
}
Common Issues and Troubleshooting
401 Unauthorized Responses
Cause: JWT token is missing, invalid, or expired.
Solutions:
- Ensure your application is behind Cloudflare Access
- Check that users are properly authenticated with Cloudflare Access
- If using
CLOUDFLARE_ACCESS_AUD, verify it matches your application's AUD tag - If using
CLOUDFLARE_ACCESS_TEAM_NAME, check that it's correct - Review middleware logs for specific validation errors
500 Internal Server Error
Cause: Unable to fetch Cloudflare public keys or other configuration issues.
Solutions:
- Verify your team name is correct (if using
CLOUDFLARE_ACCESS_TEAM_NAME) - Check network connectivity to Cloudflare
- Review logs for specific error messages
- If using only AUD, ensure the JWT contains a valid issuer claim
Users Not Being Created
Cause: Missing email claim in JWT or database issues.
Solutions:
- Check that your Cloudflare Access application is configured to include email in JWT
- Verify Django database migrations are up to date
- Check Django user model permissions
Advanced Usage
Custom User Creation
If you need custom user creation logic, you can subclass the middleware:
from django_cf.middleware import CloudflareAccessMiddleware
from django.contrib.auth import get_user_model
User = get_user_model()
class CustomCloudflareAccessMiddleware(CloudflareAccessMiddleware):
def _get_or_create_user(self, email, name):
# Your custom user creation logic here
user, created = User.objects.get_or_create(
email=email,
defaults={
'username': email,
'first_name': name.split(' ')[0] if name else '',
'last_name': ' '.join(name.split(' ')[1:]) if name else '',
'is_active': True,
'is_staff': email.endswith('@yourcompany.com'), # Custom logic
}
)
return user
Multiple Applications
If you have multiple Cloudflare Access applications, you can configure different middleware instances or use environment-specific settings.
Development
Testing
When developing applications with this middleware, you may want to disable it in certain environments:
# settings.py
if DEBUG and not os.getenv('ENABLE_CF_ACCESS'):
# Remove CloudflareAccessMiddleware from MIDDLEWARE list
MIDDLEWARE = [m for m in MIDDLEWARE if 'CloudflareAccessMiddleware' not in m]
Local Development
For local development without Cloudflare Access:
- Set exempt paths to include your development URLs
- Use Django's built-in authentication for local testing
- Consider using environment variables to toggle the middleware
Examples
Complete, production-ready examples are available in the templates/ directory:
- D1 Template - Django on Cloudflare Workers with D1 database
- Durable Objects Template - Django on Cloudflare Workers with Durable Objects
Each template includes:
- Pre-configured
wrangler.jsonc - Worker entrypoint setup
- Django settings configured for the respective backend
- Management command endpoints for migrations and admin creation
- Complete deployment instructions
Limitations
- D1 Database:
- Transactions are disabled. All queries are final, and rollbacks are not available.
- A number of Django ORM features, particularly those relying on specific SQL functions (e.g., some used by the Django Admin), may not work as expected or at all. Django Admin functionality will be limited.
- Always refer to the official Django limitations for SQLite databases, as D1 is SQLite-compatible but has its own serverless characteristics.
- Durable Objects:
- Transactions are disabled. All queries are final, and rollbacks are not available.
- While powerful for stateful serverless applications, ensure you understand the consistency model and potential data storage costs associated with Durable Objects.
Contributing
See DEVELOPMENT.md for details on setting up a development environment and contributing to django-cf.
We welcome contributions! Please see our contributing guidelines and feel free to submit issues and pull requests.
Support
For issues and questions:
- Check the troubleshooting section above
- Review Cloudflare Access documentation
- Submit an issue on GitHub with detailed error logs and configuration (remove sensitive information)
License
This project is licensed under the MIT License. See the LICENSE file for details.
For complete, working examples of Django projects on Cloudflare Workers, refer to the templates in the templates/ directory.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_cf-0.2.10.tar.gz.
File metadata
- Download URL: django_cf-0.2.10.tar.gz
- Upload date:
- Size: 32.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8a072d3c0634c0a1580666c46a335837d1b59d7c3f7ca9c7eeb38e205be1c720
|
|
| MD5 |
2a4d556a87f5b4bfe8eebfecd3f18bd4
|
|
| BLAKE2b-256 |
c607f7d3fe7aebdb1a1a92239ac8a6ddc99c5bac5cf9a473e5c489e8f968a00b
|
Provenance
The following attestation bundles were made for django_cf-0.2.10.tar.gz:
Publisher:
release.yml on G4brym/django-cf
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_cf-0.2.10.tar.gz -
Subject digest:
8a072d3c0634c0a1580666c46a335837d1b59d7c3f7ca9c7eeb38e205be1c720 - Sigstore transparency entry: 1263773531
- Sigstore integration time:
-
Permalink:
G4brym/django-cf@59eecb8f0acc6e817471d9fa16399c149e97e7ab -
Branch / Tag:
refs/heads/main - Owner: https://github.com/G4brym
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@59eecb8f0acc6e817471d9fa16399c149e97e7ab -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_cf-0.2.10-py3-none-any.whl.
File metadata
- Download URL: django_cf-0.2.10-py3-none-any.whl
- Upload date:
- Size: 26.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eddba212eb068e3628f59fc0d099642c9249e4d6b54d12dab938c39561121d03
|
|
| MD5 |
8cd64ac7d54bb229b3cbfb5e1cd76d6d
|
|
| BLAKE2b-256 |
cdf62c83d19045a30015ee5d45a0e6ebeac7c282fd2a177db51a6497e8f1adc6
|
Provenance
The following attestation bundles were made for django_cf-0.2.10-py3-none-any.whl:
Publisher:
release.yml on G4brym/django-cf
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_cf-0.2.10-py3-none-any.whl -
Subject digest:
eddba212eb068e3628f59fc0d099642c9249e4d6b54d12dab938c39561121d03 - Sigstore transparency entry: 1263773632
- Sigstore integration time:
-
Permalink:
G4brym/django-cf@59eecb8f0acc6e817471d9fa16399c149e97e7ab -
Branch / Tag:
refs/heads/main - Owner: https://github.com/G4brym
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@59eecb8f0acc6e817471d9fa16399c149e97e7ab -
Trigger Event:
push
-
Statement type: