Authentication views, mixins, and OAuth integration for djust applications.
- Login / Signup / Logout views — Drop-in authentication views with customizable templates
- LiveView mixins —
LoginRequiredLiveViewMixinandPermissionRequiredLiveViewMixinfor protecting djust LiveViews - OAuth via django-allauth — Optional OAuth support (GitHub, Google, GitLab, OIDC, etc.)
- djust-admin plugin — Dashboard widget, OAuth Providers page, and Social Accounts page for djust-admin
- Context processor — Injects configured OAuth providers into templates for rendering login buttons
For a complete integration walkthrough including OIDC, dual-template-backend setup, and troubleshooting, see docs/oidc-integration.md.
# Core (login/signup/logout views + mixins)
pip install djust-auth
# With OAuth support
pip install "djust-auth[oauth]"INSTALLED_APPS = [
# ...
"django.contrib.sites", # ← MUST come before allauth.socialaccount
"djust_auth",
"allauth",
"allauth.account",
"allauth.socialaccount",
# Add providers you need:
"allauth.socialaccount.providers.github",
"allauth.socialaccount.providers.google",
]
SITE_ID = 1 # Required for SocialApp credential resolutionWhy
django.contrib.sitesfirst:socialaccount's first migration has a hard dependency on thesitesmigration. Ifsitesis added later you getInconsistentMigrationHistory. See docs/oidc-integration.md for recovery steps.
AccountMiddleware must be last in the list:
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
# ...
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"allauth.account.middleware.AccountMiddleware", # ← LAST
]Placing it in the middle causes subtle OAuth redirect loops.
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
]# urls.py
urlpatterns = [
path("accounts/", include("djust_auth.urls")), # login/logout/signup
path("accounts/", include("allauth.urls")), # OAuth callbacks — no namespace
]Do not add a namespace= to allauth.urls — it breaks allauth's internal reverse() calls.
python manage.py migrateAfter migrating, ensure the default Site object exists for SITE_ID = 1:
python manage.py shell -c "
from django.contrib.sites.models import Site
Site.objects.update_or_create(pk=1, defaults={'domain': 'localhost:8000', 'name': 'My App'})
"SOCIALACCOUNT_PROVIDERS = {
"github": {
"APP": {
"client_id": os.getenv("GITHUB_CLIENT_ID", ""),
"secret": os.getenv("GITHUB_CLIENT_SECRET", ""),
},
"SCOPE": ["user:email"], # ← SCOPE at provider level, NOT inside APP
},
"google": {
"APP": {
"client_id": os.getenv("GOOGLE_CLIENT_ID", ""),
"secret": os.getenv("GOOGLE_CLIENT_SECRET", ""),
},
"SCOPE": ["profile", "email"],
},
}SOCIALACCOUNT_PROVIDERS = {
"openid_connect": {
"APPS": [
{
"provider_id": "oidc",
"name": os.getenv("OIDC_PROVIDER_NAME", "OIDC"),
"client_id": os.getenv("OIDC_CLIENT_ID", ""),
"secret": os.getenv("OIDC_CLIENT_SECRET", ""),
"settings": {
"server_url": os.getenv("OIDC_SERVER_URL", ""),
# allauth auto-appends /.well-known/openid-configuration
"token_auth_method": "client_secret_basic",
},
}
]
}
}Callback URI to register: http://<host>/accounts/oidc/login/callback/
Add social_auth_providers to your Django template backend (not the djust Rust backend):
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
# ...
"djust_auth.social.social_auth_providers", # injects oauth_providers
],
},
},
]djust_auth/includes/oauth_buttons.html— reusable OAuth button listdjust_auth/admin/providers.html— admin OAuth Providers pagedjust_auth/admin/social_accounts.html— admin Social Accounts pagedjust_auth/admin/widgets/auth_summary.html— admin dashboard widget
In your project's templates dir, create djust_auth/login.html and djust_auth/signup.html.
Minimal login.html:
{% if oauth_providers %}
{% for provider in oauth_providers %}
<a href="{{ provider.login_url }}?next={{ request.GET.next|default:'/' }}">
{{ provider.icon }} Continue with {{ provider.label }}
</a>
{% endfor %}
{% endif %}
<form method="post">{% csrf_token %}
<input name="username" type="text">
<input name="password" type="password">
<button type="submit">Sign In</button>
</form>oauth_providers items: name, label, icon (SVG wrapped in mark_safe() — render unescaped), login_url.
Built-in icons for GitHub, Google, GitLab.
Or use the included partial:
{% include "djust_auth/includes/oauth_buttons.html" %}from djust_auth.mixins import LoginRequiredLiveViewMixin, PermissionRequiredLiveViewMixin
from djust import LiveView
# Redirect anonymous users to login with ?next=<current-path>
class MyProtectedView(LoginRequiredLiveViewMixin, LiveView):
template_name = "my_app/protected.html"
# Raise 403 if user lacks permission
class MyAdminView(PermissionRequiredLiveViewMixin, LiveView):
template_name = "my_app/admin.html"
permission_required = "my_app.can_manage"
# Combine: redirect anonymous, 403 for authenticated-but-unauthorized
class StrictView(LoginRequiredLiveViewMixin, PermissionRequiredLiveViewMixin, LiveView):
template_name = "my_app/strict.html"
permission_required = "my_app.can_manage"
# LoginRequired must come first in MRO so anon users get a redirect, not a 403Both mixins intercept at dispatch() before mount() runs, so no LiveView state is
initialized for unauthorized users.
If djust-admin is installed, djust-auth automatically registers a plugin via autodiscovery. No extra configuration needed.
Dashboard widget (AuthSummaryWidget):
- Total users, new this week, staff, superusers
- OAuth user count and configured provider count
OAuth Providers page (/admin/auth/providers):
- Callback URLs, masked client IDs, scope validation
- Per-provider usage stats and settings snippets
- Warning if
SCOPEis misplaced insideAPP
Social Accounts page (/admin/auth/accounts, requires allauth.socialaccount):
- LiveView table — searchable, sortable, filterable by provider, paginated
The SocialAccount model is also registered with DjustModelAdmin automatically.
| Symptom | Cause | Fix |
|---|---|---|
InconsistentMigrationHistory |
allauth.socialaccount before django.contrib.sites |
Move django.contrib.sites above allauth.socialaccount |
| OAuth login fails silently | SITE_ID not set or django_site pk=1 missing |
Set SITE_ID = 1, run update_or_create shell command |
| OAuth redirect loop | AccountMiddleware not last |
Move after XFrameOptionsMiddleware |
NoReverseMatch for allauth URLs |
allauth.urls included with namespace= |
Remove namespace= |
SCOPE silently ignored |
SCOPE inside APP dict |
Move SCOPE up one level |
oauth_providers always empty |
Context processor on wrong template backend | Add only to DjangoTemplates backend, not djust Rust backend |
# Install dev dependencies
pip install -e ".[test,oauth]"
# Run tests
pytest- Go to https://github.com/settings/developers → "New OAuth App"
- Callback URL:
http://localhost:PORT/accounts/github/login/callback/ - Set
GITHUB_CLIENT_IDandGITHUB_CLIENT_SECRETin your.env
- Go to https://console.cloud.google.com/apis/credentials
- Create OAuth client ID (Web application)
- Redirect URI:
http://localhost:PORT/accounts/google/login/callback/ - Set
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRETin your.env
- Create a client in your realm with "Standard flow" enabled
- Redirect URI:
http://localhost:PORT/accounts/oidc/login/callback/ - Set
OIDC_SERVER_URL,OIDC_CLIENT_ID,OIDC_CLIENT_SECRETin your.env