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 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}"
# 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 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 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.
Authorization in Views and Templates
Implementing proper authorization in Django views and templates ensures that users can only access resources and perform actions they're permitted to. Understanding how to apply authorization at different levels - from view-level access control to fine-grained template permissions - is crucial for building secure applications.
Security Best Practices
Implementing robust security measures in Django authentication and authorization systems is crucial for protecting user data and maintaining application integrity. Understanding and applying security best practices helps prevent common vulnerabilities and ensures your authentication system remains secure against evolving threats.