CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-django-oauth-toolkit

OAuth2 Provider for Django web applications with complete server functionality, token management, and authorization endpoints.

Pending
Overview
Eval results
Files

settings.mddocs/

Settings and Configuration

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.

Capabilities

Main Settings Object

Central configuration object providing access to all OAuth2 provider settings.

oauth2_settings: OAuth2ProviderSettings
"""
Main settings object for OAuth2 provider configuration.

Provides validated access to all OAuth2 settings with defaults.
Automatically reloads when Django settings change.

Usage:
    from oauth2_provider.settings import oauth2_settings
    
    # Access settings
    scopes = oauth2_settings.SCOPES
    token_expire = oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS
    
    # Get backend instances
    server_cls = oauth2_settings.OAUTH2_SERVER_CLASS
    validator = oauth2_settings.OAUTH2_VALIDATOR_CLASS()
"""

Core Settings

Basic OAuth2 server configuration settings.

# Django setting
OAUTH2_PROVIDER: Dict[str, Any] = {
    # Client Configuration
    'CLIENT_ID_GENERATOR_CLASS': 'oauth2_provider.generators.ClientIdGenerator',
    'CLIENT_SECRET_GENERATOR_CLASS': 'oauth2_provider.generators.ClientSecretGenerator', 
    'CLIENT_SECRET_GENERATOR_LENGTH': 128,
    
    # Token Configuration
    'ACCESS_TOKEN_EXPIRE_SECONDS': 3600,  # 1 hour
    'REFRESH_TOKEN_EXPIRE_SECONDS': 3600 * 24 * 7,  # 1 week
    'ACCESS_TOKEN_GENERATOR': None,  # Use default generator
    'REFRESH_TOKEN_GENERATOR': None,  # Use default generator
    
    # Server Classes
    'OAUTH2_SERVER_CLASS': 'oauthlib.oauth2.Server',
    'OIDC_SERVER_CLASS': 'oauthlib.openid.Server',
    'OAUTH2_VALIDATOR_CLASS': 'oauth2_provider.oauth2_validators.OAuth2Validator',
    'OAUTH2_BACKEND_CLASS': 'oauth2_provider.oauth2_backends.OAuthLibCore',
    
    # Scope Configuration
    'SCOPES': {
        'read': 'Read scope',
        'write': 'Write scope',
    },
    'DEFAULT_SCOPES': ['__all__'],
    'SCOPES_BACKEND_CLASS': 'oauth2_provider.scopes.SettingsScopes',
    'READ_SCOPE': 'read',
    'WRITE_SCOPE': 'write',
    
    # Additional server configuration
    'EXTRA_SERVER_KWARGS': {},
}
"""
Main OAuth2 provider configuration dictionary.

All settings are optional and have sensible defaults.
Settings are validated on access through oauth2_settings object.
"""

Model Configuration

Settings for customizing OAuth2 models and admin classes.

# Django model settings (outside OAUTH2_PROVIDER dict)
OAUTH2_PROVIDER_APPLICATION_MODEL: str = 'oauth2_provider.Application'
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL: str = 'oauth2_provider.AccessToken'
OAUTH2_PROVIDER_ID_TOKEN_MODEL: str = 'oauth2_provider.IDToken'
OAUTH2_PROVIDER_GRANT_MODEL: str = 'oauth2_provider.Grant'
OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL: str = 'oauth2_provider.RefreshToken'

# Admin class configuration (within OAUTH2_PROVIDER dict)
OAUTH2_PROVIDER = {
    'APPLICATION_ADMIN_CLASS': 'oauth2_provider.admin.ApplicationAdmin',
    'ACCESS_TOKEN_ADMIN_CLASS': 'oauth2_provider.admin.AccessTokenAdmin',
    'GRANT_ADMIN_CLASS': 'oauth2_provider.admin.GrantAdmin',
    'ID_TOKEN_ADMIN_CLASS': 'oauth2_provider.admin.IDTokenAdmin',
    'REFRESH_TOKEN_ADMIN_CLASS': 'oauth2_provider.admin.RefreshTokenAdmin',
}
"""
Model and admin class configuration.

Allows swapping OAuth2 models for custom implementations.
Admin classes can be customized for enhanced Django admin integration.
"""

Security and Validation Settings

Configuration for OAuth2 security features and validation behavior.

