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
+74 -31

No files matched your search

+6 -4
View File
@@ -19,9 +19,9 @@ KEYWARDEN_ADMIN_EMAIL=admin@example.com
KEYWARDEN_ADMIN_PASSWORD=password KEYWARDEN_ADMIN_PASSWORD=password
# Auth mode: native | oidc | hybrid # Auth mode: native | oidc | hybrid
# native: Uses JWT authentication based on Username / Password # native: Uses Keywarden username/password login only. OIDC/social options are hidden and disabled.
# oidc: Disables in-build auth, exclusively uses OIDC configuration # oidc: Disables built-in login and uses OIDC only. Users can be auto-onboarded from IdP claims.
# hybrid: Allows native auth, prefers oidc # hybrid: Supports OIDC + Keywarden native login; login screen prefers OIDC/social options first.
KEYWARDEN_AUTH_MODE=native KEYWARDEN_AUTH_MODE=native
@@ -41,14 +41,16 @@ KEYWARDEN_AUTH_MODE=native
# KEYWARDEN_OIDC_EMAIL_CLAIM=email # KEYWARDEN_OIDC_EMAIL_CLAIM=email
# KEYWARDEN_OIDC_EMAIL_VERIFIED_CLAIM=email_verified # KEYWARDEN_OIDC_EMAIL_VERIFIED_CLAIM=email_verified
# KEYWARDEN_OIDC_USERNAME_CLAIM=preferred_username # KEYWARDEN_OIDC_USERNAME_CLAIM=preferred_username
# KEYWARDEN_OIDC_ALLOW_AUTO_ONBOARDING=true
# KEYWARDEN_OIDC_GROUPS_CLAIM=groups # 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_GROUPS=authentik Admins
# KEYWARDEN_OIDC_ADMIN_DEMOTE_ON_MISS=false # KEYWARDEN_OIDC_ADMIN_DEMOTE_ON_MISS=false
# Social login (optional) # Social login (optional)
# KEYWARDEN_SITE_ID=1 # KEYWARDEN_SITE_ID=1
# KEYWARDEN_SOCIAL_REQUIRE_VERIFIED_EMAIL=true # KEYWARDEN_SOCIAL_REQUIRE_VERIFIED_EMAIL=true
# KEYWARDEN_SOCIAL_ALLOW_AUTO_ONBOARDING=false
# KEYWARDEN_SOCIAL_GOOGLE_ENABLED=false # KEYWARDEN_SOCIAL_GOOGLE_ENABLED=false
# KEYWARDEN_SOCIAL_GOOGLE_CLIENT_ID= # KEYWARDEN_SOCIAL_GOOGLE_CLIENT_ID=
# KEYWARDEN_SOCIAL_GOOGLE_CLIENT_SECRET= # KEYWARDEN_SOCIAL_GOOGLE_CLIENT_SECRET=
+7
View File
@@ -61,6 +61,13 @@ class KeywardenSocialAccountAdapter(DefaultSocialAccountAdapter):
if user: if user:
sociallogin.connect(request, user) sociallogin.connect(request, user)
self._sync_identity(user, sociallogin) 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): def populate_user(self, request, sociallogin, data):
user = super().populate_user(request, sociallogin, data) user = super().populate_user(request, sociallogin, data)
+14 -7
View File
@@ -133,7 +133,7 @@ def profile(request):
context = { context = {
"user": request.user, "user": request.user,
"auth_mode": getattr(settings, "KEYWARDEN_AUTH_MODE", "hybrid"), "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_identity": oidc_identity,
"oidc_provider_id": getattr(settings, "KEYWARDEN_OIDC_PROVIDER_ID", "oidc"), "oidc_provider_id": getattr(settings, "KEYWARDEN_OIDC_PROVIDER_ID", "oidc"),
"oidc_issuer": getattr(settings, "KEYWARDEN_OIDC_ISSUER", ""), "oidc_issuer": getattr(settings, "KEYWARDEN_OIDC_ISSUER", ""),
@@ -149,25 +149,32 @@ def profile(request):
def login_view(request): def login_view(request):
auth_mode = getattr(settings, "KEYWARDEN_AUTH_MODE", "hybrid") 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/") return redirect("/oidc/authenticate/")
has_sso_options = bool( has_sso_options = bool(
getattr(settings, "KEYWARDEN_OIDC_ENABLED", False) oidc_login_enabled
or getattr(settings, "KEYWARDEN_SOCIAL_LOGIN_PROVIDERS", []) or social_providers
) )
show_native_login = ( show_native_login = (
request.method == "POST" request.method == "POST"
or request.GET.get("native") == "1" 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 # native or hybrid -> render Django's built-in login view
return auth_views.LoginView.as_view( return auth_views.LoginView.as_view(
template_name="accounts/login.html", template_name="accounts/login.html",
extra_context={ extra_context={
"social_providers": getattr(settings, "KEYWARDEN_SOCIAL_LOGIN_PROVIDERS", []), "social_providers": social_providers,
"oidc_enabled": bool(getattr(settings, "KEYWARDEN_OIDC_ENABLED", False)), "oidc_enabled": oidc_login_enabled,
"oidc_login_url": "/oidc/authenticate/", "oidc_login_url": "/oidc/authenticate/",
"oidc_provider_label": _oidc_provider_label(), "oidc_provider_label": _oidc_provider_label(),
"show_native_login": show_native_login, "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()] 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 BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = os.getenv("KEYWARDEN_SECRET_KEY") SECRET_KEY = os.getenv("KEYWARDEN_SECRET_KEY")
@@ -70,12 +76,15 @@ KEYWARDEN_SOCIAL_APPLE_CONFIGURED = bool(
and KEYWARDEN_SOCIAL_APPLE_CLIENT_SECRET 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_GOOGLE_CONFIGURED,
KEYWARDEN_SOCIAL_GITHUB_CONFIGURED, KEYWARDEN_SOCIAL_GITHUB_CONFIGURED,
KEYWARDEN_SOCIAL_APPLE_CONFIGURED, KEYWARDEN_SOCIAL_APPLE_CONFIGURED,
) )
)
) )
# Default to secure cookies and respect TLS termination headers. # 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_SIGN_ALGO = os.getenv("KEYWARDEN_OIDC_SIGN_ALGO", "RS256")
OIDC_RP_SCOPES = os.getenv("KEYWARDEN_OIDC_SCOPES", "openid email profile") 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_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_PROVIDER_ID = os.getenv("KEYWARDEN_OIDC_PROVIDER_ID", "oidc")
KEYWARDEN_OIDC_ISSUER = os.getenv("KEYWARDEN_OIDC_ISSUER", "").strip() 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_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_USERNAME_CLAIM = os.getenv("KEYWARDEN_OIDC_USERNAME_CLAIM", "preferred_username")
KEYWARDEN_OIDC_GROUPS_CLAIM = os.getenv("KEYWARDEN_OIDC_GROUPS_CLAIM", "groups") 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_DEMOTE_ON_MISS = env_bool("KEYWARDEN_OIDC_ADMIN_DEMOTE_ON_MISS", default=False)
KEYWARDEN_OIDC_ADMIN_GROUPS = env_csv("KEYWARDEN_OIDC_ADMIN_GROUPS") 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 = [] KEYWARDEN_SOCIAL_LOGIN_PROVIDERS = []
if KEYWARDEN_SOCIAL_GOOGLE_CONFIGURED: if KEYWARDEN_SOCIAL_AUTH_ENABLED and KEYWARDEN_SOCIAL_GOOGLE_CONFIGURED:
KEYWARDEN_SOCIAL_LOGIN_PROVIDERS.append( KEYWARDEN_SOCIAL_LOGIN_PROVIDERS.append(
{"id": "google", "name": "Google", "login_url": "/accounts/sso/google/login/"} {"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( KEYWARDEN_SOCIAL_LOGIN_PROVIDERS.append(
{"id": "github", "name": "GitHub", "login_url": "/accounts/sso/github/login/"} {"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( KEYWARDEN_SOCIAL_LOGIN_PROVIDERS.append(
{"id": "apple", "name": "Apple", "login_url": "/accounts/sso/apple/login/"} {"id": "apple", "name": "Apple", "login_url": "/accounts/sso/apple/login/"}
) )
@@ -378,7 +393,8 @@ if KEYWARDEN_SOCIAL_AUTH_ENABLED:
ACCOUNT_UNIQUE_EMAIL = True ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_USERNAME_REQUIRED = False ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = "email" 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_EMAIL_AUTHENTICATION = False
SOCIALACCOUNT_ADAPTER = "apps.accounts.social_auth.KeywardenSocialAccountAdapter" SOCIALACCOUNT_ADAPTER = "apps.accounts.social_auth.KeywardenSocialAccountAdapter"
SOCIALACCOUNT_LOGIN_ON_GET = True SOCIALACCOUNT_LOGIN_ON_GET = True
@@ -426,28 +442,37 @@ KEYWARDEN_SOCIAL_REQUIRE_VERIFIED_EMAIL = env_bool(
) )
# Auth mode: native | oidc | hybrid # Auth mode: native | oidc | hybrid
AUTH_MODE = os.getenv("KEYWARDEN_AUTH_MODE", "hybrid").lower() AUTH_MODE = KEYWARDEN_AUTH_MODE_REQUESTED
if AUTH_MODE not in {"native", "oidc", "hybrid"}:
AUTH_MODE = "hybrid"
if AUTH_MODE == "oidc" and not KEYWARDEN_OIDC_ENABLED: if AUTH_MODE == "oidc" and not KEYWARDEN_OIDC_ENABLED:
AUTH_MODE = "native" AUTH_MODE = "native"
KEYWARDEN_AUTH_MODE = AUTH_MODE KEYWARDEN_AUTH_MODE = AUTH_MODE
AUTHENTICATION_BACKENDS = [ if AUTH_MODE == "oidc":
"django.contrib.auth.backends.ModelBackend", AUTHENTICATION_BACKENDS = [
"guardian.backends.ObjectPermissionBackend", "apps.accounts.oidc.KeywardenOIDCAuthenticationBackend",
] "guardian.backends.ObjectPermissionBackend",
if KEYWARDEN_SOCIAL_AUTH_ENABLED: ]
AUTHENTICATION_BACKENDS.append("allauth.account.auth_backends.AuthenticationBackend") elif AUTH_MODE == "hybrid":
if KEYWARDEN_OIDC_ENABLED: AUTHENTICATION_BACKENDS = [
AUTHENTICATION_BACKENDS.append("apps.accounts.oidc.KeywardenOIDCAuthenticationBackend") "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": if AUTH_MODE == "oidc":
# OIDC-only: enforce identity provider logins. # OIDC-only: enforce identity provider logins.
LOGIN_URL = "/oidc/authenticate/" LOGIN_URL = "/oidc/authenticate/"
else: else:
LOGIN_URL = "/accounts/login/" 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/" LOGIN_REDIRECT_URL = "/servers/"
LOGOUT_REDIRECT_URL = "/" 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 = [ urlpatterns = [
path("admin/", admin.site.urls), 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): if getattr(settings, "KEYWARDEN_SOCIAL_AUTH_ENABLED", False):
urlpatterns.append(path("accounts/sso/", include("allauth.urls"))) urlpatterns.append(path("accounts/sso/", include("allauth.urls")))