Authentication and Authorization

Integrating Social Authentication

Social authentication allows users to log in using their existing accounts from popular platforms like Google, Facebook, Twitter, and GitHub. Implementing social authentication improves user experience by reducing registration friction while maintaining security. Understanding how to integrate and manage social authentication is essential for modern web applications.

Integrating Social Authentication

Social authentication allows users to log in using their existing accounts from popular platforms like Google, Facebook, Twitter, and GitHub. Implementing social authentication improves user experience by reducing registration friction while maintaining security. Understanding how to integrate and manage social authentication is essential for modern web applications.

Understanding Social Authentication

OAuth 2.0 and OpenID Connect Basics

# Understanding OAuth 2.0 flow for social authentication
class SocialAuthenticationOverview:
    """Overview of social authentication concepts and flows"""
    
    @staticmethod
    def oauth2_flow_explanation():
        """Explain the OAuth 2.0 authorization code flow"""
        
        flow_steps = {
            'step_1': {
                'description': 'User clicks "Login with Provider" button',
                'action': 'Redirect to provider authorization URL',
                'example': 'https://accounts.google.com/oauth/authorize?client_id=...&redirect_uri=...&scope=...'
            },
            'step_2': {
                'description': 'User authorizes application on provider site',
                'action': 'Provider redirects back with authorization code',
                'example': 'https://yoursite.com/auth/callback?code=AUTH_CODE&state=STATE'
            },
            'step_3': {
                'description': 'Exchange authorization code for access token',
                'action': 'Backend makes POST request to provider token endpoint',
                'example': 'POST https://oauth2.googleapis.com/token'
            },
            'step_4': {
                'description': 'Use access token to get user information',
                'action': 'Make API request to get user profile',
                'example': 'GET https://www.googleapis.com/oauth2/v2/userinfo'
            },
            'step_5': {
                'description': 'Create or authenticate user in Django',
                'action': 'Create User object or authenticate existing user',
                'example': 'User.objects.get_or_create(email=user_info["email"])'
            }
        }
        
        return flow_steps
    
    @staticmethod
    def security_considerations():
        """Important security considerations for social auth"""
        
        considerations = {
            'state_parameter': {
                'purpose': 'Prevent CSRF attacks',
                'implementation': 'Generate random state, store in session, verify on callback',
                'importance': 'Critical'
            },
            'redirect_uri_validation': {
                'purpose': 'Prevent redirect attacks',
                'implementation': 'Whitelist allowed redirect URIs in provider settings',
                'importance': 'Critical'
            },
            'scope_limitation': {
                'purpose': 'Minimize data access',
                'implementation': 'Request only necessary scopes (email, profile)',
                'importance': 'High'
            },
            'token_security': {
                'purpose': 'Protect access tokens',
                'implementation': 'Store securely, use HTTPS, implement token refresh',
                'importance': 'High'
            },
            'account_linking': {
                'purpose': 'Handle multiple auth methods for same user',
                'implementation': 'Link accounts by email or allow user choice',
                'importance': 'Medium'
            }
        }
        
        return considerations

# Manual OAuth 2.0 implementation example
class ManualOAuth2Implementation:
    """Manual implementation of OAuth 2.0 flow"""
    
    def __init__(self, client_id, client_secret, redirect_uri):
        self.client_id = client_id
        self.client_secret = client_secret
        self.redirect_uri = redirect_uri
    
    def get_authorization_url(self, state=None):
        """Generate authorization URL for OAuth provider"""
        
        import urllib.parse
        import secrets
        
        # Generate state for CSRF protection
        if not state:
            state = secrets.token_urlsafe(32)
        
        params = {
            'client_id': self.client_id,
            'redirect_uri': self.redirect_uri,
            'scope': 'openid email profile',
            'response_type': 'code',
            'state': state,
        }
        
        # Example for Google OAuth
        base_url = 'https://accounts.google.com/o/oauth2/v2/auth'
        
        return f"{base_url}?{urllib.parse.urlencode(params)}", state
    
    def exchange_code_for_token(self, authorization_code):
        """Exchange authorization code for access token"""
        
        import requests
        
        token_url = 'https://oauth2.googleapis.com/token'
        
        data = {
            'client_id': self.client_id,
            'client_secret': self.client_secret,
            'code': authorization_code,
            'grant_type': 'authorization_code',
            'redirect_uri': self.redirect_uri,
        }
        
        response = requests.post(token_url, data=data)
        
        if response.status_code == 200:
            return response.json()
        else:
            raise Exception(f"Token exchange failed: {response.text}")
    
    def get_user_info(self, access_token):
        """Get user information using access token"""
        
        import requests
        
        headers = {
            'Authorization': f'Bearer {access_token}'
        }
        
        # Google userinfo endpoint
        response = requests.get(
            'https://www.googleapis.com/oauth2/v2/userinfo',
            headers=headers
        )
        
        if response.status_code == 200:
            return response.json()
        else:
            raise Exception(f"Failed to get user info: {response.text}")

