.. _usage: 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 ip * ``manage.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 ip * ``reset(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.