from mozilla_django_oidc.auth import OIDCAuthenticationBackend class AuthentikOIDCBackend(OIDCAuthenticationBackend): """ Custom backend to integrate Authentik OIDC with existing Django users without breaking local authentication. """ def filter_users_by_claims(self, claims): email = (claims.get("email") or "").strip().lower() username = ( claims.get("preferred_username") or claims.get("username") or "" ).strip() # 1) Prefer matching by email if email: qs = self.UserModel.objects.filter(email__iexact=email) if qs.exists(): return qs # 2) Fallback to username match (critical for existing local users) if username: qs = self.UserModel.objects.filter(username__iexact=username) if qs.exists(): return qs # 3) Otherwise, no match -> create user return self.UserModel.objects.none() def create_user(self, claims): user = super().create_user(claims) email = (claims.get("email") or "").strip().lower() username = ( claims.get("preferred_username") or claims.get("username") or email or "" ).strip() if email: user.email = email # Only set username if Django hasn't already set one if username and not user.username: user.username = username user.first_name = claims.get("given_name", "") or user.first_name user.last_name = claims.get("family_name", "") or user.last_name # SSO-only account unless you explicitly add a password later user.set_unusable_password() user.save() return user def update_user(self, user, claims): email = (claims.get("email") or "").strip().lower() if email and user.email.lower() != email: user.email = email user.first_name = claims.get("given_name", "") or user.first_name user.last_name = claims.get("family_name", "") or user.last_name user.save() return user