# Django views for manual OAuth implementation
def social_login_view(request, provider):
    """Initiate social login"""
    
    if provider == 'google':
        oauth_client = ManualOAuth2Implementation(
            client_id=settings.GOOGLE_CLIENT_ID,
            client_secret=settings.GOOGLE_CLIENT_SECRET,
            redirect_uri=request.build_absolute_uri(reverse('social_callback', args=['google']))
        )
        
        auth_url, state = oauth_client.get_authorization_url()
        
        # Store state in session for verification
        request.session['oauth_state'] = state
        
        return redirect(auth_url)
    
    else:
        messages.error(request, f'Unsupported provider: {provider}')
        return redirect('login')

def social_callback_view(request, provider):
    """Handle OAuth callback"""
    
    # Verify state parameter
    received_state = request.GET.get('state')
    stored_state = request.session.get('oauth_state')
    
    if not received_state or received_state != stored_state:
        messages.error(request, 'Invalid state parameter. Possible CSRF attack.')
        return redirect('login')
    
    # Clean up state from session
    del request.session['oauth_state']
    
    # Get authorization code
    authorization_code = request.GET.get('code')
    
    if not authorization_code:
        error = request.GET.get('error', 'Unknown error')
        messages.error(request, f'Authorization failed: {error}')
        return redirect('login')
    
    try:
        if provider == 'google':
            oauth_client = ManualOAuth2Implementation(
                client_id=settings.GOOGLE_CLIENT_ID,
                client_secret=settings.GOOGLE_CLIENT_SECRET,
                redirect_uri=request.build_absolute_uri(reverse('social_callback', args=['google']))
            )
            
            # Exchange code for token
            token_data = oauth_client.exchange_code_for_token(authorization_code)
            access_token = token_data['access_token']
            
            # Get user information
            user_info = oauth_client.get_user_info(access_token)
            
            # Create or authenticate user
            user = create_or_authenticate_social_user(user_info, provider)
            
            if user:
                login(request, user)
                messages.success(request, f'Successfully logged in with {provider.title()}!')
                return redirect('dashboard')
            else:
                messages.error(request, 'Failed to create or authenticate user.')
                return redirect('login')
    
    except Exception as e:
        messages.error(request, f'Authentication failed: {str(e)}')
        return redirect('login')

def create_or_authenticate_social_user(user_info, provider):
    """Create or authenticate user from social provider data"""
    
    email = user_info.get('email')
    
    if not email:
        return None
    
    try:
        # Try to find existing user by email
        user = User.objects.get(email=email)
        
        # Update user information if needed
        if not user.first_name and user_info.get('given_name'):
            user.first_name = user_info['given_name']
        
        if not user.last_name and user_info.get('family_name'):
            user.last_name = user_info['family_name']
        
        user.save()
        
    except User.DoesNotExist:
        # Create new user
        user = User.objects.create_user(
            username=email,  # Use email as username
            email=email,
            first_name=user_info.get('given_name', ''),
            last_name=user_info.get('family_name', ''),
        )
        
        # Set unusable password for social users
        user.set_unusable_password()
        user.save()
    
    # Store social authentication data
    store_social_auth_data(user, provider, user_info)
    
    return user

