I want to do the following with django's authentication:
Log incorrect log-in attempts
Temporarily lock accounts after 'x' number of incorrect log-in attempts
Log successful log-ins.
I thought a custom auth backend would be the solution.
I can do most of what i want, but I want to log the IP and REMOTE_HOST of the user making the attempt.
how can I access the request object in the auth backend?
Thanks
The authentication backend can take any number of custom parameters for the authenticate() method. For example:
class MyBackend:
def authenticate(self, username=None, password=None, request=None):
# check username, password
if request is not None:
# log values from request object
If you are calling authenticate in your own view, you can pass the request object:
from django.contrib.auth import authenticate
def login(request):
# discover username and password
authenticate(username=username, password=password, request=request)
# continue as normal
If you're using django's login view (or the admin login), you wont have the extra information. Put simply, you'll have to use your own custom login view.
Also, be careful when automatically locking accounts: you allow someone to deliberately lock one of your user's accounts (denial of service). There are ways around this. Also, make sure your log of incorrect attempts doesn't contain any attempted passwords.
In recent versions of Django, authenticate() accepts "request" as first parameter:
optionally since Django 1.1
required since Django 2.1
See:
https://docs.djangoproject.com/en/2.1/releases/1.11/#deprecated-features-1-11
https://docs.djangoproject.com/en/2.1/releases/2.1/
Related
I am working on a project that's running on django. I would like to authenticate in multiple places. First, I would like to maintain the standard authentication mechanism and continue using it for site administration. Second, I would like to intercept the login request in addition to the standard authentication, and check if the user has is authenticated on another system and store a session variable or cookie to be used later if authenticated. Then on logout remove the session variable or cookie. The second authentication mechanism should not affect the first. In addition, if the first succeeds and the second fails, it should have no affect on the standard administration.
I have looked into declaring a custom authentication backend in the settings AUTHENTICATION_BACKENDS tuple. But from what I understand, it authenticates in order and will stop authenticating once a match is made.
Any guidance in regards to this would be most appreciated. Thanks
If all you need is set and unset cookie or session var you can use signals sent by authentication module (docs)
Example:
from django.contrib.auth.signals import user_logged_in, user_logged_out
def user_logged_in_hook(sender, **kwargs):
# kwargs will contain request and user objects
print kwargs
def user_logged_out_hook(sender, **kwargs):
# kwargs will contain request and user objects
print kwargs
user_logged_in.connect(user_logged_in_hook)
user_logged_out.connect(user_logged_out_hook)
Is there any way in django to perform login using credentials supplied while accessing any view in the application?
My motivation here is availability monitoring using a service such as Pingdom. Most of the urls I want to validated their availability are decorated with a #login_required which makes it impossible to access unless I have previously logged in.
My idea solution will be a way to access my views while supplying credentials in GET or POST parameters. Another alternative could probably be a site uptime monitoring service that supports logging in and acquiring a session prior to accessing the URL in question.
Update
Thanks to #Filip Dupanović's direction and the code from here my simple working middleware looks like this:
from django.contrib.auth import authenticate, login
class AuthenticationEverywhereMiddleware(object):
"""
Middleware to allow logging in by supplying login credentials in any URL
"""
def process_request(self, request):
if (request.GET.get('authenticateEverywhere','') == 'GET'):
username = request.GET['username']
password = request.GET['password']
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
I have added the triggering parameter authenticateEverywhere to prevent any possible clashes with views that might use username or password parameters.
You most certainly can! You'll need to write a custom middleware class that implements a custom process_request method where you'll be able to inspect the request object, obtain the credentials and sign the user in before the request gets routed to a view that's decorated with login_required.
I am using django and have email verification feature so when someone verifiy email , I activate his/her account by setting status. If some one will someone else email address then he will not be able to verify and will not able to login with that. So I was expecting django to look at status of user also while login as this status field is in table that is of django auth app. While on login django tries to login with only username param. So on login it says:
MultipleObjectsReturned at /accounts/login/
get() returned more than one User -- it returned 2! Lookup parameters were {'username': u'kaasib122#gmail.com'}
Is there a way to add status field also in lookup? Is there something in cofiguration for that or I need to write some sort of login backend for that? Or should I write my own view for login table in some different way? What is recommended way? Also I am thinking to let user either login using email or username in username field. So what is recommended way?
You need to write an authentication backend that suits your needs. Check out the django documentation.
Basically, you will need to write an "authenticate" method which will use "status" in the filter parameters. For using email or username, you could basically filter on one field (username or email) and if no User objects are returned for that field, filter on the other. Or you can get the user in a single filter by ORing those fields. Here's a pseudocode:
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
class AuthenticationBackend(ModelBackend):
def authenticate(self, username=None, password=None):
try:
user = User.objects.get(email=username, status=True)
except User.DoesNotExist:
try:
user = User.objects.get(username=username, status=True)
except User.DoesNotExist:
return None
# check password too
if user.check_password(password):
return user
return None
Note that when you write new authentication backend, you need to add it to AUTHENTICATION_BACKENDS parameter in your settings.py for it to be used. If you don't need the default one Django provides because you have customized it, just override that parameter, no need to add to the tuple.
i've recently implemented a simple change password view in my django project. The thing is that the old session should be destroyed for security reasons. What's the best way of doing this without asking the user to log in again.
I think i could just logout/login him/her, something like this:
from django.contrib.auth import login as auth_login
from django.contrib.auth import logout as auth_logout
#login_required
def change_password(request):
# My stuff
request.user.set_password(new_password)
request.user.save()
# I need this:
logout(request)
login(request,request.user)
But i think this is not the best idea. What do you think?
Is there another way to do this?
Am I missing something? (I mean, is this secure)
Take a look at this app https://github.com/atugushev/django-password-session.
This package makes invalidated all sessions (except a current session) after change a password.
Also this feature finally was implemented in Django 1.7. See: https://docs.djangoproject.com/en/dev/topics/auth/default/#session-invalidation-on-password-change
I just found out that this is now a built-in feature of Django, and has been since 1.7:
https://docs.djangoproject.com/en/1.7/topics/auth/default/#session-invalidation-on-password-change
Essentially, all sessions now include a hash of the users' password, so if the user ever changes their password, all their existing sessions are automatically invalidated.
So, the short answer to your question is: upgrade django.
One possibly undesirable side effect of this change is that, by default, a user ends up having to log in again as soon as they change their password. So you probably actually want the current user session to stay logged in. See the docs already linked, Django's built-in views for password change do that for you default, or you can manually call a function called update_session_auth_hash
django clears the session on logout so you will be fine:
https://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.logout
When you call logout(), the session data for the current request is completely cleaned out. All existing data is removed. This is to prevent another person from using the same Web browser to log in and have access to the previous user's session data.
I don't understand whats are these security reasons that forces to reset session. But, the way is:
#login_required
def change_password(request):
request.user.set_password(new_password)
request.user.save()
username = request.user.username
logout(request)
user = authenticate(username=username, password=new_password) #<-- here!!
if user is not None:
login(request,user)
else:
#raise your exception
you should authenticate before login. Quoting doc:
Calling authenticate() first When you're manually logging a user in,
you must call authenticate() before you call login(). authenticate()
sets an attribute on the User noting which authentication backend
successfully authenticated that user (see the backends documentation
for details), and this information is needed later during the login
process.
I'm trying to set up a custom backend that queries another database, for which I have created a model in the system. It uses its own rules (email instead of username, and a differently salted/hashed password) so I can't use built in authentication. I've set up a custom authentication backend like so:
class BlahBlahBackend:
def check_password():
# check password code here
return true
def authenticate(self, email=None, password=None):
import myapp.models.loginmodel
try:
person = myapp.models.loginmodel.People.objects.get(email=email)
if check_password(password, person.password):
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
username=person.first_name + person.last_name
name_count = User.objects.filter(username__startswith = username).count()
if name_count:
username = '%s%s'%(username, name_count + 1)
user = User.objects.create_user(username,email)
else:
user = User.objects.create_user(username,email)
except People.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
I've added BlahBlahBackend as an authentication backend:
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',
'socialauth.auth_backends.OpenIdBackend',
'socialauth.auth_backends.TwitterBackend',
'socialauth.auth_backends.FacebookBackend',
'socialauth.auth_backends.BlahBlahBackend',
)
As you can see, I'm also using some pre-existing auth backends that are also in socialauth.
I have a submission form that points to the following view:
def blahblah_login_complete(request):
email = request.POST.get('email')
password = request.POST.get('password')
user = authenticate(email,password)
# if user is authenticated then login user
if user:
login(request, user)
else:
return HttpResponseRedirect(reverse('socialauth_login_page'))
However, when I try to login in this way, it seems like one or more of the other backends are acting as if I'm trying to log in using their method.
I read that backends are cached and so ran
Session.objects.all().delete()
to clear out the backends cache.
My main questions are:
Does the order in which items are listed in AUTHENTICATION_BACKENDS
How does the system decide/know which Backend to use? This was never made clear by any of the documentation, and I find it a bit confusing.
Is there any way to force the use of a specific authorization based on the request. In other words, if someone submits a form, is there a way to force them to use the form-login-based authentication as opposed to the login via openid or Twitter?
Update:
It works! This is very cool, thanks. I guess it just seemed like the django doc was saying "You don't have to do anything else, it just sort of works like magic" and it turns out this is absolutely the case. So long as the backend is there and the credentials are set up correctly, the authentication will work. As it turns out the real problem was a misconfiguration in the urls.py file that wasn't sending the post from the login form to the correct handler, which is why it kept trying to use another authentication method.
You're supposed to use keyword arguments to django.contrib.auth.authenticate() The names should match the names of the arguments in your backend's authenticate method. The default backend handles the names 'username' & 'password'.
Your backend can use a different name for the keyword arguments e.g.: blahblah_email and blahblah_password, and then call authenticate(blahblah_email=..., blahblah_password=...).
It's clearly described here -
django tries each backend in order
defined, if first fails to
authenticate it goes to second etc.
I believe you can load backend class dynamically and authenticate
directly through it. Look at django authenticate() function sources on how to do that.
I guess django-cas will be a good reference for you :)
And yes, the order of AUTHENTICATION_BACKENDS matters.
Django loops over the backends list and stop at the first backend that has a authenticate method accepting the credential parameters you passed to it.