from mozilla_django_oidc.auth import OIDCAuthenticationBackend class AuthentikOIDCBackend(OIDCAuthenticationBackend): """ Minimal, safe backend: - preserves existing local auth (ModelBackend stays enabled) - creates/updates Django users from Authentik claims """ def filter_users_by_claims(self, claims): # Prefer stable identifiers. Authentik often provides email + preferred_username. email = (claims.get("email") or "").strip().lower() username = (claims.get("preferred_username") or claims.get("username") or "").strip() # If you want "email is identity", use email matching first: if email: return self.UserModel.objects.filter(email__iexact=email) # Fallback to username matching if no email if username: return self.UserModel.objects.filter(username__iexact=username) 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 username: user.username = username if email: user.email = email # Optional nice-to-have mappings user.first_name = claims.get("given_name", "") or user.first_name user.last_name = claims.get("family_name", "") or user.last_name user.set_unusable_password() # SSO-only unless you explicitly set local passwords 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 # keep username stable once set, unless you *want* it to track upstream changes 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