def store_social_auth_data(user, provider, user_info):
    """Store social authentication data"""
    
    # Create or update social auth record
    social_auth, created = SocialAuth.objects.get_or_create(
        user=user,
        provider=provider,
        defaults={
            'provider_user_id': user_info.get('id'),
            'extra_data': user_info,
        }
    )
    
    if not created:
        social_auth.extra_data = user_info
        social_auth.save()

# Model for storing social authentication data
class SocialAuth(models.Model):
    """Model to store social authentication information"""
    
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='social_auths')
    provider = models.CharField(max_length=50)
    provider_user_id = models.CharField(max_length=100)
    extra_data = models.JSONField(default=dict)
    
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        unique_together = ('user', 'provider')
        indexes = [
            models.Index(fields=['provider', 'provider_user_id']),
        ]
    
    def __str__(self):
        return f"{self.user.username} - {self.provider}"

Using Django-Allauth

Django-Allauth Setup and Configuration

# Django-Allauth setup and configuration
class DjangoAllauthSetup:
    """Setup and configuration for django-allauth"""
    
    @staticmethod
    def installation_and_settings():
        """Installation and basic settings configuration"""
        
        # Installation
        installation_command = "pip install django-allauth"
        
        # Settings configuration
        settings_config = """
        # settings.py
        
        INSTALLED_APPS = [
            'django.contrib.admin',
            'django.contrib.auth',
            'django.contrib.contenttypes',
            'django.contrib.sessions',
            'django.contrib.messages',
            'django.contrib.staticfiles',
            'django.contrib.sites',  # Required for allauth
            
            # Allauth apps
            'allauth',
            'allauth.account',
            'allauth.socialaccount',
            
            # Social providers
            'allauth.socialaccount.providers.google',
            'allauth.socialaccount.providers.facebook',
            'allauth.socialaccount.providers.twitter',
            'allauth.socialaccount.providers.github',
            'allauth.socialaccount.providers.linkedin_oauth2',
            
            # Your apps
            'myapp',
        ]
        
        MIDDLEWARE = [
            'django.middleware.security.SecurityMiddleware',
            'django.contrib.sessions.middleware.SessionMiddleware',
            'django.middleware.common.CommonMiddleware',
            'django.middleware.csrf.CsrfViewMiddleware',
            'django.contrib.auth.middleware.AuthenticationMiddleware',
            'django.contrib.messages.middleware.MessageMiddleware',
            'django.middleware.clickjacking.XFrameOptionsMiddleware',
        ]
        
        # Required for allauth
        SITE_ID = 1
        
        # Authentication backends
        AUTHENTICATION_BACKENDS = [
            'django.contrib.auth.backends.ModelBackend',
            'allauth.account.auth_backends.AuthenticationBackend',
        ]
        
        # Allauth configuration
        ACCOUNT_EMAIL_REQUIRED = True
        ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
        ACCOUNT_AUTHENTICATION_METHOD = 'email'
        ACCOUNT_USERNAME_REQUIRED = False
        ACCOUNT_USER_MODEL_USERNAME_FIELD = None
        ACCOUNT_USER_MODEL_EMAIL_FIELD = 'email'
        
        # Social account configuration
        SOCIALACCOUNT_EMAIL_REQUIRED = True
        SOCIALACCOUNT_EMAIL_VERIFICATION = 'none'
        SOCIALACCOUNT_QUERY_EMAIL = True
        SOCIALACCOUNT_AUTO_SIGNUP = True
        
        # Provider specific settings
        SOCIALACCOUNT_PROVIDERS = {
            'google': {
                'SCOPE': [
                    'profile',
                    'email',
                ],
                'AUTH_PARAMS': {
                    'access_type': 'online',
                }
            },
            'facebook': {
                'METHOD': 'oauth2',
                'SCOPE': ['email', 'public_profile'],
                'AUTH_PARAMS': {'auth_type': 'reauthenticate'},
                'INIT_PARAMS': {'cookie': True},
                'FIELDS': [
                    'id',
                    'first_name',
                    'last_name',
                    'middle_name',
                    'name',
                    'name_format',
                    'picture',
                    'short_name'
                ],
                'EXCHANGE_TOKEN': True,
                'LOCALE_FUNC': 'path.to.callable',
                'VERIFIED_EMAIL': False,
                'VERSION': 'v13.0',
            },
            'github': {
                'SCOPE': [
                    'user:email',
                ],
            }
        }
        
        # Login/logout URLs
        LOGIN_REDIRECT_URL = '/dashboard/'
        LOGOUT_REDIRECT_URL = '/'
        """
        
        return {
            'installation': installation_command,
            'settings': settings_config
        }
    
    @staticmethod
    def url_configuration():
        """URL configuration for allauth"""
        
        url_config = """
        # urls.py
        from django.contrib import admin
        from django.urls import path, include
        
        urlpatterns = [
            path('admin/', admin.site.urls),
            path('accounts/', include('allauth.urls')),
            path('', include('myapp.urls')),
        ]
        """
        
        return url_config
    
    @staticmethod
    def provider_setup_instructions():
        """Instructions for setting up social providers"""
        
        providers = {
            'google': {
                'setup_url': 'https://console.developers.google.com/',
                'steps': [
                    '1. Create a new project or select existing project',
                    '2. Enable Google+ API',
                    '3. Create OAuth 2.0 credentials',
                    '4. Add authorized redirect URIs: http://localhost:8000/accounts/google/login/callback/',
                    '5. Copy Client ID and Client Secret to Django admin'
                ],
                'scopes': ['profile', 'email']
            },
            'facebook': {
                'setup_url': 'https://developers.facebook.com/',
                'steps': [
                    '1. Create a new app',
                    '2. Add Facebook Login product',
                    '3. Configure Valid OAuth Redirect URIs: http://localhost:8000/accounts/facebook/login/callback/',
                    '4. Copy App ID and App Secret to Django admin'
                ],
                'scopes': ['email', 'public_profile']
            },
            'github': {
                'setup_url': 'https://github.com/settings/applications/new',
                'steps': [
                    '1. Register a new OAuth application',
                    '2. Set Authorization callback URL: http://localhost:8000/accounts/github/login/callback/',
                    '3. Copy Client ID and Client Secret to Django admin'
                ],
                'scopes': ['user:email']
            }
        }
        
        return providers

