When I introduced django-axes to app with a login feature, many tests which use client.login started failing. In this article, I explain why this happened and how I solved the problem.
Background
What is django-axes?
django-axes is a Django package that monitors login attempts and to lock out users based on authentication setting.It helps prevent brute-force attacks.
(Official document)Why I used django-axes
I was working on collaborative project to develop a small app with a login feature.
To prevent brute-force attacks, we wanted to limit the number of login attempts, so we decided to introduce django-axes.
Issue
After implementing of django-axes, I ran all tests.
Many tests are failed, even though they had passed before introducing django-axes.
After investigating the errors, I found that all failing tests were related to login functionality. The root cause was that test did not include a request object.
When tests use client.login(as shown below), the user is authenticated without a HTTP request. However, django-axes determines whether a user is authenticated by inspecting the request. As a result, all tests using client.login failed.
- Failing pattern (using client.login)
self.user = User.objects.create_user(
username="login_user",
email="login@example.com",
password="LoginPass123",
)
self.client.login(email="login@example.com", password="LoginPass123")
On the other hand, test using client.post passed successfully because they include a HTTP request.
- Working pattern (using client.post)
self.user = User.objects.create_user(
username="login_user",
email="login@example.com",
password="LoginPass123",
)
self.client.post(
self.login_url,
{"email": "login@example.com", "password": "LoginPass123"},
REMOTE_ADDR="192.168.1.1",
)
Solutions
I found two practical solutions.
- Disable django-axes in tests using AXES_ENABLE By setting AXES_ENABLE = False in test settings, you can disable django-axes checks during testing. Of course, when testing features related to django-axes, you should enable it by setting AXES_ENABLE = True. (document)
This approach allows you to limit django-axes behavior only to tests that actually need it.
- Using force_login() If you use force_login(), Django skips the authentication process that django-axes hooks into.
There, when testing features not directly related to login verification, it is reasonable to use force_login() instead of client.login().
(document
- Working pattern (Using force_login)
self.user = User.objects.create_user(
username="login_user",
email="login@example.com",
password="LoginPass123",
)
self.client.force_login(self.user)
As mentioned earlier, using client.post() also works because it includes a request.
Conclusion
django-axes is a very useful package for enhance application security. However, when introducing it, we need to be carful about how authentication is handled in tests.
Depend on the app architecture, and testing strategy, we should choose the most appropriate solution.
I hope this help anyone facing similar issues.
Top comments (0)