0
# Settings and Configuration
1
2
Django OAuth Toolkit provides comprehensive configuration through Django settings. All OAuth2 provider settings are namespaced under `OAUTH2_PROVIDER` with sensible defaults and extensive customization options.
3
4
## Capabilities
5
6
### Main Settings Object
7
8
Central configuration object providing access to all OAuth2 provider settings.
9
10
```python { .api }
11
oauth2_settings: OAuth2ProviderSettings
12
"""
13
Main settings object for OAuth2 provider configuration.
14
15
Provides validated access to all OAuth2 settings with defaults.
16
Automatically reloads when Django settings change.
17
18
Usage:
19
from oauth2_provider.settings import oauth2_settings
20
21
# Access settings
22
scopes = oauth2_settings.SCOPES
23
token_expire = oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS
24
25
# Get backend instances
26
server_cls = oauth2_settings.OAUTH2_SERVER_CLASS
27
validator = oauth2_settings.OAUTH2_VALIDATOR_CLASS()
28
"""
29
```
30
31
### Core Settings
32
33
Basic OAuth2 server configuration settings.
34
35
```python { .api }
36
# Django setting
37
OAUTH2_PROVIDER: Dict[str, Any] = {
38
# Client Configuration
39
'CLIENT_ID_GENERATOR_CLASS': 'oauth2_provider.generators.ClientIdGenerator',
40
'CLIENT_SECRET_GENERATOR_CLASS': 'oauth2_provider.generators.ClientSecretGenerator',
41
'CLIENT_SECRET_GENERATOR_LENGTH': 128,
42
43
# Token Configuration
44
'ACCESS_TOKEN_EXPIRE_SECONDS': 3600, # 1 hour
45
'REFRESH_TOKEN_EXPIRE_SECONDS': 3600 * 24 * 7, # 1 week
46
'ACCESS_TOKEN_GENERATOR': None, # Use default generator
47
'REFRESH_TOKEN_GENERATOR': None, # Use default generator
48
49
# Server Classes
50
'OAUTH2_SERVER_CLASS': 'oauthlib.oauth2.Server',
51
'OIDC_SERVER_CLASS': 'oauthlib.openid.Server',
52
'OAUTH2_VALIDATOR_CLASS': 'oauth2_provider.oauth2_validators.OAuth2Validator',
53
'OAUTH2_BACKEND_CLASS': 'oauth2_provider.oauth2_backends.OAuthLibCore',
54
55
# Scope Configuration
56
'SCOPES': {
57
'read': 'Read scope',
58
'write': 'Write scope',
59
},
60
'DEFAULT_SCOPES': ['__all__'],
61
'SCOPES_BACKEND_CLASS': 'oauth2_provider.scopes.SettingsScopes',
62
'READ_SCOPE': 'read',
63
'WRITE_SCOPE': 'write',
64
65
# Additional server configuration
66
'EXTRA_SERVER_KWARGS': {},
67
}
68
"""
69
Main OAuth2 provider configuration dictionary.
70
71
All settings are optional and have sensible defaults.
72
Settings are validated on access through oauth2_settings object.
73
"""
74
```
75
76
### Model Configuration
77
78
Settings for customizing OAuth2 models and admin classes.
79
80
```python { .api }
81
# Django model settings (outside OAUTH2_PROVIDER dict)
82
OAUTH2_PROVIDER_APPLICATION_MODEL: str = 'oauth2_provider.Application'
83
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL: str = 'oauth2_provider.AccessToken'
84
OAUTH2_PROVIDER_ID_TOKEN_MODEL: str = 'oauth2_provider.IDToken'
85
OAUTH2_PROVIDER_GRANT_MODEL: str = 'oauth2_provider.Grant'
86
OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL: str = 'oauth2_provider.RefreshToken'
87
88
# Admin class configuration (within OAUTH2_PROVIDER dict)
89
OAUTH2_PROVIDER = {
90
'APPLICATION_ADMIN_CLASS': 'oauth2_provider.admin.ApplicationAdmin',
91
'ACCESS_TOKEN_ADMIN_CLASS': 'oauth2_provider.admin.AccessTokenAdmin',
92
'GRANT_ADMIN_CLASS': 'oauth2_provider.admin.GrantAdmin',
93
'ID_TOKEN_ADMIN_CLASS': 'oauth2_provider.admin.IDTokenAdmin',
94
'REFRESH_TOKEN_ADMIN_CLASS': 'oauth2_provider.admin.RefreshTokenAdmin',
95
}
96
"""
97
Model and admin class configuration.
98
99
Allows swapping OAuth2 models for custom implementations.
100
Admin classes can be customized for enhanced Django admin integration.
101
"""
102
```
103
104
### Security and Validation Settings
105
106
Configuration for OAuth2 security features and validation behavior.
107
108
```python { .api }
109
OAUTH2_PROVIDER = {
110
# URI and Origin Validation
111
'ALLOWED_REDIRECT_URI_SCHEMES': ['http', 'https'],
112
'ALLOWED_SCHEMES': ['http', 'https'],
113
114
# PKCE (Proof Key for Code Exchange)
115
'PKCE_REQUIRED': False, # Require PKCE for all clients
116
117
# Authorization and Token Behavior
118
'AUTHORIZATION_CODE_EXPIRE_SECONDS': 600, # 10 minutes
119
'ROTATE_REFRESH_TOKEN': True, # Issue new refresh token on use
120
'REFRESH_TOKEN_GRACE_PERIOD_SECONDS': 120, # Grace period for rotation
121
122
# Error Response Configuration
123
'ERROR_RESPONSE_WITH_SCOPES': False, # Include required scopes in error responses
124
125
# Token Cleanup
126
'CLEAR_EXPIRED_TOKENS_BATCH_SIZE': 10000,
127
'CLEAR_EXPIRED_TOKENS_BATCH_INTERVAL': 2, # seconds between batches
128
}
129
"""
130
Security and validation configuration options.
131
132
Controls URI validation, PKCE requirements, token rotation,
133
and error response behavior.
134
"""
135
```
136
137
### OpenID Connect (OIDC) Settings
138
139
Configuration for OpenID Connect features and ID tokens.
140
141
```python { .api }
142
OAUTH2_PROVIDER = {
143
# OIDC Enable/Disable
144
'OIDC_ENABLED': True,
145
146
# ID Token Configuration
147
'ID_TOKEN_EXPIRE_SECONDS': 3600, # 1 hour
148
149
# Signing Configuration
150
'OIDC_RSA_PRIVATE_KEY': '''-----BEGIN RSA PRIVATE KEY-----
151
MIIEowIBAAKCAQEA...
152
-----END RSA PRIVATE KEY-----''',
153
154
# OIDC Endpoints
155
'OIDC_USERINFO_ENDPOINT_RESPONSE_TYPE': 'application/json',
156
'OIDC_ISSUER': 'https://yourdomain.com', # Issuer identifier
157
158
# Claims and Scopes
159
'OIDC_USERINFO_CLAIMS': {
160
'sub': lambda user: str(user.pk),
161
'name': lambda user: user.get_full_name(),
162
'email': lambda user: user.email,
163
'email_verified': lambda user: True,
164
},
165
}
166
"""
167
OpenID Connect specific configuration.
168
169
Controls ID token behavior, signing keys, userinfo claims,
170
and OIDC endpoint responses.
171
"""
172
```
173
174
### Advanced Configuration
175
176
Advanced settings for customizing OAuth2 server behavior.
177
178
```python { .api }
179
OAUTH2_PROVIDER = {
180
# Custom Backend Configuration
181
'REQUEST_APPROVAL_PROMPT': 'auto', # 'force', 'auto', or 'none'
182
183
# Token Introspection
184
'INTROSPECTION_ENDPOINT_AUTHENTICATION_REQUIRED': True,
185
186
# Resource Server Configuration
187
'RESOURCE_SERVER_INTROSPECTION_URL': None, # External introspection endpoint
188
'RESOURCE_SERVER_AUTH_TOKEN': None, # Token for external introspection
189
190
# Custom Validators and Generators
191
'ACCESS_TOKEN_GENERATOR_CLASS': 'oauth2_provider.generators.BaseHashGenerator',
192
'REFRESH_TOKEN_GENERATOR_CLASS': 'oauth2_provider.generators.BaseHashGenerator',
193
194
# Application Model Configuration
195
'APPLICATION_MODEL_ABSTRACT': True, # Use abstract base model
196
197
# Custom Exception Handling
198
'EXCEPTION_HANDLER': None, # Custom exception handler function
199
}
200
"""
201
Advanced configuration options for specialized use cases.
202
203
Includes custom backends, external resource servers,
204
and advanced security configurations.
205
"""
206
```
207
208
## Usage Examples
209
210
### Basic Configuration
211
212
```python
213
# settings.py
214
INSTALLED_APPS = [
215
'django.contrib.admin',
216
'django.contrib.auth',
217
'django.contrib.contenttypes',
218
'oauth2_provider',
219
# ... your apps
220
]
221
222
MIDDLEWARE = [
223
'oauth2_provider.middleware.OAuth2TokenMiddleware',
224
# ... other middleware
225
]
226
227
# Basic OAuth2 configuration
228
OAUTH2_PROVIDER = {
229
'SCOPES': {
230
'read': 'Read access to API',
231
'write': 'Write access to API',
232
'admin': 'Administrative access',
233
},
234
'ACCESS_TOKEN_EXPIRE_SECONDS': 3600, # 1 hour
235
'REFRESH_TOKEN_EXPIRE_SECONDS': 3600 * 24 * 7, # 1 week
236
}
237
```
238
239
### Production Configuration
240
241
```python
242
# settings.py - Production settings
243
OAUTH2_PROVIDER = {
244
# Security settings
245
'ALLOWED_REDIRECT_URI_SCHEMES': ['https'], # HTTPS only
246
'PKCE_REQUIRED': True, # Require PKCE for security
247
'ERROR_RESPONSE_WITH_SCOPES': False, # Don't leak scope info
248
249
# Token configuration
250
'ACCESS_TOKEN_EXPIRE_SECONDS': 1800, # 30 minutes
251
'REFRESH_TOKEN_EXPIRE_SECONDS': 3600 * 24 * 30, # 30 days
252
'ROTATE_REFRESH_TOKEN': True,
253
254
# Cleanup configuration
255
'CLEAR_EXPIRED_TOKENS_BATCH_SIZE': 50000,
256
'CLEAR_EXPIRED_TOKENS_BATCH_INTERVAL': 1,
257
258
# Comprehensive scopes
259
'SCOPES': {
260
'read': 'Read access to your data',
261
'write': 'Write access to your data',
262
'profile': 'Access to your profile information',
263
'email': 'Access to your email address',
264
'admin': 'Administrative access (restricted)',
265
},
266
}
267
268
# OIDC configuration for production
269
OAUTH2_PROVIDER.update({
270
'OIDC_ENABLED': True,
271
'OIDC_RSA_PRIVATE_KEY': os.environ.get('OIDC_RSA_PRIVATE_KEY'),
272
'OIDC_ISSUER': 'https://api.yourcompany.com',
273
'ID_TOKEN_EXPIRE_SECONDS': 3600,
274
})
275
```
276
277
### Custom Model Configuration
278
279
```python
280
# models.py - Custom OAuth2 models
281
from oauth2_provider.models import AbstractApplication, AbstractAccessToken
282
283
class CustomApplication(AbstractApplication):
284
"""Custom application model with additional fields"""
285
description = models.TextField(blank=True)
286
logo_url = models.URLField(blank=True)
287
website_url = models.URLField(blank=True)
288
289
class Meta(AbstractApplication.Meta):
290
swappable = 'OAUTH2_PROVIDER_APPLICATION_MODEL'
291
292
class CustomAccessToken(AbstractAccessToken):
293
"""Custom access token with tracking"""
294
last_used = models.DateTimeField(null=True, blank=True)
295
usage_count = models.PositiveIntegerField(default=0)
296
297
class Meta(AbstractAccessToken.Meta):
298
swappable = 'OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL'
299
300
# settings.py
301
OAUTH2_PROVIDER_APPLICATION_MODEL = 'myapp.CustomApplication'
302
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = 'myapp.CustomAccessToken'
303
```
304
305
### Custom Generators and Validators
306
307
```python
308
# generators.py - Custom token generators
309
from oauth2_provider.generators import BaseHashGenerator
310
import secrets
311
import string
312
313
class SecureTokenGenerator(BaseHashGenerator):
314
"""Custom secure token generator"""
315
316
def hash(self):
317
"""Generate cryptographically secure token"""
318
alphabet = string.ascii_letters + string.digits
319
return ''.join(secrets.choice(alphabet) for _ in range(64))
320
321
# validators.py - Custom OAuth2 validator
322
from oauth2_provider.oauth2_validators import OAuth2Validator
323
324
class CustomOAuth2Validator(OAuth2Validator):
325
"""Custom validator with additional business logic"""
326
327
def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
328
"""Custom scope validation"""
329
# Add business logic here
330
return super().validate_scopes(client_id, scopes, client, request, *args, **kwargs)
331
332
def validate_redirect_uri(self, client_id, redirect_uri, request, *args, **kwargs):
333
"""Custom redirect URI validation"""
334
# Add custom validation logic
335
return super().validate_redirect_uri(client_id, redirect_uri, request, *args, **kwargs)
336
337
# settings.py
338
OAUTH2_PROVIDER = {
339
'ACCESS_TOKEN_GENERATOR_CLASS': 'myapp.generators.SecureTokenGenerator',
340
'OAUTH2_VALIDATOR_CLASS': 'myapp.validators.CustomOAuth2Validator',
341
}
342
```
343
344
### Scope Backend Customization
345
346
```python
347
# scopes.py - Custom scope backend
348
from oauth2_provider.scopes import BaseScopes
349
350
class DatabaseScopes(BaseScopes):
351
"""Scope backend that loads scopes from database"""
352
353
def get_all_scopes(self):
354
"""Load scopes from database"""
355
from myapp.models import OAuth2Scope
356
scopes = {}
357
for scope in OAuth2Scope.objects.filter(active=True):
358
scopes[scope.name] = scope.description
359
return scopes
360
361
def get_available_scopes(self, application=None, request=None, *args, **kwargs):
362
"""Get scopes available for specific application"""
363
all_scopes = self.get_all_scopes()
364
if application and hasattr(application, 'allowed_scopes'):
365
# Filter by application-specific scopes
366
allowed = application.allowed_scopes.split()
367
return {k: v for k, v in all_scopes.items() if k in allowed}
368
return all_scopes
369
370
def get_default_scopes(self, application=None, request=None, *args, **kwargs):
371
"""Get default scopes for application"""
372
if application and hasattr(application, 'default_scopes'):
373
return application.default_scopes.split()
374
return ['read']
375
376
# settings.py
377
OAUTH2_PROVIDER = {
378
'SCOPES_BACKEND_CLASS': 'myapp.scopes.DatabaseScopes',
379
}
380
```
381
382
### Environment-Based Configuration
383
384
```python
385
# settings.py - Environment-based configuration
386
import os
387
from datetime import timedelta
388
389
# Base configuration
390
OAUTH2_PROVIDER = {
391
'SCOPES': {
392
'read': 'Read access',
393
'write': 'Write access',
394
'admin': 'Admin access',
395
},
396
}
397
398
# Environment-specific overrides
399
if os.environ.get('DJANGO_ENV') == 'production':
400
OAUTH2_PROVIDER.update({
401
'ACCESS_TOKEN_EXPIRE_SECONDS': 1800, # 30 minutes
402
'REFRESH_TOKEN_EXPIRE_SECONDS': timedelta(days=30).total_seconds(),
403
'ALLOWED_REDIRECT_URI_SCHEMES': ['https'],
404
'PKCE_REQUIRED': True,
405
'OIDC_ENABLED': True,
406
'OIDC_RSA_PRIVATE_KEY': os.environ.get('OIDC_PRIVATE_KEY'),
407
'OIDC_ISSUER': os.environ.get('OIDC_ISSUER'),
408
})
409
elif os.environ.get('DJANGO_ENV') == 'development':
410
OAUTH2_PROVIDER.update({
411
'ACCESS_TOKEN_EXPIRE_SECONDS': 3600 * 24, # 24 hours (development)
412
'REFRESH_TOKEN_EXPIRE_SECONDS': 3600 * 24 * 365, # 1 year
413
'ALLOWED_REDIRECT_URI_SCHEMES': ['http', 'https'], # Allow HTTP in dev
414
})
415
416
# Load sensitive settings from environment
417
if os.environ.get('OAUTH2_CLIENT_SECRET_LENGTH'):
418
OAUTH2_PROVIDER['CLIENT_SECRET_GENERATOR_LENGTH'] = int(
419
os.environ.get('OAUTH2_CLIENT_SECRET_LENGTH')
420
)
421
```
422
423
### Multi-Tenant Configuration
424
425
```python
426
# settings.py - Multi-tenant setup
427
from oauth2_provider.settings import oauth2_settings
428
429
class TenantOAuth2Settings:
430
"""Tenant-specific OAuth2 settings"""
431
432
def __init__(self, tenant):
433
self.tenant = tenant
434
self._cache = {}
435
436
def get_scopes(self):
437
"""Get scopes for specific tenant"""
438
if 'scopes' not in self._cache:
439
# Load tenant-specific scopes
440
self._cache['scopes'] = self.tenant.oauth_scopes or oauth2_settings.SCOPES
441
return self._cache['scopes']
442
443
def get_token_lifetime(self):
444
"""Get token lifetime for tenant"""
445
return self.tenant.token_lifetime or oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS
446
447
# Usage in views
448
def get_tenant_oauth_settings(request):
449
"""Get OAuth2 settings for current tenant"""
450
tenant = getattr(request, 'tenant', None)
451
if tenant:
452
return TenantOAuth2Settings(tenant)
453
return oauth2_settings
454
```
455
456
### Settings Validation and Testing
457
458
```python
459
# tests.py - Settings validation
460
from django.test import TestCase, override_settings
461
from oauth2_provider.settings import oauth2_settings
462
463
class OAuth2SettingsTest(TestCase):
464
"""Test OAuth2 settings validation"""
465
466
def test_default_settings(self):
467
"""Test default settings are valid"""
468
self.assertIsNotNone(oauth2_settings.SCOPES)
469
self.assertGreater(oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS, 0)
470
471
@override_settings(OAUTH2_PROVIDER={'ACCESS_TOKEN_EXPIRE_SECONDS': 0})
472
def test_invalid_token_lifetime(self):
473
"""Test handling of invalid token lifetime"""
474
# Settings should handle validation
475
self.assertGreater(oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS, 0)
476
477
@override_settings(OAUTH2_PROVIDER={'SCOPES': {'read': 'Read', 'write': 'Write'}})
478
def test_custom_scopes(self):
479
"""Test custom scope configuration"""
480
scopes = oauth2_settings.SCOPES
481
self.assertIn('read', scopes)
482
self.assertIn('write', scopes)
483
self.assertEqual(scopes['read'], 'Read')
484
485
# management/commands/validate_oauth_settings.py
486
from django.core.management.base import BaseCommand
487
from oauth2_provider.settings import oauth2_settings
488
489
class Command(BaseCommand):
490
"""Validate OAuth2 settings"""
491
help = 'Validate OAuth2 provider settings'
492
493
def handle(self, *args, **options):
494
"""Validate all OAuth2 settings"""
495
errors = []
496
497
# Validate token lifetimes
498
if oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS <= 0:
499
errors.append('ACCESS_TOKEN_EXPIRE_SECONDS must be positive')
500
501
# Validate scopes
502
if not oauth2_settings.SCOPES:
503
errors.append('SCOPES setting cannot be empty')
504
505
# Validate OIDC settings if enabled
506
if oauth2_settings.OIDC_ENABLED:
507
if not oauth2_settings.OIDC_RSA_PRIVATE_KEY:
508
errors.append('OIDC_RSA_PRIVATE_KEY required when OIDC is enabled')
509
510
if errors:
511
self.stdout.write(self.style.ERROR('OAuth2 settings validation failed:'))
512
for error in errors:
513
self.stdout.write(self.style.ERROR(f' - {error}'))
514
else:
515
self.stdout.write(self.style.SUCCESS('OAuth2 settings validation passed'))
516
```