# Custom allauth adapters
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter

class CustomAccountAdapter(DefaultAccountAdapter):
    """Custom account adapter for allauth"""
    
    def is_open_for_signup(self, request):
        """Control whether signup is allowed"""
        
        # Allow signup only if enabled in settings
        return getattr(settings, 'ACCOUNT_ALLOW_REGISTRATION', True)
    
    def save_user(self, request, user, form, commit=True):
        """Customize user saving process"""
        
        user = super().save_user(request, user, form, commit=False)
        
        # Add custom fields or logic
        if hasattr(form, 'cleaned_data'):
            # Example: set user as inactive until email verification
            if not user.pk:  # New user
                user.is_active = False
        
        if commit:
            user.save()
        
        return user
    
    def send_mail(self, template_prefix, email, context):
        """Customize email sending"""
        
        # Add custom context
        context.update({
            'site_name': 'Your Site Name',
            'support_email': 'support@yoursite.com',
        })
        
        return super().send_mail(template_prefix, email, context)

class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
    """Custom social account adapter"""
    
    def is_open_for_signup(self, request, sociallogin):
        """Control social signup"""
        
        # Allow social signup
        return getattr(settings, 'SOCIALACCOUNT_ALLOW_REGISTRATION', True)
    
    def pre_social_login(self, request, sociallogin):
        """Handle pre-social login logic"""
        
        # Check if user exists with same email
        if sociallogin.user.email:
            try:
                existing_user = User.objects.get(email=sociallogin.user.email)
                
                # Connect social account to existing user
                if not sociallogin.is_existing:
                    sociallogin.connect(request, existing_user)
            
            except User.DoesNotExist:
                pass
    
    def populate_user(self, request, sociallogin, data):
        """Populate user data from social account"""
        
        user = super().populate_user(request, sociallogin, data)
        
        # Add custom data population
        extra_data = sociallogin.account.extra_data
        
        # Example: Get profile picture URL
        if sociallogin.account.provider == 'google':
            picture_url = extra_data.get('picture')
            if picture_url and hasattr(user, 'profile'):
                user.profile.avatar_url = picture_url
        
        elif sociallogin.account.provider == 'facebook':
            picture_data = extra_data.get('picture', {}).get('data', {})
            picture_url = picture_data.get('url')
            if picture_url and hasattr(user, 'profile'):
                user.profile.avatar_url = picture_url
        
        return user
    
    def save_user(self, request, sociallogin, form=None):
        """Save social user"""
        
        user = super().save_user(request, sociallogin, form)
        
        # Create user profile if it doesn't exist
        if hasattr(user, 'profile'):
            profile, created = UserProfile.objects.get_or_create(user=user)
            
            if created:
                # Set default values for social users
                profile.email_notifications = True
                profile.save()
        
        return user

