Authentication in Django is one of the framework’s strongest batteries-included features, but using it well in production requires more than the default User model and a login form. This guide covers the authentication patterns that actually hold up under real use: custom user models, session security, password validation, permission hierarchies, OAuth and social login integration, and two-factor authentication. I walk through the decisions you need to make early, before your user table has real data, and the hardening steps that matter when the application handles sensitive information. For a broader security perspective, see the Security hub.
The most expensive authentication mistake is starting a project with Django’s default User model and trying to change it after the first migration. That migration path is painful enough that the Django documentation explicitly warns against it. If you take nothing else from this guide, set up a custom user model before your first migrate command.
Custom user model
Always define a custom user model, even if you think the defaults are fine:
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
Register it in settings:
AUTH_USER_MODEL = 'accounts.User'
This gives you a clean migration path when you eventually need to add fields like phone_number, organization, or timezone. Without a custom user model, adding fields to the user table later requires complex data migrations.
The AbstractUser approach keeps all the built-in fields and authentication methods. If you need more control, use AbstractBaseUser with a custom manager, but that requires implementing considerably more plumbing.
Session configuration
Django’s session framework stores a session identifier in a cookie and keeps session data server-side. In production, configure sessions for security:
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
SESSION_COOKIE_AGE = 1209600 # 2 weeks
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_SAVE_EVERY_REQUEST = False
SESSION_COOKIE_SECURE ensures the cookie is only sent over HTTPS. SESSION_COOKIE_HTTPONLY prevents JavaScript access. SameSite=Lax protects against CSRF in most scenarios.
Use cached_db as the session engine to get both the speed of cache-backed sessions and the durability of database storage as a fallback.
Password validation
Django ships with a configurable password validation framework. Enable validators that enforce meaningful security:
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {'min_length': 10}},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
The CommonPasswordValidator checks against a list of commonly compromised passwords. Consider adding a custom validator that checks passwords against breach databases using the Have I Been Pwned API (k-anonymity model preserves privacy).
Login and logout views
Django provides LoginView and LogoutView in django.contrib.auth.views:
from django.contrib.auth import views as auth_views
urlpatterns = [
path('login/', auth_views.LoginView.as_view(template_name='accounts/login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(next_page='/'), name='logout'),
path('password-change/', auth_views.PasswordChangeView.as_view(), name='password_change'),
path('password-change/done/', auth_views.PasswordChangeDoneView.as_view(), name='password_change_done'),
]
Provide your own templates to control the appearance. The views handle the authentication logic, CSRF protection, and session creation.
Permission system
Django’s permission framework provides three levels:
- Model-level permissions: add, change, delete, view per model
- Object-level permissions: use
django-guardianfor per-object access control - Group-based permissions: assign permissions to groups, then add users to groups
from django.contrib.auth.decorators import login_required, permission_required
@login_required
def dashboard(request):
return render(request, 'dashboard.html')
@permission_required('catalog.change_product', raise_exception=True)
def edit_product(request, pk):
# Only users with the specific permission reach here
...
For class-based views, use LoginRequiredMixin and PermissionRequiredMixin. Always specify raise_exception=True to return 403 instead of silently redirecting.
OAuth and social authentication
For social login (Google, GitHub, etc.), django-allauth is the standard:
INSTALLED_APPS = [
# ...
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
'allauth.socialaccount.providers.github',
]
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
]
Configure providers through Django admin or settings. The key decisions are: do you allow both social and password login? Do you require email verification? What happens when a social account email matches an existing user?
Answer these questions in your settings and test the edge cases. Users who cannot log in are users you lose.
Two-factor authentication
For applications handling sensitive data, two-factor authentication (2FA) is not optional. django-otp provides TOTP (time-based one-time passwords) compatible with authenticator apps:
INSTALLED_APPS = [
# ...
'django_otp',
'django_otp.plugins.otp_totp',
]
MIDDLEWARE = [
# ... after AuthenticationMiddleware
'django_otp.middleware.OTPMiddleware',
]
Enforce 2FA for admin users at minimum. For general users, offer it as opt-in with clear setup instructions and backup codes.
API authentication
For API-based authentication, session cookies work for same-origin requests. For cross-origin or mobile clients, use token-based authentication:
# Django REST Framework token auth
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
}
For more sophisticated requirements, use JWT through djangorestframework-simplejwt, but be aware that JWT introduces complexity around token refresh, revocation, and storage that session-based auth handles automatically.
Frequently asked questions
Can I switch to a custom user model after the project has data? Technically yes, but it requires manual migration surgery, data copying, and careful handling of foreign keys. It is significantly easier to start with a custom user model. This is a genuine “do it first or regret it” situation.
Should I use email as the username?
Many applications benefit from email-based login. With AbstractUser, set USERNAME_FIELD = 'email' and make the email field unique. With AbstractBaseUser, you have full control over the username field.
How do I handle password resets?
Django provides PasswordResetView, PasswordResetConfirmView, and related views. They generate secure tokens, send emails, and handle the reset flow. Provide custom templates and configure your email backend.
What is the best approach for API key authentication? For server-to-server authentication, API keys stored as hashed values in the database work well. Each key maps to a user or service account. Rotate keys regularly and support multiple active keys per account for zero-downtime rotation.