Compare commits
2 Commits
e5682c2573
...
ef733cb7bf
| Author | SHA1 | Date | |
|---|---|---|---|
| ef733cb7bf | |||
| 88afd5d307 |
@@ -1,4 +1,4 @@
|
|||||||
# SSO with Allauth & Casdoor Pattern v1.0.0
|
# SSO with Allauth & Casdoor Pattern v1.02
|
||||||
|
|
||||||
Standardizes OIDC-based Single Sign-On using Django Allauth and Casdoor, covering adapter customization, user provisioning, group mapping, superuser protection, and configurable local-login fallback. Used by the `core` Django application.
|
Standardizes OIDC-based Single Sign-On using Django Allauth and Casdoor, covering adapter customization, user provisioning, group mapping, superuser protection, and configurable local-login fallback. Used by the `core` Django application.
|
||||||
|
|
||||||
@@ -35,6 +35,7 @@ Every SSO implementation following this pattern must provide these files:
|
|||||||
| Local account adapter | `<app>/adapters.py` | Disable local signup, authentication logging |
|
| Local account adapter | `<app>/adapters.py` | Disable local signup, authentication logging |
|
||||||
| Management command | `<app>/management/commands/create_sso_groups.py` | Idempotent group + permission creation |
|
| Management command | `<app>/management/commands/create_sso_groups.py` | Idempotent group + permission creation |
|
||||||
| Login template | `templates/account/login.html` | SSO button + conditional local login form |
|
| Login template | `templates/account/login.html` | SSO button + conditional local login form |
|
||||||
|
| SSO signup template | `templates/socialaccount/signup.html` | Email confirmation step for first-time SSO users |
|
||||||
| Context processor | `<app>/context_processors.py` | Expose `CASDOOR_ENABLED` / `ALLOW_LOCAL_LOGIN` to templates |
|
| Context processor | `<app>/context_processors.py` | Expose `CASDOOR_ENABLED` / `ALLOW_LOCAL_LOGIN` to templates |
|
||||||
| SSL patch (optional) | `<app>/ssl_patch.py` | Development-only SSL bypass |
|
| SSL patch (optional) | `<app>/ssl_patch.py` | Development-only SSL bypass |
|
||||||
|
|
||||||
@@ -194,7 +195,7 @@ The social account adapter is the core of the pattern. It handles user provision
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
||||||
from allauth.exceptions import ImmediateHttpResponse
|
from allauth.core.exceptions import ImmediateHttpResponse
|
||||||
from django.contrib.auth.models import User, Group
|
from django.contrib.auth.models import User, Group
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
@@ -440,6 +441,73 @@ The login template shows an SSO button when Casdoor is enabled and conditionally
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## SSO Signup Template
|
||||||
|
|
||||||
|
When a new SSO user has no existing account, allauth redirects them to `accounts/3rdparty/signup/` to confirm their email before the account is created. Without a custom template this page renders with no styling.
|
||||||
|
|
||||||
|
Create `templates/socialaccount/signup.html` extending the project base:
|
||||||
|
|
||||||
|
```html
|
||||||
|
{% extends "<app>/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Complete Sign Up — {{ themis_app_name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="flex justify-center items-center min-h-[60vh]">
|
||||||
|
<div class="card bg-base-200 shadow-xl w-full max-w-md">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-2xl justify-center mb-2">Complete Sign Up</h2>
|
||||||
|
<p class="text-center text-base-content/70 mb-4">
|
||||||
|
Confirm your email address to finish signing in with SSO.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% if form.errors %}
|
||||||
|
<div class="alert alert-error mb-4">
|
||||||
|
<span>Please correct the errors below.</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post" action="{{ action_url }}">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="form-control mb-6">
|
||||||
|
<label class="label" for="id_email">
|
||||||
|
<span class="label-text">Email</span>
|
||||||
|
</label>
|
||||||
|
<input type="email" name="email" id="id_email"
|
||||||
|
class="input input-bordered w-full{% if form.email.errors %} input-error{% endif %}"
|
||||||
|
value="{{ form.email.value|default:'' }}"
|
||||||
|
autocomplete="email" required>
|
||||||
|
{% if form.email.errors %}
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt text-error">{{ form.email.errors|join:", " }}</span>
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mt-2">
|
||||||
|
<button type="submit" class="btn btn-primary w-full">Complete Sign Up</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
```
|
||||||
|
|
||||||
|
Key context variables allauth provides to this template:
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `form` | `SignupForm` with a single `email` field pre-populated from the OIDC claim |
|
||||||
|
| `action_url` | POST target (`/accounts/3rdparty/signup/`) — always use this, not a hard-coded path |
|
||||||
|
| `sociallogin` | The in-progress social login object (rarely needed in the template) |
|
||||||
|
|
||||||
|
> **Why this page exists:** `SOCIALACCOUNT_AUTO_SIGNUP = True` skips it when the IdP provides a valid email. It only appears when allauth cannot confirm the email (e.g. the IdP omitted it or there is a conflict with an existing account).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Context Processor
|
## Context Processor
|
||||||
|
|
||||||
Exposes SSO settings to every template:
|
Exposes SSO settings to every template:
|
||||||
@@ -701,7 +769,7 @@ class CasdoorAdapterTest(TestCase):
|
|||||||
|
|
||||||
def test_superuser_sso_login_blocked(self):
|
def test_superuser_sso_login_blocked(self):
|
||||||
"""pre_social_login must raise ImmediateHttpResponse for superusers."""
|
"""pre_social_login must raise ImmediateHttpResponse for superusers."""
|
||||||
from allauth.exceptions import ImmediateHttpResponse
|
from allauth.core.exceptions import ImmediateHttpResponse
|
||||||
user = User.objects.create_superuser(
|
user = User.objects.create_superuser(
|
||||||
'admin@example.com', 'admin@example.com', 'pass'
|
'admin@example.com', 'admin@example.com', 'pass'
|
||||||
)
|
)
|
||||||
Reference in New Issue
Block a user