OAUTH2_PROVIDER = {
    # URI and Origin Validation
    'ALLOWED_REDIRECT_URI_SCHEMES': ['http', 'https'],
    'ALLOWED_SCHEMES': ['http', 'https'],
    
    # PKCE (Proof Key for Code Exchange)
    'PKCE_REQUIRED': False,  # Require PKCE for all clients
    
    # Authorization and Token Behavior
    'AUTHORIZATION_CODE_EXPIRE_SECONDS': 600,  # 10 minutes
    'ROTATE_REFRESH_TOKEN': True,  # Issue new refresh token on use
    'REFRESH_TOKEN_GRACE_PERIOD_SECONDS': 120,  # Grace period for rotation
    
    # Error Response Configuration
    'ERROR_RESPONSE_WITH_SCOPES': False,  # Include required scopes in error responses
    
    # Token Cleanup
    'CLEAR_EXPIRED_TOKENS_BATCH_SIZE': 10000,
    'CLEAR_EXPIRED_TOKENS_BATCH_INTERVAL': 2,  # seconds between batches
}
"""
Security and validation configuration options.

Controls URI validation, PKCE requirements, token rotation,
and error response behavior.
"""

OpenID Connect (OIDC) Settings

Configuration for OpenID Connect features and ID tokens.

OAUTH2_PROVIDER = {
    # OIDC Enable/Disable
    'OIDC_ENABLED': True,
    
    # ID Token Configuration
    'ID_TOKEN_EXPIRE_SECONDS': 3600,  # 1 hour
    
    # Signing Configuration
    'OIDC_RSA_PRIVATE_KEY': '''-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA...
-----END RSA PRIVATE KEY-----''',
    
    # OIDC Endpoints
    'OIDC_USERINFO_ENDPOINT_RESPONSE_TYPE': 'application/json',
    'OIDC_ISSUER': 'https://yourdomain.com',  # Issuer identifier
    
    # Claims and Scopes
    'OIDC_USERINFO_CLAIMS': {
        'sub': lambda user: str(user.pk),
        'name': lambda user: user.get_full_name(),
        'email': lambda user: user.email,
        'email_verified': lambda user: True,
    },
}
"""
OpenID Connect specific configuration.

Controls ID token behavior, signing keys, userinfo claims,
and OIDC endpoint responses.
"""

Advanced Configuration

Advanced settings for customizing OAuth2 server behavior.

OAUTH2_PROVIDER = {
    # Custom Backend Configuration
    'REQUEST_APPROVAL_PROMPT': 'auto',  # 'force', 'auto', or 'none'
    
    # Token Introspection
    'INTROSPECTION_ENDPOINT_AUTHENTICATION_REQUIRED': True,
    
    # Resource Server Configuration
    'RESOURCE_SERVER_INTROSPECTION_URL': None,  # External introspection endpoint
    'RESOURCE_SERVER_AUTH_TOKEN': None,  # Token for external introspection
    
    # Custom Validators and Generators
    'ACCESS_TOKEN_GENERATOR_CLASS': 'oauth2_provider.generators.BaseHashGenerator',
    'REFRESH_TOKEN_GENERATOR_CLASS': 'oauth2_provider.generators.BaseHashGenerator',
    
    # Application Model Configuration
    'APPLICATION_MODEL_ABSTRACT': True,  # Use abstract base model
    
    # Custom Exception Handling
    'EXCEPTION_HANDLER': None,  # Custom exception handler function
}
"""
Advanced configuration options for specialized use cases.

Includes custom backends, external resource servers,
and advanced security configurations.
"""

Usage Examples

Basic Configuration

# settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'oauth2_provider',
    # ... your apps
]

MIDDLEWARE = [
    'oauth2_provider.middleware.OAuth2TokenMiddleware',
    # ... other middleware
]

# Basic OAuth2 configuration
OAUTH2_PROVIDER = {
    'SCOPES': {
        'read': 'Read access to API',
        'write': 'Write access to API',
        'admin': 'Administrative access',
    },
    'ACCESS_TOKEN_EXPIRE_SECONDS': 3600,  # 1 hour
    'REFRESH_TOKEN_EXPIRE_SECONDS': 3600 * 24 * 7,  # 1 week
}

Production Configuration

# settings.py - Production settings
OAUTH2_PROVIDER = {
    # Security settings
    'ALLOWED_REDIRECT_URI_SCHEMES': ['https'],  # HTTPS only
    'PKCE_REQUIRED': True,  # Require PKCE for security
    'ERROR_RESPONSE_WITH_SCOPES': False,  # Don't leak scope info
    
    # Token configuration
    'ACCESS_TOKEN_EXPIRE_SECONDS': 1800,  # 30 minutes
    'REFRESH_TOKEN_EXPIRE_SECONDS': 3600 * 24 * 30,  # 30 days
    'ROTATE_REFRESH_TOKEN': True,
    
    # Cleanup configuration
    'CLEAR_EXPIRED_TOKENS_BATCH_SIZE': 50000,
    'CLEAR_EXPIRED_TOKENS_BATCH_INTERVAL': 1,
    
    # Comprehensive scopes
    'SCOPES': {
        'read': 'Read access to your data',
        'write': 'Write access to your data',
        'profile': 'Access to your profile information',
        'email': 'Access to your email address',
        'admin': 'Administrative access (restricted)',
    },
}