# Settings for custom adapters
ACCOUNT_ADAPTER = 'myapp.adapters.CustomAccountAdapter'
SOCIALACCOUNT_ADAPTER = 'myapp.adapters.CustomSocialAccountAdapter'

Advanced Allauth Configuration

# Advanced allauth configuration and customization
class AdvancedAllauthConfiguration:
    """Advanced configuration options for django-allauth"""
    
    @staticmethod
    def custom_forms():
        """Custom forms for allauth"""
        
        from allauth.account.forms import SignupForm, LoginForm
        from allauth.socialaccount.forms import SignupForm as SocialSignupForm
        
        class CustomSignupForm(SignupForm):
            """Custom signup form with additional fields"""
            
            first_name = forms.CharField(max_length=30, required=True)
            last_name = forms.CharField(max_length=30, required=True)
            terms_accepted = forms.BooleanField(
                required=True,
                label="I accept the Terms of Service and Privacy Policy"
            )
            
            def save(self, request):
                """Save user with additional fields"""
                
                user = super().save(request)
                user.first_name = self.cleaned_data['first_name']
                user.last_name = self.cleaned_data['last_name']
                user.save()
                
                return user
        
        class CustomSocialSignupForm(SocialSignupForm):
            """Custom social signup form"""
            
            terms_accepted = forms.BooleanField(
                required=True,
                label="I accept the Terms of Service and Privacy Policy"
            )
            
            def save(self, request):
                """Save social user"""
                
                user = super().save(request)
                
                # Additional processing for social users
                if hasattr(user, 'profile'):
                    profile, created = UserProfile.objects.get_or_create(user=user)
                    profile.registration_method = 'social'
                    profile.save()
                
                return user
        
        # Settings for custom forms
        settings_config = """
        ACCOUNT_FORMS = {
            'signup': 'myapp.forms.CustomSignupForm',
        }
        
        SOCIALACCOUNT_FORMS = {
            'signup': 'myapp.forms.CustomSocialSignupForm',
        }
        """
        
        return {
            'CustomSignupForm': CustomSignupForm,
            'CustomSocialSignupForm': CustomSocialSignupForm,
            'settings': settings_config
        }
    
    @staticmethod
    def email_verification_customization():
        """Customize email verification process"""
        
        settings_config = """
        # Email verification settings
        ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
        ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 3
        ACCOUNT_EMAIL_CONFIRMATION_HMAC = True
        
        # Custom email templates
        ACCOUNT_EMAIL_SUBJECT_PREFIX = '[Your Site] '
        
        # Email confirmation URLs
        ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = '/accounts/login/'
        ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = '/dashboard/'
        """
        
        # Custom email confirmation view
        from allauth.account.views import ConfirmEmailView
        
        class CustomConfirmEmailView(ConfirmEmailView):
            """Custom email confirmation view"""
            
            def post(self, request, *args, **kwargs):
                """Handle email confirmation"""
                
                response = super().post(request, *args, **kwargs)
                
                # Add custom logic after email confirmation
                if hasattr(self, 'object') and self.object:
                    user = self.object.email_address.user
                    
                    # Send welcome email
                    self.send_welcome_email(user)
                    
                    # Log email confirmation
                    import logging
                    logger = logging.getLogger('auth')
                    logger.info(f"Email confirmed for user: {user.username}")
                
                return response
            
            def send_welcome_email(self, user):
                """Send welcome email after confirmation"""
                
                from django.core.mail import send_mail
                
                subject = 'Welcome to Our Site!'
                message = f'Hello {user.get_full_name() or user.username}, welcome to our site!'
                
                send_mail(
                    subject,
                    message,
                    settings.DEFAULT_FROM_EMAIL,
                    [user.email],
                    fail_silently=True,
                )
        
        return {
            'settings': settings_config,
            'CustomConfirmEmailView': CustomConfirmEmailView
        }
    
    @staticmethod
    def social_account_management():
        """Social account management and linking"""
        
        # View for managing social accounts
        from allauth.socialaccount.models import SocialAccount
        
        class SocialAccountManagementView(LoginRequiredMixin, TemplateView):
            """View for managing connected social accounts"""
            
            template_name = 'account/social_accounts.html'
            
            def get_context_data(self, **kwargs):
                """Add social accounts to context"""
                
                context = super().get_context_data(**kwargs)
                
                # Get user's social accounts
                social_accounts = SocialAccount.objects.filter(user=self.request.user)
                
                # Get available providers
                from allauth.socialaccount import providers
                available_providers = []
                
                for provider in providers.registry.get_list():
                    # Check if user already has account with this provider
                    has_account = social_accounts.filter(provider=provider.id).exists()
                    
                    available_providers.append({
                        'id': provider.id,
                        'name': provider.name,
                        'has_account': has_account,
                        'login_url': f'/accounts/{provider.id}/login/',
                    })
                
                context.update({
                    'social_accounts': social_accounts,
                    'available_providers': available_providers,
                })
                
                return context
        
        # View for disconnecting social accounts
        class DisconnectSocialAccountView(LoginRequiredMixin, View):
            """View for disconnecting social accounts"""
            
            def post(self, request, *args, **kwargs):
                """Disconnect social account"""
                
                account_id = request.POST.get('account_id')
                
                try:
                    social_account = SocialAccount.objects.get(
                        id=account_id,
                        user=request.user
                    )
                    
                    # Check if user has other login methods
                    if self.can_disconnect_account(request.user, social_account):
                        provider_name = social_account.get_provider().name
                        social_account.delete()
                        
                        messages.success(
                            request,
                            f'Successfully disconnected {provider_name} account.'
                        )
                    else:
                        messages.error(
                            request,
                            'Cannot disconnect this account. You need at least one login method.'
                        )
                
                except SocialAccount.DoesNotExist:
                    messages.error(request, 'Social account not found.')
                
                return redirect('social_accounts')
            
            def can_disconnect_account(self, user, social_account):
                """Check if account can be safely disconnected"""
                
                # Check if user has a usable password
                if user.has_usable_password():
                    return True
                
                # Check if user has other social accounts
                other_accounts = SocialAccount.objects.filter(user=user).exclude(
                    id=social_account.id
                )
                
                return other_accounts.exists()
        
        return {
            'SocialAccountManagementView': SocialAccountManagementView,
            'DisconnectSocialAccountView': DisconnectSocialAccountView
        }

