Fixed auth flow for KEYWARDEN_AUTH_MODE, native; oidc; hybrid work as intended. no login page for OIDC mode, assumes internal IdP for now. will fix when social login implemented

This commit is contained in:
boris committed 2026-03-30 14:36:28 +01:00
1 parent 68bbb53976
commit ea7470231f
5 files changed
+75 -32

No files matched your search

+7 -5
View File
@@ -19,9 +19,9 @@ KEYWARDEN_ADMIN_EMAIL=admin@example.com
KEYWARDEN_ADMIN_PASSWORD=password
# Auth mode: native | oidc | hybrid
# native: Uses JWT authentication based on Username / Password
# oidc: Disables in-build auth, exclusively uses OIDC configuration
# hybrid: Allows native auth, prefers oidc
# native: Uses Keywarden username/password login only. OIDC/social options are hidden and disabled.
# oidc: Disables built-in login and uses OIDC only. Users can be auto-onboarded from IdP claims.
# hybrid: Supports OIDC + Keywarden native login; login screen prefers OIDC/social options first.
KEYWARDEN_AUTH_MODE=native
@@ -41,14 +41,16 @@ KEYWARDEN_AUTH_MODE=native
# KEYWARDEN_OIDC_EMAIL_CLAIM=email
# KEYWARDEN_OIDC_EMAIL_VERIFIED_CLAIM=email_verified
# KEYWARDEN_OIDC_USERNAME_CLAIM=preferred_username
# KEYWARDEN_OIDC_ALLOW_AUTO_ONBOARDING=true
# KEYWARDEN_OIDC_GROUPS_CLAIM=groups
# KEYWARDEN_OIDC_SYNC_ADMIN_FROM_GROUPS=false
# KEYWARDEN_OIDC_SYNC_ADMIN_FROM_GROUPS=true # defaults to true when KEYWARDEN_AUTH_MODE=oidc
# KEYWARDEN_OIDC_ADMIN_GROUPS=authentik Admins
# KEYWARDEN_OIDC_ADMIN_DEMOTE_ON_MISS=false
# Social login (optional)
# KEYWARDEN_SITE_ID=1
# KEYWARDEN_SOCIAL_REQUIRE_VERIFIED_EMAIL=true
# KEYWARDEN_SOCIAL_ALLOW_AUTO_ONBOARDING=false
# KEYWARDEN_SOCIAL_GOOGLE_ENABLED=false
# KEYWARDEN_SOCIAL_GOOGLE_CLIENT_ID=
# KEYWARDEN_SOCIAL_GOOGLE_CLIENT_SECRET=
@@ -61,4 +63,4 @@ KEYWARDEN_AUTH_MODE=native
# Authentik Secrets
AUTHENTIK_PG_PASS=
AUTHENTIK_SECRET_KEY=
AUTHENTIK_SECRET_KEY=
+7
View File
@@ -61,6 +61,13 @@ class KeywardenSocialAccountAdapter(DefaultSocialAccountAdapter):
if user:
sociallogin.connect(request, user)
self._sync_identity(user, sociallogin)
return
if not getattr(settings, "KEYWARDEN_SOCIAL_ALLOW_AUTO_ONBOARDING", False):
self._reject(
request,
"Social auto-onboarding is disabled. Ask an administrator to create/link your account.",
)
def populate_user(self, request, sociallogin, data):
user = super().populate_user(request, sociallogin, data)
+14 -7
View File
@@ -133,7 +133,7 @@ def profile(request):
context = {
"user": request.user,
"auth_mode": getattr(settings, "KEYWARDEN_AUTH_MODE", "hybrid"),
"oidc_enabled": bool(getattr(settings, "KEYWARDEN_OIDC_ENABLED", False)),
"oidc_enabled": bool(getattr(settings, "KEYWARDEN_OIDC_LOGIN_ENABLED", False)),
"oidc_identity": oidc_identity,
"oidc_provider_id": getattr(settings, "KEYWARDEN_OIDC_PROVIDER_ID", "oidc"),
"oidc_issuer": getattr(settings, "KEYWARDEN_OIDC_ISSUER", ""),
@@ -149,25 +149,32 @@ def profile(request):
def login_view(request):
auth_mode = getattr(settings, "KEYWARDEN_AUTH_MODE", "hybrid")
if auth_mode == "oidc":
oidc_login_enabled = bool(getattr(settings, "KEYWARDEN_OIDC_LOGIN_ENABLED", False))
social_providers = (
getattr(settings, "KEYWARDEN_SOCIAL_LOGIN_PROVIDERS", [])
if auth_mode == "hybrid"
else []
)
if auth_mode == "oidc" and oidc_login_enabled:
return redirect("/oidc/authenticate/")
has_sso_options = bool(
getattr(settings, "KEYWARDEN_OIDC_ENABLED", False)
or getattr(settings, "KEYWARDEN_SOCIAL_LOGIN_PROVIDERS", [])
oidc_login_enabled
or social_providers
)
show_native_login = (
request.method == "POST"
or request.GET.get("native") == "1"
or (auth_mode == "native" and not has_sso_options)
or auth_mode == "native"
)
# native or hybrid -> render Django's built-in login view
return auth_views.LoginView.as_view(
template_name="accounts/login.html",
extra_context={
"social_providers": getattr(settings, "KEYWARDEN_SOCIAL_LOGIN_PROVIDERS", []),
"oidc_enabled": bool(getattr(settings, "KEYWARDEN_OIDC_ENABLED", False)),
"social_providers": social_providers,
"oidc_enabled": oidc_login_enabled,
"oidc_login_url": "/oidc/authenticate/",
"oidc_provider_label": _oidc_provider_label(),
"show_native_login": show_native_login,
+44 -19
View File
@@ -22,6 +22,12 @@ def env_csv(name: str, default: str = "") -> list[str]:
return [item.strip() for item in raw.split(",") if item.strip()]
# Auth mode: native | oidc | hybrid
KEYWARDEN_AUTH_MODE_REQUESTED = os.getenv("KEYWARDEN_AUTH_MODE", "hybrid").lower()
if KEYWARDEN_AUTH_MODE_REQUESTED not in {"native", "oidc", "hybrid"}:
KEYWARDEN_AUTH_MODE_REQUESTED = "hybrid"
BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = os.getenv("KEYWARDEN_SECRET_KEY")
@@ -70,12 +76,15 @@ KEYWARDEN_SOCIAL_APPLE_CONFIGURED = bool(
and KEYWARDEN_SOCIAL_APPLE_CLIENT_SECRET
)
KEYWARDEN_SOCIAL_AUTH_ENABLED = any(
KEYWARDEN_SOCIAL_AUTH_ENABLED = (
KEYWARDEN_AUTH_MODE_REQUESTED == "hybrid"
and any(
(
KEYWARDEN_SOCIAL_GOOGLE_CONFIGURED,
KEYWARDEN_SOCIAL_GITHUB_CONFIGURED,
KEYWARDEN_SOCIAL_APPLE_CONFIGURED,
)
)
)
# Default to secure cookies and respect TLS termination headers.
@@ -331,7 +340,8 @@ OIDC_OP_JWKS_ENDPOINT = os.getenv("KEYWARDEN_OIDC_JWKS_ENDPOINT")
OIDC_RP_SIGN_ALGO = os.getenv("KEYWARDEN_OIDC_SIGN_ALGO", "RS256")
OIDC_RP_SCOPES = os.getenv("KEYWARDEN_OIDC_SCOPES", "openid email profile")
OIDC_STORE_ACCESS_TOKEN = env_bool("KEYWARDEN_OIDC_STORE_ACCESS_TOKEN", default=False)
OIDC_CREATE_USER = True
KEYWARDEN_OIDC_ALLOW_AUTO_ONBOARDING = env_bool("KEYWARDEN_OIDC_ALLOW_AUTO_ONBOARDING", default=True)
OIDC_CREATE_USER = KEYWARDEN_OIDC_ALLOW_AUTO_ONBOARDING
KEYWARDEN_OIDC_PROVIDER_ID = os.getenv("KEYWARDEN_OIDC_PROVIDER_ID", "oidc")
KEYWARDEN_OIDC_ISSUER = os.getenv("KEYWARDEN_OIDC_ISSUER", "").strip()
@@ -340,7 +350,10 @@ KEYWARDEN_OIDC_EMAIL_VERIFIED_CLAIM = os.getenv("KEYWARDEN_OIDC_EMAIL_VERIFIED_C
KEYWARDEN_OIDC_REQUIRE_VERIFIED_EMAIL = env_bool("KEYWARDEN_OIDC_REQUIRE_VERIFIED_EMAIL", default=True)
KEYWARDEN_OIDC_USERNAME_CLAIM = os.getenv("KEYWARDEN_OIDC_USERNAME_CLAIM", "preferred_username")
KEYWARDEN_OIDC_GROUPS_CLAIM = os.getenv("KEYWARDEN_OIDC_GROUPS_CLAIM", "groups")
KEYWARDEN_OIDC_SYNC_ADMIN_FROM_GROUPS = env_bool("KEYWARDEN_OIDC_SYNC_ADMIN_FROM_GROUPS", default=False)
KEYWARDEN_OIDC_SYNC_ADMIN_FROM_GROUPS = env_bool(
"KEYWARDEN_OIDC_SYNC_ADMIN_FROM_GROUPS",
default=(KEYWARDEN_AUTH_MODE_REQUESTED == "oidc"),
)
KEYWARDEN_OIDC_ADMIN_DEMOTE_ON_MISS = env_bool("KEYWARDEN_OIDC_ADMIN_DEMOTE_ON_MISS", default=False)
KEYWARDEN_OIDC_ADMIN_GROUPS = env_csv("KEYWARDEN_OIDC_ADMIN_GROUPS")
@@ -358,16 +371,18 @@ KEYWARDEN_OIDC_ENABLED = bool(
)
)
KEYWARDEN_OIDC_LOGIN_ENABLED = KEYWARDEN_OIDC_ENABLED and KEYWARDEN_AUTH_MODE_REQUESTED in {"oidc", "hybrid"}
KEYWARDEN_SOCIAL_LOGIN_PROVIDERS = []
if KEYWARDEN_SOCIAL_GOOGLE_CONFIGURED:
if KEYWARDEN_SOCIAL_AUTH_ENABLED and KEYWARDEN_SOCIAL_GOOGLE_CONFIGURED:
KEYWARDEN_SOCIAL_LOGIN_PROVIDERS.append(
{"id": "google", "name": "Google", "login_url": "/accounts/sso/google/login/"}
)
if KEYWARDEN_SOCIAL_GITHUB_CONFIGURED:
if KEYWARDEN_SOCIAL_AUTH_ENABLED and KEYWARDEN_SOCIAL_GITHUB_CONFIGURED:
KEYWARDEN_SOCIAL_LOGIN_PROVIDERS.append(
{"id": "github", "name": "GitHub", "login_url": "/accounts/sso/github/login/"}
)
if KEYWARDEN_SOCIAL_APPLE_CONFIGURED:
if KEYWARDEN_SOCIAL_AUTH_ENABLED and KEYWARDEN_SOCIAL_APPLE_CONFIGURED:
KEYWARDEN_SOCIAL_LOGIN_PROVIDERS.append(
{"id": "apple", "name": "Apple", "login_url": "/accounts/sso/apple/login/"}
)
@@ -378,7 +393,8 @@ if KEYWARDEN_SOCIAL_AUTH_ENABLED:
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = "email"
SOCIALACCOUNT_AUTO_SIGNUP = True
KEYWARDEN_SOCIAL_ALLOW_AUTO_ONBOARDING = env_bool("KEYWARDEN_SOCIAL_ALLOW_AUTO_ONBOARDING", default=False)
SOCIALACCOUNT_AUTO_SIGNUP = KEYWARDEN_SOCIAL_ALLOW_AUTO_ONBOARDING
SOCIALACCOUNT_EMAIL_AUTHENTICATION = False
SOCIALACCOUNT_ADAPTER = "apps.accounts.social_auth.KeywardenSocialAccountAdapter"
SOCIALACCOUNT_LOGIN_ON_GET = True
@@ -426,28 +442,37 @@ KEYWARDEN_SOCIAL_REQUIRE_VERIFIED_EMAIL = env_bool(
)
# Auth mode: native | oidc | hybrid
AUTH_MODE = os.getenv("KEYWARDEN_AUTH_MODE", "hybrid").lower()
if AUTH_MODE not in {"native", "oidc", "hybrid"}:
AUTH_MODE = "hybrid"
AUTH_MODE = KEYWARDEN_AUTH_MODE_REQUESTED
if AUTH_MODE == "oidc" and not KEYWARDEN_OIDC_ENABLED:
AUTH_MODE = "native"
KEYWARDEN_AUTH_MODE = AUTH_MODE
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"guardian.backends.ObjectPermissionBackend",
]
if KEYWARDEN_SOCIAL_AUTH_ENABLED:
AUTHENTICATION_BACKENDS.append("allauth.account.auth_backends.AuthenticationBackend")
if KEYWARDEN_OIDC_ENABLED:
AUTHENTICATION_BACKENDS.append("apps.accounts.oidc.KeywardenOIDCAuthenticationBackend")
if AUTH_MODE == "oidc":
AUTHENTICATION_BACKENDS = [
"apps.accounts.oidc.KeywardenOIDCAuthenticationBackend",
"guardian.backends.ObjectPermissionBackend",
]
elif AUTH_MODE == "hybrid":
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"guardian.backends.ObjectPermissionBackend",
]
if KEYWARDEN_SOCIAL_AUTH_ENABLED:
AUTHENTICATION_BACKENDS.append("allauth.account.auth_backends.AuthenticationBackend")
if KEYWARDEN_OIDC_ENABLED:
AUTHENTICATION_BACKENDS.append("apps.accounts.oidc.KeywardenOIDCAuthenticationBackend")
else:
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"guardian.backends.ObjectPermissionBackend",
]
if AUTH_MODE == "oidc":
# OIDC-only: enforce identity provider logins.
LOGIN_URL = "/oidc/authenticate/"
else:
LOGIN_URL = "/accounts/login/"
LOGOUT_URL = "/oidc/logout/" if KEYWARDEN_OIDC_ENABLED else "/accounts/logout/"
LOGOUT_URL = "/oidc/logout/" if AUTH_MODE in {"oidc", "hybrid"} and KEYWARDEN_OIDC_ENABLED else "/accounts/logout/"
LOGIN_REDIRECT_URL = "/servers/"
LOGOUT_REDIRECT_URL = "/"
+3 -1
View File
@@ -7,9 +7,11 @@ from keywarden.api import api as ninja_api, api_v1 as ninja_api_v1
urlpatterns = [
path("admin/", admin.site.urls),
path("oidc/", include("mozilla_django_oidc.urls")),
]
if getattr(settings, "KEYWARDEN_OIDC_LOGIN_ENABLED", False):
urlpatterns.append(path("oidc/", include("mozilla_django_oidc.urls")))
if getattr(settings, "KEYWARDEN_SOCIAL_AUTH_ENABLED", False):
urlpatterns.append(path("accounts/sso/", include("allauth.urls")))