# OIDC configuration for production
OAUTH2_PROVIDER.update({
    'OIDC_ENABLED': True,
    'OIDC_RSA_PRIVATE_KEY': os.environ.get('OIDC_RSA_PRIVATE_KEY'),
    'OIDC_ISSUER': 'https://api.yourcompany.com',
    'ID_TOKEN_EXPIRE_SECONDS': 3600,
})

Custom Model Configuration

# models.py - Custom OAuth2 models
from oauth2_provider.models import AbstractApplication, AbstractAccessToken

class CustomApplication(AbstractApplication):
    """Custom application model with additional fields"""
    description = models.TextField(blank=True)
    logo_url = models.URLField(blank=True)
    website_url = models.URLField(blank=True)
    
    class Meta(AbstractApplication.Meta):
        swappable = 'OAUTH2_PROVIDER_APPLICATION_MODEL'

class CustomAccessToken(AbstractAccessToken):
    """Custom access token with tracking"""
    last_used = models.DateTimeField(null=True, blank=True)
    usage_count = models.PositiveIntegerField(default=0)
    
    class Meta(AbstractAccessToken.Meta):
        swappable = 'OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL'

# settings.py
OAUTH2_PROVIDER_APPLICATION_MODEL = 'myapp.CustomApplication'
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = 'myapp.CustomAccessToken'

Custom Generators and Validators

# generators.py - Custom token generators
from oauth2_provider.generators import BaseHashGenerator
import secrets
import string

class SecureTokenGenerator(BaseHashGenerator):
    """Custom secure token generator"""
    
    def hash(self):
        """Generate cryptographically secure token"""
        alphabet = string.ascii_letters + string.digits
        return ''.join(secrets.choice(alphabet) for _ in range(64))

# validators.py - Custom OAuth2 validator
from oauth2_provider.oauth2_validators import OAuth2Validator

class CustomOAuth2Validator(OAuth2Validator):
    """Custom validator with additional business logic"""
    
    def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
        """Custom scope validation"""
        # Add business logic here
        return super().validate_scopes(client_id, scopes, client, request, *args, **kwargs)
    
    def validate_redirect_uri(self, client_id, redirect_uri, request, *args, **kwargs):
        """Custom redirect URI validation"""
        # Add custom validation logic
        return super().validate_redirect_uri(client_id, redirect_uri, request, *args, **kwargs)

# settings.py
OAUTH2_PROVIDER = {
    'ACCESS_TOKEN_GENERATOR_CLASS': 'myapp.generators.SecureTokenGenerator',
    'OAUTH2_VALIDATOR_CLASS': 'myapp.validators.CustomOAuth2Validator',
}

Scope Backend Customization

# scopes.py - Custom scope backend
from oauth2_provider.scopes import BaseScopes

class DatabaseScopes(BaseScopes):
    """Scope backend that loads scopes from database"""
    
    def get_all_scopes(self):
        """Load scopes from database"""
        from myapp.models import OAuth2Scope
        scopes = {}
        for scope in OAuth2Scope.objects.filter(active=True):
            scopes[scope.name] = scope.description
        return scopes
    
    def get_available_scopes(self, application=None, request=None, *args, **kwargs):
        """Get scopes available for specific application"""
        all_scopes = self.get_all_scopes()
        if application and hasattr(application, 'allowed_scopes'):
            # Filter by application-specific scopes
            allowed = application.allowed_scopes.split()
            return {k: v for k, v in all_scopes.items() if k in allowed}
        return all_scopes
    
    def get_default_scopes(self, application=None, request=None, *args, **kwargs):
        """Get default scopes for application"""
        if application and hasattr(application, 'default_scopes'):
            return application.default_scopes.split()
        return ['read']

# settings.py
OAUTH2_PROVIDER = {
    'SCOPES_BACKEND_CLASS': 'myapp.scopes.DatabaseScopes',
}

Environment-Based Configuration

# settings.py - Environment-based configuration
import os
from datetime import timedelta

# Base configuration
OAUTH2_PROVIDER = {
    'SCOPES': {
        'read': 'Read access',
        'write': 'Write access',
        'admin': 'Admin access',
    },
}

