Adapter Customization
The social account adapter allows you to customize core behaviors throughout the authentication flow. Create a custom adapter by subclassingDefaultSocialAccountAdapter.
Creating a Custom Adapter
myapp/adapters.py:from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def is_open_for_signup(self, request, socialaccount):
"""
Control whether new signups are allowed.
Return False to disable social signups.
"""
return True
def new_user(self, request, sociallogin):
"""
Instantiate a new User instance.
Override to use a custom user model or set defaults.
"""
user = super().new_user(request, sociallogin)
# Set custom defaults
user.is_active = True
return user
def save_user(self, request, sociallogin, form=None):
"""
Save the user instance.
Override to add custom logic before/after saving.
"""
user = super().save_user(request, sociallogin, form)
# Add custom logic
return user
def populate_user(self, request, sociallogin, data):
"""
Populate user instance with data from provider.
Args:
data: Dictionary with common fields (first_name, last_name,
email, username, name)
"""
user = super().populate_user(request, sociallogin, data)
# Add custom field mapping
if 'company' in sociallogin.account.extra_data:
user.company = sociallogin.account.extra_data['company']
return user
def get_connect_redirect_url(self, request, socialaccount):
"""
Return URL to redirect to after connecting a social account.
"""
return '/dashboard/accounts/'
SOCIALACCOUNT_ADAPTER = 'myapp.adapters.MySocialAccountAdapter'
User Creation and Population
Control Signup Availability
Restrict signups based on provider, domain, or other criteria:class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def is_open_for_signup(self, request, socialaccount):
# Only allow signups from Google
if socialaccount.provider != 'google':
return False
# Only allow company email domains
email = socialaccount.account.extra_data.get('email', '')
if not email.endswith('@company.com'):
return False
return True
Custom User Field Mapping
Map provider-specific data to user fields:class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def populate_user(self, request, sociallogin, data):
user = super().populate_user(request, sociallogin, data)
provider = sociallogin.account.provider
extra_data = sociallogin.account.extra_data
if provider == 'github':
user.bio = extra_data.get('bio', '')
user.location = extra_data.get('location', '')
user.website = extra_data.get('blog', '')
elif provider == 'google':
user.locale = extra_data.get('locale', 'en')
user.avatar_url = extra_data.get('picture', '')
elif provider == 'linkedin':
user.headline = extra_data.get('headline', '')
return user
Auto-Assign Roles Based on Provider
from django.contrib.auth.models import Group
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def save_user(self, request, sociallogin, form=None):
user = super().save_user(request, sociallogin, form)
# Assign role based on provider
if sociallogin.account.provider == 'google':
group = Group.objects.get(name='Google Users')
user.groups.add(group)
# Assign role based on email domain
email = user.email
if email.endswith('@company.com'):
group = Group.objects.get(name='Staff')
user.groups.add(group)
return user
Custom Redirects
Provider-Specific Redirects
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def get_connect_redirect_url(self, request, socialaccount):
"""
Called after successfully connecting a social account.
"""
if socialaccount.provider == 'github':
return '/dashboard/github-connected/'
elif socialaccount.provider == 'google':
return '/dashboard/google-connected/'
return '/dashboard/accounts/'
Dynamic Redirects Based on User Type
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def get_login_redirect_url(self, request):
"""
Called after successful social login.
"""
user = request.user
# Redirect based on user type
if user.is_staff:
return '/admin/dashboard/'
elif user.groups.filter(name='Premium').exists():
return '/premium/dashboard/'
else:
return '/dashboard/'
Customizing Provider Scopes
Request Additional Scopes
Most providers support custom scopes for accessing additional data:SOCIALACCOUNT_PROVIDERS = {
'google': {
'SCOPE': [
'profile',
'email',
'https://www.googleapis.com/auth/calendar.readonly',
'https://www.googleapis.com/auth/drive.readonly',
],
},
'github': {
'SCOPE': [
'user',
'user:email',
'repo',
'read:org',
'gist',
],
},
'microsoft': {
'SCOPE': [
'openid',
'profile',
'email',
'User.Read',
'Calendars.Read',
'Mail.Read',
],
},
}
Finding Default Scopes
To find the default scopes for a provider:- Look in
allauth/socialaccount/providers/<provider>/provider.py - Find the
get_default_scope(self)method
class GoogleProvider(OAuth2Provider):
def get_default_scope(self):
scope = ['profile']
if app_settings.QUERY_EMAIL:
scope.append('email')
return scope
Dynamic Scopes
Request different scopes based on context:class CustomGoogleProvider(GoogleProvider):
id = 'google_custom'
def get_scope(self, request):
scopes = super().get_scope(request)
# Add calendar scope for premium users
if request.user.is_authenticated and request.user.is_premium:
scopes.append('https://www.googleapis.com/auth/calendar')
return scopes
Customizing Providers
Subclass an Existing Provider
Create a custom provider variant: myapp/providers/google_custom/provider.py:from allauth.socialaccount.providers.google.provider import GoogleProvider
class GoogleCustomProvider(GoogleProvider):
id = 'google_custom'
name = 'Google Custom'
def get_default_scope(self):
# No scopes by default
return []
def extract_common_fields(self, data):
# Custom field extraction
fields = super().extract_common_fields(data)
fields['nickname'] = data.get('name', '')
return fields
provider_classes = [GoogleCustomProvider]
from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
from .provider import GoogleCustomProvider
urlpatterns = default_urlpatterns(GoogleCustomProvider)
INSTALLED_APPS = [
# ...
'myapp.providers.google_custom',
]
Override Provider URLs
Customize the provider URLs:from allauth.socialaccount.providers.google.views import (
GoogleOAuth2Adapter,
oauth2_login,
oauth2_callback,
)
from django.urls import path
class CustomGoogleAdapter(GoogleOAuth2Adapter):
# Custom adapter logic
pass
urlpatterns = [
path(
'google/login/',
oauth2_login,
{'adapter_class': CustomGoogleAdapter},
name='google_login'
),
path(
'google/login/callback/',
oauth2_callback,
{'adapter_class': CustomGoogleAdapter},
name='google_callback'
),
]
Authentication Parameters
OAuth2 Authentication Parameters
Pass additional parameters to the authorization endpoint:SOCIALACCOUNT_PROVIDERS = {
'google': {
'AUTH_PARAMS': {
'access_type': 'offline', # Request refresh token
'prompt': 'consent', # Force consent screen
'hd': 'company.com', # Limit to domain
},
},
'github': {
'AUTH_PARAMS': {
'allow_signup': 'false', # Disable GitHub signups
},
},
'microsoft': {
'AUTH_PARAMS': {
'prompt': 'select_account', # Always show account picker
},
},
}
Dynamic Auth Parameters
Set parameters dynamically:class CustomGoogleProvider(GoogleProvider):
def get_auth_params(self, request, action):
params = super().get_auth_params(request, action)
# Force consent for premium features
if request.GET.get('premium'):
params['prompt'] = 'consent'
params['access_type'] = 'offline'
return params
Working with OAuth Tokens
Enable Token Storage
SOCIALACCOUNT_STORE_TOKENS = True
Retrieve Access Tokens
from allauth.socialaccount.models import SocialToken
def get_google_token(user):
try:
token = SocialToken.objects.get(
account__user=user,
account__provider='google'
)
return token.token
except SocialToken.DoesNotExist:
return None
Make API Calls
import requests
from allauth.socialaccount.models import SocialToken
def get_github_repos(user):
token = SocialToken.objects.get(
account__user=user,
account__provider='github'
)
headers = {'Authorization': f'token {token.token}'}
response = requests.get(
'https://api.github.com/user/repos',
headers=headers
)
return response.json()
Refresh Tokens
from allauth.socialaccount.models import SocialToken, SocialApp
from allauth.socialaccount.providers.oauth2.client import OAuth2Client, OAuth2Error
import requests
def refresh_google_token(user):
try:
token = SocialToken.objects.get(
account__user=user,
account__provider='google'
)
app = token.app
# Request new access token
response = requests.post(
'https://oauth2.googleapis.com/token',
data={
'client_id': app.client_id,
'client_secret': app.secret,
'refresh_token': token.token_secret,
'grant_type': 'refresh_token',
}
)
data = response.json()
# Update stored token
token.token = data['access_token']
if 'refresh_token' in data:
token.token_secret = data['refresh_token']
token.save()
return token.token
except SocialToken.DoesNotExist:
return None
Email Verification
Trust Provider Email Verification
Mark emails from specific providers as verified:SOCIALACCOUNT_PROVIDERS = {
'google': {
'VERIFIED_EMAIL': True, # Trust Google's verification
},
'github': {
'VERIFIED_EMAIL': True, # Trust GitHub's verification
},
'facebook': {
'VERIFIED_EMAIL': False, # Don't trust Facebook
},
}
Custom Email Verification Logic
from allauth.account.models import EmailAddress
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def save_user(self, request, sociallogin, form=None):
user = super().save_user(request, sociallogin, form)
# Mark email as verified for specific providers
if sociallogin.account.provider in ['google', 'microsoft']:
email = user.email
EmailAddress.objects.filter(
user=user,
email=email
).update(verified=True)
return user
Signals and Hooks
django-allauth provides signals for hooking into the authentication flow:Available Signals
from allauth.socialaccount.signals import (
pre_social_login,
social_account_added,
social_account_updated,
social_account_removed,
)
from django.dispatch import receiver
@receiver(pre_social_login)
def before_social_login(sender, request, sociallogin, **kwargs):
"""
Called before a social login is processed.
Raise an exception to prevent login.
"""
# Check if email is blacklisted
email = sociallogin.account.extra_data.get('email')
if email and is_blacklisted(email):
raise ImmediateHttpResponse(
HttpResponse('Email domain not allowed')
)
@receiver(social_account_added)
def after_account_connected(sender, request, sociallogin, **kwargs):
"""
Called after a social account is connected to a user.
"""
user = sociallogin.user
provider = sociallogin.account.provider
# Log the connection
logger.info(f'User {user.id} connected {provider} account')
# Award points for connecting account
user.points += 10
user.save()
@receiver(social_account_updated)
def after_account_updated(sender, request, sociallogin, **kwargs):
"""
Called when social account data is updated.
"""
# Sync profile data
user = sociallogin.user
extra_data = sociallogin.account.extra_data
if 'avatar_url' in extra_data:
user.avatar_url = extra_data['avatar_url']
user.save()
@receiver(social_account_removed)
def after_account_disconnected(sender, request, socialaccount, **kwargs):
"""
Called after a social account is disconnected.
"""
user = socialaccount.user
provider = socialaccount.provider
logger.info(f'User {user.id} disconnected {provider} account')
Prevent Login Based on Criteria
from django.http import HttpResponse
from allauth.exceptions import ImmediateHttpResponse
@receiver(pre_social_login)
def check_user_eligibility(sender, request, sociallogin, **kwargs):
# Only allow users from specific domain
email = sociallogin.account.extra_data.get('email', '')
if not email.endswith('@company.com'):
raise ImmediateHttpResponse(
HttpResponse('Only company employees can sign in', status=403)
)
# Check against external API
if not is_authorized_user(email):
raise ImmediateHttpResponse(
HttpResponse('User not authorized', status=403)
)
Multi-Tenant Support
Using Django Sites Framework
Configure different apps per site:# Enable sites framework
INSTALLED_APPS = [
'django.contrib.sites',
# ...
]
SITE_ID = 1
SocialApp instances via admin and assign to different sites.
Custom Tenant Logic
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def get_app(self, request, provider, client_id=None):
"""
Return the appropriate app based on request context.
"""
# Determine tenant from domain
tenant = get_tenant_from_request(request)
# Get app for this tenant
from allauth.socialaccount.models import SocialApp
return SocialApp.objects.get(
provider=provider,
settings__tenant=tenant.id
)
Security Best Practices
Require HTTPS
# settings.py
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
Limit Email Authentication
# Only enable for trusted providers
SOCIALACCOUNT_PROVIDERS = {
'google': {
'EMAIL_AUTHENTICATION': True,
},
# Don't enable for less trusted providers
'facebook': {
'EMAIL_AUTHENTICATION': False,
},
}
Require POST for Login
SOCIALACCOUNT_LOGIN_ON_GET = False # Default, keep it this way
Enable PKCE for OAuth2
SOCIALACCOUNT_PROVIDERS = {
'google': {
'OAUTH_PKCE_ENABLED': True,
},
'github': {
'OAUTH_PKCE_ENABLED': True,
},
}
Next Steps
Providers
Browse all 100+ supported providers
Configuration
Complete settings reference
