Skip to content

Commit c7e0cdf

Browse files
committed
Update README.md
1 parent a01356c commit c7e0cdf

File tree

1 file changed

+291
-6
lines changed

1 file changed

+291
-6
lines changed

README.md

Lines changed: 291 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ This package provides integration with the following Cloudflare products:
1515
* **Cloudflare Workers**: Run your Django application on Cloudflare's serverless compute platform.
1616
* **Cloudflare D1**: Use Cloudflare's serverless SQL database as a backend for your Django application.
1717
* **Cloudflare Durable Objects**: Leverage stateful objects for applications requiring persistent state within Cloudflare Workers.
18+
* **Cloudflare Access Authentication**: Seamless authentication middleware for Cloudflare Access protected applications.
1819

19-
### 1. Cloudflare Workers Integration
20+
## Cloudflare Workers Integration
2021

2122
Run your Django application directly on Cloudflare Workers for edge performance and scalability.
2223

@@ -174,7 +175,7 @@ root
174175
Access `https://your-worker-url.com/__run_migrations__/` to trigger migrations.
175176
Access `https://your-worker-url.com/__create_admin__/` to trigger admin creation.
176177
177-
### 2. Cloudflare D1 Integration
178+
## Cloudflare D1 Integration
178179
179180
Use Cloudflare D1, a serverless SQL database, as your Django application's database.
180181
@@ -184,7 +185,7 @@ Use Cloudflare D1, a serverless SQL database, as your Django application's datab
184185
185186
There are two ways to connect to D1:
186187
187-
#### a) D1 Binding (Recommended for Workers)
188+
### a) D1 Binding (Recommended for Workers)
188189
189190
When your Django application is running on Cloudflare Workers, use D1 Bindings for the fastest access. The D1 database is made available to your worker via an environment binding.
190191
@@ -200,7 +201,7 @@ DATABASES = {
200201
```
201202
Ensure your `wrangler.toml` includes the D1 database binding (as shown in the Cloudflare Workers section).
202203

203-
#### b) D1 API (For Local Development or External Access)
204+
### b) D1 API (For Local Development or External Access)
204205

205206
Connect to D1 via its HTTP API. This method is suitable for local development (e.g., running migrations) or when accessing D1 from outside Cloudflare Workers.
206207

@@ -220,7 +221,7 @@ DATABASES = {
220221

221222
A simple tutorial for getting started with Django and D1 is [available here](https://massadas.com/posts/django-meets-cloudflare-d1/).
222223

223-
### 3. Cloudflare Durable Objects Integration
224+
## Cloudflare Durable Objects Integration
224225

225226
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.
226227

@@ -276,6 +277,280 @@ Utilize Durable Objects for stateful data persistence directly within your Cloud
276277
```
277278
The `django_cf.do_binding` engine will automatically use the Durable Object binding specified in your `wrangler.toml` that is associated with the `DjangoCFDurableObject` class. Ensure your `wrangler.toml` correctly binds a name to your `DjangoCFDurableObject` subclass.
278279

280+
## Cloudflare Access Authentication
281+
282+
Seamless authentication middleware for Cloudflare Access protected applications. The middleware is implemented using only Python standard library modules - no external dependencies required!
283+
284+
This middleware works on both self-hosted Django applications and Django hosted on Cloudflare Workers.
285+
286+
### Quick Start
287+
288+
#### 1. Add to INSTALLED_APPS
289+
290+
```python
291+
# settings.py
292+
INSTALLED_APPS = [
293+
# ... your other apps
294+
'django_cf',
295+
]
296+
```
297+
298+
#### 2. Configure Cloudflare Access Middleware
299+
300+
Add the middleware to your Django settings:
301+
302+
```python
303+
# settings.py
304+
MIDDLEWARE = [
305+
'django.middleware.security.SecurityMiddleware',
306+
'django.contrib.sessions.middleware.SessionMiddleware', # Must be before CloudflareAccessMiddleware
307+
'django.middleware.common.CommonMiddleware',
308+
'django.middleware.csrf.CsrfViewMiddleware',
309+
'django_cf.middleware.CloudflareAccessMiddleware',
310+
# ... other middleware (should be placed appropriately in your middleware stack)
311+
]
312+
```
313+
314+
#### 3. Configure Cloudflare Access Settings
315+
316+
Add the required settings to your Django configuration:
317+
318+
```python
319+
# settings.py
320+
321+
# Required settings (at least one must be provided)
322+
CLOUDFLARE_ACCESS_AUD = 'your-application-audience-tag' # Optional if team name is provided
323+
CLOUDFLARE_ACCESS_TEAM_NAME = 'yourteam' # Optional if AUD is provided
324+
325+
# Optional settings
326+
CLOUDFLARE_ACCESS_EXEMPT_PATHS = [
327+
'/health/',
328+
'/api/public/',
329+
'/admin/login/', # If you want to keep Django admin login
330+
]
331+
CLOUDFLARE_ACCESS_CACHE_TIMEOUT = 3600 # Cache timeout for public keys (seconds)
332+
```
333+
334+
### Required Settings (At Least One)
335+
336+
You must provide **at least one** of the following settings:
337+
338+
#### `CLOUDFLARE_ACCESS_AUD`
339+
Your Cloudflare Access Application Audience (AUD) tag. You can find this in your Cloudflare Zero Trust dashboard:
340+
1. Go to Access → Applications
341+
2. Select your application
342+
3. Copy the "Application Audience (AUD) Tag"
343+
344+
**When to use**: Provide this if you want strict audience validation or if you have multiple applications with the same team.
345+
346+
#### `CLOUDFLARE_ACCESS_TEAM_NAME`
347+
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`.
348+
349+
**When to use**: Provide this if you want simpler configuration or if you only have one application per team.
350+
351+
### Configuration Options
352+
353+
You can configure the middleware in three ways:
354+
355+
1. **Both AUD and Team Name** (Recommended for production):
356+
```python
357+
CLOUDFLARE_ACCESS_AUD = 'your-application-audience-tag'
358+
CLOUDFLARE_ACCESS_TEAM_NAME = 'yourteam'
359+
```
360+
361+
2. **Only AUD** (Team name extracted from JWT):
362+
```python
363+
CLOUDFLARE_ACCESS_AUD = 'your-application-audience-tag'
364+
```
365+
366+
3. **Only Team Name** (AUD validated but not enforced):
367+
```python
368+
CLOUDFLARE_ACCESS_TEAM_NAME = 'yourteam'
369+
```
370+
371+
### Optional Settings
372+
373+
#### `CLOUDFLARE_ACCESS_EXEMPT_PATHS`
374+
Default: `[]`
375+
376+
List of URL paths that should be exempt from Cloudflare Access authentication. Useful for:
377+
- Health check endpoints
378+
- Public API endpoints
379+
- Webhooks
380+
- Static file serving (if not handled by your web server)
381+
382+
Example:
383+
```python
384+
CLOUDFLARE_ACCESS_EXEMPT_PATHS = [
385+
'/health/',
386+
'/api/public/',
387+
'/webhooks/',
388+
'/static/', # If serving static files through Django
389+
]
390+
```
391+
392+
#### `CLOUDFLARE_ACCESS_CACHE_TIMEOUT`
393+
Default: `3600` (1 hour)
394+
395+
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.
396+
397+
### How It Works
398+
399+
#### Authentication Flow
400+
401+
1. **Request Processing**: For each incoming request, the middleware checks if the path is exempt from authentication
402+
2. **JWT Extraction**: Extracts the JWT token from:
403+
- `CF-Access-Jwt-Assertion` header (preferred)
404+
- `CF_Authorization` cookie (fallback)
405+
3. **Team Discovery**: If team name is not configured, extracts it from the JWT's issuer claim
406+
4. **Public Key Retrieval**: Fetches Cloudflare's public keys from the team's certs endpoint
407+
5. **Token Validation**: Validates the JWT against Cloudflare's public keys:
408+
- Validates token signature and expiration
409+
- Validates audience (AUD) if configured
410+
6. **User Management**:
411+
- Extracts email and name from JWT claims
412+
- Creates new Django user if doesn't exist
413+
- Updates existing user's name if changed
414+
- Logs the user into Django session
415+
7. **Response**: Proceeds with the request if authentication succeeds, returns 401 if it fails
416+
417+
#### User Creation
418+
419+
When a user authenticates for the first time:
420+
- Username is set to the user's email address
421+
- Email is extracted from JWT `email` claim
422+
- Name is split into first_name and last_name from JWT `name` claim
423+
- User is created with `is_active=True`
424+
425+
For existing users:
426+
- Name is updated if it has changed in Cloudflare
427+
- User is automatically logged in
428+
429+
### Security Considerations
430+
431+
#### Token Validation
432+
- All JWT tokens are validated against Cloudflare's public keys
433+
- Tokens are checked for expiration
434+
- Audience (AUD) is validated to ensure tokens are for your application
435+
436+
#### Caching
437+
- Public keys are cached to reduce API calls to Cloudflare
438+
- Cache keys are scoped to your team name
439+
- Failed key fetches are logged but don't crash the application
440+
441+
#### Error Handling
442+
- Authentication failures return 401 responses
443+
- Server errors return 500 responses
444+
- All errors are logged for debugging
445+
446+
### Logging
447+
448+
The middleware uses Django's logging system with logger name `django_cf.middleware`. To see debug information:
449+
450+
```python
451+
# settings.py
452+
LOGGING = {
453+
'version': 1,
454+
'disable_existing_loggers': False,
455+
'handlers': {
456+
'console': {
457+
'class': 'logging.StreamHandler',
458+
},
459+
},
460+
'loggers': {
461+
'django_cf.middleware': {
462+
'handlers': ['console'],
463+
'level': 'INFO',
464+
'propagate': True,
465+
},
466+
},
467+
}
468+
```
469+
470+
### Common Issues and Troubleshooting
471+
472+
#### 401 Unauthorized Responses
473+
474+
**Cause**: JWT token is missing, invalid, or expired.
475+
476+
**Solutions**:
477+
1. Ensure your application is behind Cloudflare Access
478+
2. Check that users are properly authenticated with Cloudflare Access
479+
3. If using `CLOUDFLARE_ACCESS_AUD`, verify it matches your application's AUD tag
480+
4. If using `CLOUDFLARE_ACCESS_TEAM_NAME`, check that it's correct
481+
5. Review middleware logs for specific validation errors
482+
483+
#### 500 Internal Server Error
484+
485+
**Cause**: Unable to fetch Cloudflare public keys or other configuration issues.
486+
487+
**Solutions**:
488+
1. Verify your team name is correct (if using `CLOUDFLARE_ACCESS_TEAM_NAME`)
489+
2. Check network connectivity to Cloudflare
490+
3. Review logs for specific error messages
491+
4. If using only AUD, ensure the JWT contains a valid issuer claim
492+
493+
#### Users Not Being Created
494+
495+
**Cause**: Missing email claim in JWT or database issues.
496+
497+
**Solutions**:
498+
1. Check that your Cloudflare Access application is configured to include email in JWT
499+
2. Verify Django database migrations are up to date
500+
3. Check Django user model permissions
501+
502+
### Advanced Usage
503+
504+
#### Custom User Creation
505+
506+
If you need custom user creation logic, you can subclass the middleware:
507+
508+
```python
509+
from django_cf.middleware import CloudflareAccessMiddleware
510+
from django.contrib.auth import get_user_model
511+
512+
User = get_user_model()
513+
514+
class CustomCloudflareAccessMiddleware(CloudflareAccessMiddleware):
515+
def _get_or_create_user(self, email, name):
516+
# Your custom user creation logic here
517+
user, created = User.objects.get_or_create(
518+
email=email,
519+
defaults={
520+
'username': email,
521+
'first_name': name.split(' ')[0] if name else '',
522+
'last_name': ' '.join(name.split(' ')[1:]) if name else '',
523+
'is_active': True,
524+
'is_staff': email.endswith('@yourcompany.com'), # Custom logic
525+
}
526+
)
527+
return user
528+
```
529+
530+
#### Multiple Applications
531+
532+
If you have multiple Cloudflare Access applications, you can configure different middleware instances or use environment-specific settings.
533+
534+
### Development
535+
536+
#### Testing
537+
538+
When developing applications with this middleware, you may want to disable it in certain environments:
539+
540+
```python
541+
# settings.py
542+
if DEBUG and not os.getenv('ENABLE_CF_ACCESS'):
543+
# Remove CloudflareAccessMiddleware from MIDDLEWARE list
544+
MIDDLEWARE = [m for m in MIDDLEWARE if 'CloudflareAccessMiddleware' not in m]
545+
```
546+
547+
#### Local Development
548+
549+
For local development without Cloudflare Access:
550+
1. Set exempt paths to include your development URLs
551+
2. Use Django's built-in authentication for local testing
552+
3. Consider using environment variables to toggle the middleware
553+
279554
## Limitations
280555

281556
* **D1 Database:**
@@ -285,13 +560,23 @@ Utilize Durable Objects for stateful data persistence directly within your Cloud
285560
* Always refer to the official [Django limitations for SQLite databases](https://docs.djangoproject.com/en/stable/ref/databases/#sqlite-notes), as D1 is SQLite-compatible but has its own serverless characteristics.
286561
* **Durable Objects:** While powerful for stateful serverless applications, ensure you understand the consistency model and potential data storage costs associated with Durable Objects.
287562

288-
## Development
563+
## Contributing
289564

290565
See [DEVELOPMENT.md](DEVELOPMENT.md) for details on setting up a development environment and contributing to `django-cf`.
291566

567+
We welcome contributions! Please see our contributing guidelines and feel free to submit issues and pull requests.
568+
569+
## Support
570+
571+
For issues and questions:
572+
1. Check the troubleshooting section above
573+
2. Review Cloudflare Access documentation
574+
3. Submit an issue on GitHub with detailed error logs and configuration (remove sensitive information)
575+
292576
## License
293577

294578
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
579+
295580
---
296581

297582
*For a more detailed example of a Django project structured for Cloudflare Workers with D1, check out relevant community projects or the `django-cf` examples if available.*

0 commit comments

Comments
 (0)