# Custom provider implementation
class CustomOAuthProvider:
    """Example of implementing a custom OAuth provider"""
    
    @staticmethod
    def create_custom_provider():
        """Create a custom OAuth provider for allauth"""
        
        # Provider class
        from allauth.socialaccount.providers.base import ProviderAccount
        from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider
        
        class CustomAccount(ProviderAccount):
            """Custom provider account"""
            
            def get_profile_url(self):
                """Get profile URL"""
                return self.account.extra_data.get('profile_url')
            
            def get_avatar_url(self):
                """Get avatar URL"""
                return self.account.extra_data.get('avatar_url')
            
            def to_str(self):
                """String representation"""
                dflt = super().to_str()
                return self.account.extra_data.get('name', dflt)
        
        class CustomProvider(OAuth2Provider):
            """Custom OAuth2 provider"""
            
            id = 'custom'
            name = 'Custom Provider'
            account_class = CustomAccount
            
            def get_default_scope(self):
                """Default OAuth scopes"""
                return ['read:user', 'user:email']
            
            def extract_uid(self, data):
                """Extract unique user ID"""
                return str(data['id'])
            
            def extract_common_fields(self, data):
                """Extract common user fields"""
                return {
                    'username': data.get('login'),
                    'email': data.get('email'),
                    'first_name': data.get('name', '').split(' ')[0] if data.get('name') else '',
                    'last_name': ' '.join(data.get('name', '').split(' ')[1:]) if data.get('name') else '',
                }
        
        # OAuth2 adapter
        from allauth.socialaccount.providers.oauth2.views import (
            OAuth2Adapter,
            OAuth2LoginView,
            OAuth2CallbackView,
        )
        
        class CustomOAuth2Adapter(OAuth2Adapter):
            """Custom OAuth2 adapter"""
            
            provider_id = CustomProvider.id
            access_token_url = 'https://api.customprovider.com/oauth/token'
            authorize_url = 'https://api.customprovider.com/oauth/authorize'
            profile_url = 'https://api.customprovider.com/user'
            
            def complete_login(self, request, app, token, **kwargs):
                """Complete login process"""
                
                headers = {'Authorization': f'token {token.token}'}
                resp = requests.get(self.profile_url, headers=headers)
                
                extra_data = resp.json()
                
                return self.get_provider().sociallogin_from_response(
                    request,
                    extra_data
                )
        
        # Views
        oauth2_login = OAuth2LoginView.adapter_view(CustomOAuth2Adapter)
        oauth2_callback = OAuth2CallbackView.adapter_view(CustomOAuth2Adapter)
        
        # URL patterns
        urlpatterns = [
            path('login/', oauth2_login, name='custom_login'),
            path('login/callback/', oauth2_callback, name='custom_callback'),
        ]
        
        return {
            'provider': CustomProvider,
            'adapter': CustomOAuth2Adapter,
            'urls': urlpatterns
        }