# Environment-specific overrides
if os.environ.get('DJANGO_ENV') == 'production':
    OAUTH2_PROVIDER.update({
        'ACCESS_TOKEN_EXPIRE_SECONDS': 1800,  # 30 minutes
        'REFRESH_TOKEN_EXPIRE_SECONDS': timedelta(days=30).total_seconds(),
        'ALLOWED_REDIRECT_URI_SCHEMES': ['https'],
        'PKCE_REQUIRED': True,
        'OIDC_ENABLED': True,
        'OIDC_RSA_PRIVATE_KEY': os.environ.get('OIDC_PRIVATE_KEY'),
        'OIDC_ISSUER': os.environ.get('OIDC_ISSUER'),
    })
elif os.environ.get('DJANGO_ENV') == 'development':
    OAUTH2_PROVIDER.update({
        'ACCESS_TOKEN_EXPIRE_SECONDS': 3600 * 24,  # 24 hours (development)
        'REFRESH_TOKEN_EXPIRE_SECONDS': 3600 * 24 * 365,  # 1 year
        'ALLOWED_REDIRECT_URI_SCHEMES': ['http', 'https'],  # Allow HTTP in dev
    })

# Load sensitive settings from environment
if os.environ.get('OAUTH2_CLIENT_SECRET_LENGTH'):
    OAUTH2_PROVIDER['CLIENT_SECRET_GENERATOR_LENGTH'] = int(
        os.environ.get('OAUTH2_CLIENT_SECRET_LENGTH')
    )

Multi-Tenant Configuration

# settings.py - Multi-tenant setup
from oauth2_provider.settings import oauth2_settings

class TenantOAuth2Settings:
    """Tenant-specific OAuth2 settings"""
    
    def __init__(self, tenant):
        self.tenant = tenant
        self._cache = {}
    
    def get_scopes(self):
        """Get scopes for specific tenant"""
        if 'scopes' not in self._cache:
            # Load tenant-specific scopes
            self._cache['scopes'] = self.tenant.oauth_scopes or oauth2_settings.SCOPES
        return self._cache['scopes']
    
    def get_token_lifetime(self):
        """Get token lifetime for tenant"""
        return self.tenant.token_lifetime or oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS

# Usage in views
def get_tenant_oauth_settings(request):
    """Get OAuth2 settings for current tenant"""
    tenant = getattr(request, 'tenant', None)
    if tenant:
        return TenantOAuth2Settings(tenant)
    return oauth2_settings

Settings Validation and Testing

# tests.py - Settings validation
from django.test import TestCase, override_settings
from oauth2_provider.settings import oauth2_settings

class OAuth2SettingsTest(TestCase):
    """Test OAuth2 settings validation"""
    
    def test_default_settings(self):
        """Test default settings are valid"""
        self.assertIsNotNone(oauth2_settings.SCOPES)
        self.assertGreater(oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS, 0)
    
    @override_settings(OAUTH2_PROVIDER={'ACCESS_TOKEN_EXPIRE_SECONDS': 0})
    def test_invalid_token_lifetime(self):
        """Test handling of invalid token lifetime"""
        # Settings should handle validation
        self.assertGreater(oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS, 0)
    
    @override_settings(OAUTH2_PROVIDER={'SCOPES': {'read': 'Read', 'write': 'Write'}})
    def test_custom_scopes(self):
        """Test custom scope configuration"""
        scopes = oauth2_settings.SCOPES
        self.assertIn('read', scopes)
        self.assertIn('write', scopes)
        self.assertEqual(scopes['read'], 'Read')

# management/commands/validate_oauth_settings.py
from django.core.management.base import BaseCommand
from oauth2_provider.settings import oauth2_settings

class Command(BaseCommand):
    """Validate OAuth2 settings"""
    help = 'Validate OAuth2 provider settings'
    
    def handle(self, *args, **options):
        """Validate all OAuth2 settings"""
        errors = []
        
        # Validate token lifetimes
        if oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS <= 0:
            errors.append('ACCESS_TOKEN_EXPIRE_SECONDS must be positive')
            
        # Validate scopes
        if not oauth2_settings.SCOPES:
            errors.append('SCOPES setting cannot be empty')
            
        # Validate OIDC settings if enabled
        if oauth2_settings.OIDC_ENABLED:
            if not oauth2_settings.OIDC_RSA_PRIVATE_KEY:
                errors.append('OIDC_RSA_PRIVATE_KEY required when OIDC is enabled')
                
        if errors:
            self.stdout.write(self.style.ERROR('OAuth2 settings validation failed:'))
            for error in errors:
                self.stdout.write(self.style.ERROR(f'  - {error}'))
        else:
            self.stdout.write(self.style.SUCCESS('OAuth2 settings validation passed'))

Install with Tessl CLI

npx tessl i tessl/pypi-django-oauth-toolkit

docs

drf-integration.md

index.md

management-commands.md

management-views.md

models.md

oauth2-endpoints.md

oidc.md

settings.md

view-protection.md

tile.json