User not logging in with Custom Authentication Backend

I’ve built an app that uses a custom authentication backend because I needed to integrate ldap. It works when authenticating into the admin console, but doesn’t on my app.

The issue
After hitting the submit button on the login page, I am redirected back to the login page even if the authentication was successful.

My troubleshooting

  • I am implementing class-based views and using the LoginRequiredMixin. I thought this was causing a redirection loop back to login, so I removed it. I’m no longer being redirected back to login, but using {{ user.is_authenticated }} within my index page tells me that the user isn’t being “logged in”. I found that this wasn’t the cause so I added the mixin back in.

  • I’ve double-checked the methods within the custom backend to ensure they’re working like Django’s backend, but haven’t seen any errors.

  • I’ve also found that request.user returns Anonymous User, but within the request.session, the _auth_user_id returns the authenticated user. It seems like there’s a disconnect between the request user and auth user.

Please let me know what information you need to help me troubleshoot.

Thank you!

Custom Backend Code

class LDAPAuthBackend(BaseBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        # will validate if the user exists in ldap
        user_exists = ldap.check_user_exists(username, "username")
        if user_exists[0]:
            user = None
            domain = user_exists[1]
            # checking to see if the user exists in the database, if not, create them
            try:
                user = User.objects.get(username=username)
                ldap_data = ldap.ldap_login(username, password, domain)
                if not ldap_data[0]:
                    return None
                return user
            except User.DoesNotExist:
                # create a new user if they authenticate
                # getting ldap data to create them in the system
                ldap_data = ldap.ldap_login(username, password, user_exists[1])
                if ldap_data[0]:
                    user_data = ldap_data[1]
                    user = User(
                        username=user_data["username"],
                        email=user_data["email"],
                        first_name=user_data["first_name"],
                        last_name=user_data["last_name"],
                        is_staff=True,
                        is_superuser=True if user_data["username"].lower()  in settings.WEB_STAFF else False
                    )
                    user.set_unusable_password()
                    user.save()
                    return user
        return None

    def get_user(self, user_id):
        try:
            return User.objects.get(id=user_id)
        except ObjectDoesNotExist:
            return None

Login View Code

class UserLogin(View):
    title = "Login "
    template = "ipa/user-login.html"

    def get(self, request):
        print(request.user)
        if request.user.is_authenticated:
            return redirect("index")
        else:
            form = CustomLoginForm()
            context = {
                "title": self.title,
                "form": form,
                "next": request.GET.get("next"),
            }
            return render(request, self.template, context)

    def post(self, request):
        form = CustomLoginForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data.get("username")
            password = form.cleaned_data.get("password")
            # authenticate user
            user = ldap.authenticate(self.request, username=username, password=password)
            # user = authenticate(self.request, username=username, password=password)
            # login user
            if user is not None and user.is_active:
                login(self.request, user, backend="core.backends.LDAPBackend")
                index_urls = ["/", "/ipa/"]
                redirect_to = request.GET.get("next") if request.GET.get("next") not in index_urls and not None else "index"
                if redirect_to == "None":
                    redirect_to = "index"
                print(request.user.is_authenticated)
                return redirect(redirect_to)
                # return HttpResponseRedirect(reverse_lazy('index'))
            else:
                messages.add_message(request, messages.ERROR, "Username or password is incorrect\nUse Active directory credentials.")
                return redirect("login")

For situations like this, particularly when you are working with remote resources, debugging generally involves either using a debugger, or adding in a bunch of print statements to see what the data is that you’re sending, receiving, or using at every step along the way.

For example, in your authenticate method, I’d print user_exists after the ldap.check_user_exists call. I’d print user after the User.objects.get, then ldap_data after ldap.ldap_login, etc, etc, etc.

Another thing you could try would be to use the Django shell to manually execute these functions to be able to examine the responses.

There are many things that could be wrong. You may not be sending the data in the exact format expected, or not getting back what you think you’re going to get. Finding the real issue here is likely going to require that you keep digging until you find the cause.

Side note: I question the accuracy of this expression:

Since not x is of higher precedence than and, I believe this gets parsed as:

request.GET.get("next") 
  if (request.GET.get("next") not in index_urls)
     and 
     (not None) 
else "index"`

The expression not None is always going to be True, so I’m not sure what you’re trying to test here.

About the side note - You’re right with that logic. I will remove this and ensure the next parameter is always set to a valid url name. I need to revisit the logic of what I’m trying to achieve there.

I’m using pycharm; with that, I’m using their debugger which allows me to evaluate all objects in the current session. I see that after the login, within the post method, request.user.is_authenticated returns True. Somewhere within the redirect, I feel like a new session is being created.

I’ve tried following the logic of how the redirect functions but have yet to see why/how the sessions are either being carried onto the next request or re-created.

There appear to be two different situations here.

If the browser has a session id for an existing, authenticated, user, then the login function is going to create a new session id and delete the old one. (See the login function in django.contrib.auth.__init__) This new sessionid cookie should be sent to the browser in the response sent after being logged in.

If the browser is currently without an authenticated sessionid, then it appears to keep that same session.

I’d probably want to look at the cookies being exchanged between the browser and the server for each exchange to see if the cookie is being dropped somewhere in between.

Update on the issue. The authentication process is now functioning. Here’s what I did to resolve the steps. Although it is working, I still don’t know why this works versus my other method.

I set my UserLogin view to inherit the LoginView class from django.contrib.auth.views. I also changed my login form to inhert AuthenticationForm from django.contrib.auth.forms.

I changed the context of my view to this:

class UserLogin(LoginView):
    title = "Login"
    template_name = "ipa/user-login.html"
    authentication_form = CustomLoginForm

    @method_decorator(sensitive_post_parameters())
    @method_decorator(csrf_protect)
    @method_decorator(never_cache)
    def dispatch(self, request, *args, **kwargs):
        return super(UserLogin, self).dispatch(request, *args, **kwargs)

can you show the exact code for CustomLoginForm? Also can you include complete code for this problem. I am also having the same issue but not able to troubleshoot. Thanks

My CustomLoginForm is just a basic for that inherits AuthenticationForm

Here’s the code:

class CustomLoginForm(AuthenticationForm):
    username = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control", "autofocus": True}))
    password = forms.CharField(widget=forms.PasswordInput(attrs={"class": "form-control"}))

Not all problems are the same though. If you want assistance with troubleshooting I would create a new post with the errors and code so that we can best assist you.