Security and Best Practices

Social Authentication Security

# Security best practices for social authentication
class SocialAuthSecurity:
    """Security considerations and best practices"""
    
    @staticmethod
    def security_checklist():
        """Security checklist for social authentication"""
        
        checklist = {
            'provider_configuration': {
                'items': [
                    'Use HTTPS for all redirect URIs',
                    'Whitelist only necessary redirect URIs',
                    'Keep client secrets secure and rotate regularly',
                    'Use environment variables for sensitive data',
                    'Enable provider security features (2FA, etc.)'
                ],
                'priority': 'Critical'
            },
            'application_security': {
                'items': [
                    'Validate state parameter to prevent CSRF',
                    'Implement proper session management',
                    'Use secure cookies (HTTPS only)',
                    'Implement rate limiting for auth endpoints',
                    'Log authentication events for monitoring'
                ],
                'priority': 'Critical'
            },
            'data_protection': {
                'items': [
                    'Request minimal necessary scopes',
                    'Store minimal user data from providers',
                    'Implement data retention policies',
                    'Encrypt sensitive data at rest',
                    'Comply with privacy regulations (GDPR, etc.)'
                ],
                'priority': 'High'
            },
            'account_management': {
                'items': [
                    'Handle account linking securely',
                    'Prevent account takeover via email',
                    'Implement account recovery procedures',
                    'Allow users to disconnect social accounts',
                    'Provide clear privacy controls'
                ],
                'priority': 'High'
            }
        }
        
        return checklist
    
    @staticmethod
    def implement_security_middleware():
        """Security middleware for social authentication"""
        
        class SocialAuthSecurityMiddleware:
            """Middleware for social auth security"""
            
            def __init__(self, get_response):
                self.get_response = get_response
            
            def __call__(self, request):
                # Check for social auth security
                if self.is_social_auth_request(request):
                    if not self.validate_social_auth_security(request):
                        return self.handle_security_violation(request)
                
                response = self.get_response(request)
                
                # Add security headers for social auth
                if self.is_social_auth_request(request):
                    self.add_security_headers(response)
                
                return response
            
            def is_social_auth_request(self, request):
                """Check if request is social auth related"""
                return '/accounts/' in request.path and any(
                    provider in request.path 
                    for provider in ['google', 'facebook', 'twitter', 'github']
                )
            
            def validate_social_auth_security(self, request):
                """Validate social auth security"""
                
                # Check for HTTPS in production
                if not settings.DEBUG and not request.is_secure():
                    return False
                
                # Validate referer for callback requests
                if 'callback' in request.path:
                    referer = request.META.get('HTTP_REFERER', '')
                    if not self.is_valid_referer(referer):
                        return False
                
                return True
            
            def is_valid_referer(self, referer):
                """Validate referer for social auth callbacks"""
                
                valid_referers = [
                    'https://accounts.google.com',
                    'https://www.facebook.com',
                    'https://api.twitter.com',
                    'https://github.com',
                ]
                
                return any(referer.startswith(valid) for valid in valid_referers)
            
            def handle_security_violation(self, request):
                """Handle security violation"""
                
                from django.http import HttpResponseForbidden
                
                # Log security violation
                import logging
                logger = logging.getLogger('security')
                logger.warning(
                    f"Social auth security violation: {request.path} "
                    f"from {request.META.get('REMOTE_ADDR')}"
                )
                
                return HttpResponseForbidden("Security violation detected")
            
            def add_security_headers(self, response):
                """Add security headers"""
                
                response['X-Frame-Options'] = 'DENY'
                response['X-Content-Type-Options'] = 'nosniff'
                response['Referrer-Policy'] = 'strict-origin-when-cross-origin'
        
        return SocialAuthSecurityMiddleware
    
    @staticmethod
    def account_linking_security():
        """Secure account linking implementation"""
        
        def secure_account_linking(request, sociallogin):
            """Securely link social account to existing user"""
            
            email = sociallogin.user.email
            
            if not email:
                # Cannot link without email
                return False
            
            try:
                existing_user = User.objects.get(email=email)
                
                # Check if linking is allowed
                if not can_link_accounts(request, existing_user, sociallogin):
                    return False
                
                # Require additional verification for linking
                if requires_verification_for_linking(existing_user, sociallogin):
                    # Send verification email or require password
                    send_account_linking_verification(request, existing_user, sociallogin)
                    return 'verification_required'
                
                # Link accounts
                sociallogin.connect(request, existing_user)
                
                # Log account linking
                import logging
                logger = logging.getLogger('auth')
                logger.info(
                    f"Social account linked: {existing_user.username} "
                    f"with {sociallogin.account.provider}"
                )
                
                return True
            
            except User.DoesNotExist:
                # No existing user, proceed with normal signup
                return True
        
        def can_link_accounts(request, user, sociallogin):
            """Check if accounts can be linked"""
            
            # Don't link if user already has this provider
            if user.socialaccount_set.filter(
                provider=sociallogin.account.provider
            ).exists():
                return False
            
            # Check if email is verified
            if hasattr(user, 'emailaddress_set'):
                email_verified = user.emailaddress_set.filter(
                    email=user.email,
                    verified=True
                ).exists()
                
                if not email_verified:
                    return False
            
            return True
        
        def requires_verification_for_linking(user, sociallogin):
            """Check if additional verification is required"""
            
            # Require verification if user has password
            if user.has_usable_password():
                return True
            
            # Require verification for high-privilege users
            if user.is_staff or user.is_superuser:
                return True
            
            return False
        
        def send_account_linking_verification(request, user, sociallogin):
            """Send verification for account linking"""
            
            # Generate secure token
            import secrets
            token = secrets.token_urlsafe(32)
            
            # Store linking request in cache
            from django.core.cache import cache
            cache_key = f'account_linking_{token}'
            cache.set(cache_key, {
                'user_id': user.id,
                'provider': sociallogin.account.provider,
                'provider_uid': sociallogin.account.uid,
                'extra_data': sociallogin.account.extra_data,
            }, 3600)  # 1 hour expiry
            
            # Send verification email
            verification_url = request.build_absolute_uri(
                reverse('verify_account_linking', args=[token])
            )
            
            send_mail(
                'Confirm Account Linking',
                f'Click here to confirm linking your {sociallogin.account.get_provider().name} account: {verification_url}',
                settings.DEFAULT_FROM_EMAIL,
                [user.email],
            )
        
        return {
            'secure_account_linking': secure_account_linking,
            'can_link_accounts': can_link_accounts,
            'requires_verification_for_linking': requires_verification_for_linking,
            'send_account_linking_verification': send_account_linking_verification
        }

Social authentication integration enhances user experience while maintaining security. By understanding OAuth flows, properly configuring providers, and implementing security best practices, you can create seamless authentication experiences that users trust and prefer over traditional registration processes.