I'm trying to use a custom backend for user authorization with django-nonrel (email adrresses instead of usernames, nothing fancy), and it's just not working. All calls to authenticate() return None. Here is my backend:
from django.contrib.auth.models import User, check_password
class EmailAuthBackend(object):
"""
Email Authentication Backend
Allows a user to sign in using an email/password pair rather than
a username/password pair.
"""
supports_anonymous_user = False
supports_object_permissions = True
def authenticate(self, email=None, password=None):
""" Authenticate a user based on email address as the user name. """
try:
user = User.objects.get(email=email)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
def get_user(self, user_id):
""" Get a User object from the user_id. """
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
And the view (I'm trying to log a user in immediately after they have signed up). new_user is always None:
def create_new_user(request):
if request.method == 'POST':
data = request.POST.copy()
data['username'] = ''.join([choice(letters) for i in xrange(30)])
data['password'] = data['password1']
form = CustomUserCreationForm(data)
if form.is_valid():
user = form.save(commit=False)
# user must be active for login to work
user.is_active = True
user.save() #not sure if this is actually saving
new_user = authenticate(username=data['username'],\
password=data['password'])
if new_user is None:
messages.error(request,"God damn it.")
login(request, new_user)
messages.info(request, "Thanks for registering! You are\
now logged in.")
return render_to_response('invoicer/welcome.html',
{'user':new_user})
else:
messages.error(request, form._errors)
return HttpResponseRedirect('//')
Can anyone give me some insights as to why my call to authenticate() isn't working?
Edit: Also, When deployed, my signup form is definitely creating users which can be seen in the data viewer on appengine.google.com
Turns out all I needed to do was change
authenticate(username=email
in the view to:
authenticate(email=email
My backend wasn't being sent an email so it defaulted to none. Shame on me. However, as I'm using the standard django login form, it will pass a named username and password to my backend, so I have to be able to handle both a username (which is actually the user's email address) and email as named keywords.
For now, I'm just doing it like this in my backend:
def authenticate(self, username=None, email=None, password=None):
""" Authenticate a user based on email address as the user name. """
if username:
might_be_email = username
elif email:
might_be_email = email
An try to find the user by might_be_email.
Related
I'm working on a django application. The application has a dashboard. How can i make it so that everytime the user wants to access the dashboard, he/she has to confirm their identity, with the same password they use to log into the same application?
Thank you
#Verify that the USERNAME and PASSWORD combination exist USING THE AUTHENTICATE METHOD,
Views.py:
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
#IN YOUR CASE get the pwd using forms or something,
instance_password = request.post.get('the_pwd_field')
user = authenticate(request, username= request.user.username, password= instance_password)
if user is not None:
# REDIRECT TO THE DASHBOARD
else:
# FAIL CASE SCENARIO
In views.py
from django.contrib.auth.hashers import check_password
form = YourFormForPassword(request.POST or None)
if form.is_valid():
currentpasswordentered = form.cleaned_data.get("try_password")
currentpassword = request.user.password
authenticate_user = check_password(currentpasswordentered, currentpassword)
if authenticate_user:
# REDIRECT TO THE DASHBOARD
else:
#Redirect to other page or keep same login page
If you want, user to enter credentials explicitly before accessing the dashboard, then you have to return the "form" that accepts creds from user, when they try to access the dashboard.
def dashboard_login_view(request):
context = {}
if request.method == 'POST':
form = DashboardLoginForm(request.POST)
if form.is_valid():
username = form.cleaned_data.get('dashboard_login_username').lower()
password = form.cleaned_data.get('dashboard_login_password')
authenticated = check_password(password)
if authenticated:
return HttpResponseRedirect(reverse('dashboard_view'))
else:
messages.error('User is not authenticated.')
return HttpResponseRedirect(reverse('login'))
else:
context['form_one'] = DashboardLoginForm()
return render(request, 'dashboardlogin.html', context)
I am trying to log in to a database that is not the default database, and for that I've wrote a custom authentication code but whenever I try to login the method returns an AnonymousUser. I've no idea why is it doing so because user authentication is done properly using the authenticate method.
Any help would be really appreciated.
MY FILES
views.py
def login_authentication(request):
if request.method == "POST":
form = New_Login_Form(request.POST)
# print(request.POST)
if form.is_valid():
email = request.POST['email']
password = request.POST['password']
user_operating_company = request.POST['user_operating_company']
user = authenticate(request, email=email,
password=password, db=user_operating_company)
if user:
login(request, user, user_operating_company)
return redirect('test')
else:
form = New_Login_Form()
return render(request, 'index.html', {'form': form})
backends.py
from django.contrib.auth.backends import ModelBackend
from .models import Account
class CustomAuthenticate(ModelBackend):
def authenticate(self, request, email=None, password=None, db=None):
try:
user = Account.objects.all().using(db).get(email=email)
if user.check_password(password):
return user
except:
return None
def get_user(self, request, email, db):
try:
return Account.objects.using(db).get(pk=email)
except:
return None
and in the
settings.py
AUTHENTICATION_BACKENDS = ('accounts.backends.CustomAuthenticate', 'django.contrib.auth.backends.ModelBackend')
EDIT:
I made the changes as per #schillingt 's answer the updated backend is:
from django.contrib.auth.backends import ModelBackend
from .models import Account
class CustomAuthenticate(ModelBackend):
def authenticate(self, request, email=None, password=None, db=None):
self.db = db
try:
user = Account.objects.using(db).get(email=email)
if user.check_password(password):
return user
except Account.DoesNotExist:
return None
def get_user(self, email):
try:
user = Account.objects.using(self.db).get(pk=email)
except Account.DoesNotExist:
return None
return user if self.user_can_authenticate(user) else None
But now it gives me an error which says
'CustomAuthenticate' object has no attribute 'db'
I believe you have the wrong signature for get_user. ModelBackend's is:
def get_user(self, user_id):
try:
user = UserModel._default_manager.get(pk=user_id)
except UserModel.DoesNotExist:
return None
return user if self.user_can_authenticate(user) else None
This method is used by django.contrib.auth.get_user. Does your backend have a reference to the db instance that should be used? Or is that defined on the request? If it's defined on the request, you may have to monkey patch the django.contrib.auth.get_user method to supply the proper parameters to the call to the backend's get_user method so that you have the right db instance.
Edit:
This makes it seem like I'm wrong. You shouldn't have to monkey patch django.contrib.auth.get_user. You should be able to set the db instance on the backend instance in authenticate, then utilize that in get_user.
In Django default user authentication is integrated through Username and Password. In my project profile page, I have an option to change Username. So, it is necessary to change my authentication system in back end and front end with email and password.
Using authentication backend i can change default authentication system through email and password in admin. Here is the code -
class EmailBackend(object):
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
user = UserModel.objects.get(email=username)
except UserModel.DoesNotExist:
return None
else:
if getattr(user, 'is_active', False) and user.check_password(password):
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
In settings.py -
AUTHENTICATION_BACKENDS = (
'apps.account.email-auth.EmailBackend',
)
I was wondering how could i do the authentication through in front end. Already, I prepared front login page through email and password.
But see form.errors and predict must be missing any front authentication like AUTHENTICATION_BACKENDS
Thank you very much for your help!
Actually answer is within the method -
def authenticate(self, username=None, password=None, **kwargs):
In front-end, naming the user input with email wouldn't be passed within the method then form errors show up with don't match credentials. So, simply taking input as username solves the trick.
There is a package django-allauth. It handles the authentication.
It allows using 'email' and 'password' or 'username' and 'password' for authentication. It includes forms and everything else needed.
Django-cookiecutter project template uses this package to handle authentication, so you can look there and use it as a sample.
In my django app, I have written a custom authentication backend following the example in the docs that appears to be working fine. (I email users a login url containing a signed token; the view below handles these token login urls.) I can confirm from the log messages that request.user is being set correctly.
However, the profile view, protected by the #login_required decorator, redirects to the login page as if the user were not logged in. It seems like the call to login is not working. Am I overlooking something? I'm not sure how to debug this further. (As this is my first django app, I decided to write my own login view, and that's working fine...) Thanks!
From views.py:
def token_login(request, token):
user = authenticate(token=token)
if user:
log.info("LOGGING IN {}".format(user))
login(request, user)
log.info(request.user)
return redirect('myapp:profile')
else:
return render(request, 'myapp/error.html', {
'title': 'Invalid Token',
'message': 'Sorry, this is not a valid login token.'
})
Custom Authentication Backend:
class TokenBackend:
"""
Authenticates against a signed token; creates a new user if none existed before.
"""
def get_user(self, username):
try:
User.objects.get(pk=username)
except User.DoesNotExist:
return None
def authenticate(self, token):
log.info("AUTHENTICATING WITH TOKENBACKEND")
try:
token_contents = signing.loads(token, salt=settings.SALT,
max_age=settings.PASSWORD_TOKEN_MAX_AGE)
log.info(token_contents)
except signing.BadSignature:
return None
try:
user = User.objects.get(email=token_contents['email'])
except User.DoesNotExist:
user = User(
username=self.username_from_email(token_contents['email']),
password=self.random_password()
)
user.save()
return user
def random_password(self):
return ''.join(random.choice(string.ascii_uppercase + string.digits)
for _ in range(10))
def username_from_email(self, email):
return email.split('#')[0]
from settings.py:
AUTHENTICATION_BACKENDS = (
'myapp.auth.TokenBackend',
'django.contrib.auth.backends.ModelBackend'
)
Oops. I wasn't returning the User in TokenBackend.get_user.
I was working with a legacy database where there was a table 'tbl_personaldetails', from where i ported data to custome user model.
In code to port data from tbl_personaldetails, i use user.set_password(password) which sets password as hash in user table.
Trouble is when i try to authenticate(username=username, password=password) where password and username are plain text, authenticate returns None (Even for superuser account from which i can login in admin section).
The code to login is as follows:
class LoginView(FormView):
form_class = LoginForm
template_name = 'account/login.html'
def get_success_url(self):
return reverse("userHomeAfterLogin")
def form_valid(self, form):
email = form.cleaned_data['email'].lower().strip()
password = form.cleaned_data['password']
user = authenticate(email=email, password=password)
if user:
login(self.request, user)
return redirect(self.get_success_url())
else:
try:
user = User.objects.get(email__iexact=email)
if not check_password(password, user.password):
form._errors['password'] = ErrorList([u'That is not the correct Password.'])
except User.DoesNotExist:
form._errors['email'] = ErrorList([u'This email is not registered with us.'])
context = self.get_context_data(form=form)
return self.render_to_response(context)
As of now it flows like this:
1.authenticate returns none, landing up in else part:
2. can retrieve the user with email and check_password is correct.
3. it renders the form w/o any error message
.
what is it that i am doing wrong, everything looks fine though
As far as I understand from the code snippet, you are using email as your username. With email address, Django's authenticate will never work. It expects username instead. See code below.
def authenticate(**credentials):
"""
If the given credentials are valid, return a User object.
"""
for backend in get_backends():
try:
user = backend.authenticate(**credentials)
except TypeError:
# This backend doesn't accept these credentials as arguments. Try the next one.
continue
if user is None:
continue
# Annotate the user object with the path of the backend.
user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
return user
In order to use email address as the username field, please refer to http://justcramer.com/2008/08/23/logging-in-with-email-addresses-in-django/.
Hope this helps.