or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

drf-integration.mdindex.mdmanagement-commands.mdmanagement-views.mdmodels.mdoauth2-endpoints.mdoidc.mdsettings.mdview-protection.md

settings.mddocs/

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

```