Usage¶
django-axes
listens to signals from django.contrib.auth.signals
to
log access attempts:
user_logged_in
user_logged_out
user_login_failed
You can also use django-axes
with your own auth module, but you’ll need
to ensure that it sends the correct signals in order for django-axes
to
log the access attempts.
Quickstart¶
Once axes
is in your INSTALLED_APPS
in your project settings file,
you can login and logout of your application via the django.contrib.auth
views. The access attempts will be logged and visible in the “Access Attempts”
secion of the admin app.
By default, django-axes will lock out repeated attempts from the same IP
address. You can allow this IP to attempt again by deleting the relevant
AccessAttempt
records in the admin.
You can also use the axes_reset
and axes_reset_user
management commands
using Django’s manage.py
.
manage.py axes_reset
will reset all lockouts and access records.manage.py axes_reset ip
will clear lockout/records for ipmanage.py axes_reset_user username
will clear lockout/records for an username
In your code, you can use from axes.utils import reset
.
reset()
will reset all lockouts and access records.reset(ip=ip)
will clear lockout/records for ipreset(username=username)
will clear lockout/records for a username
Example usage¶
Here is a more detailed example of sending the necessary signals using
django-axes and a custom auth backend at an endpoint that expects JSON
requests. The custom authentication can be swapped out with authenticate
and login
from django.contrib.auth
, but beware that those methods take
care of sending the nessary signals for you, and there is no need to duplicate
them as per the example.
forms.py:
from django import forms
class LoginForm(forms.Form):
username = forms.CharField(max_length=128, required=True)
password = forms.CharField(max_length=128, required=True)
views.py:
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.http import JsonResponse, HttpResponse
from django.contrib.auth.signals import user_logged_in,\
user_logged_out,\
user_login_failed
import json
from myapp.forms import LoginForm
from myapp.auth import custom_authenticate, custom_login
from axes.decorators import axes_dispatch
@method_decorator(axes_dispatch, name='dispatch')
@method_decorator(csrf_exempt, name='dispatch')
class Login(View):
''' Custom login view that takes JSON credentials '''
http_method_names = ['post',]
def post(self, request):
# decode post json to dict & validate
post_data = json.loads(request.body.decode('utf-8'))
form = LoginForm(post_data)
if not form.is_valid():
# inform axes of failed login
user_login_failed.send(
sender = User,
request = request,
credentials = {
'username': form.cleaned_data.get('username')
}
)
return HttpResponse(status=400)
user = custom_authenticate(
request = request,
username = form.cleaned_data.get('username'),
password = form.cleaned_data.get('password'),
)
if user is not None:
custom_login(request, user)
user_logged_in.send(
sender = User,
request = request,
user = user,
)
return JsonResponse({'message':'success!'}, status=200)
else:
user_login_failed.send(
sender = User,
request = request,
credentials = {
'username':form.cleaned_data.get('username')
},
)
return HttpResponse(status=403)
urls.py:
from django.urls import path
from myapp.views import Login
urlpatterns = [
path('login/', Login.as_view(), name='login'),
]
Integration with django-allauth¶
axes
relies on having login information stored under AXES_USERNAME_FORM_FIELD
key
both in request.POST
and in credentials
dict passed to
user_login_failed
signal. This is not the case with allauth
.
allauth
always uses login
key in post POST data but it becomes username
key in credentials
dict in signal handler.
To overcome this you need to use custom login form that duplicates the value
of username
key under a login
key in that dict
(and set AXES_USERNAME_FORM_FIELD = 'login'
).
You also need to decorate dispatch()
and form_invalid()
methods
of the allauth
login view. By default axes
is patching only the
LoginView
from django.contrib.auth
app and with allauth
you have to
do the patching of views yourself.
settings.py:
AXES_USERNAME_FORM_FIELD = 'login'
forms.py:
from allauth.account.forms import LoginForm
class AllauthCompatLoginForm(LoginForm):
def user_credentials(self):
credentials = super(AllauthCompatLoginForm, self).user_credentials()
credentials['login'] = credentials.get('email') or credentials.get('username')
return credentials
urls.py:
from allauth.account.views import LoginView
from axes.decorators import axes_dispatch
from axes.decorators import axes_form_invalid
from django.utils.decorators import method_decorator
from my_app.forms import AllauthCompatLoginForm
LoginView.dispatch = method_decorator(axes_dispatch)(LoginView.dispatch)
LoginView.form_invalid = method_decorator(axes_form_invalid)(LoginView.form_invalid)
urlpatterns = [
# ...
url(r'^accounts/login/$', # Override allauth's default view with a patched view
LoginView.as_view(form_class=AllauthCompatLoginForm),
name="account_login"),
url(r'^accounts/', include('allauth.urls')),
# ...
]
Altering username before login¶
In special cases, you may have the need to modify the username that is
submitted before attempting to authenticate. For example, adding namespacing or
removing client-set prefixes. In these cases, axes
needs to know how to make
these changes so that it can correctly identify the user without any form
cleaning or validation. This is where the AXES_USERNAME_CALLABLE
setting
comes in. You can define how to make these modifications in a callable that
takes a request object, and provide that callable to axes
via this setting.
For example, a function like this could take a post body with something like
username='prefixed-username'
and namespace=my_namespace
and turn it
into my_namespace-username
:
settings.py:
def sample_username_modifier(request):
provided_username = request.POST.get('username')
some_namespace = request.POST.get('namespace')
return '-'.join([some_namespace, provided_username[9:]])
AXES_USERNAME_CALLABLE = sample_username_modifier
NOTE: You still have to make these modifications yourself before calling
authenticate. If you want to re-use the same function for consistency, that’s
fine, but axes
doesn’t inject these changes into the authentication flow
for you.