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.
Related
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.
I'm looking for a way in which I can send out a user an email with a url that will log them into their user. This doesn't even necessarily have to expire. I have found tons of material on sessions however this is not what I'm after. What I need is this:
User submits something to the website
They have an account made in the background
An email is sent out containing a link "site.com/&token=foobar23124123"
The user can log in using that link (optional: for the next week)
Is there something out there that I'm missing that would help me or would I have to implement my own solution? Could I potentially just include the token from Django REST framework's Tokens?
Thank you for taking the time to read my question.
I'm Using Django 1.9 and Python 2.7
I don't think there is something for authenticating users using url get-parameters. AFAIK Django REST framework's Tokens uses HTTP headers for tokens.
You can write your own auth backend, it's quite easy. Here is an example
myproject/setting.py
AUTHENTICATION_BACKENDS = [
'myproject.backends.UrlTokenBackend',
'django.contrib.auth.backends.ModelBackend'
]
myproject/backends.py
class UrlTokenBackend(ModelBackend):
def authenticate(self, token):
try:
user = User.objects.get(token=token)
except User.DoesNotExist:
return None
if not user.is_active:
return None
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Now when you will call authenticate and login function Django will check your user against each of your backends. You can manually login user like this (this is view function):
from django.contrib.auth import authenticate, login
def user_auth(request):
token = request.GET.get('token')
user = authenticate(token=token)
login(request, user)
return redirect('index')
Update
Or you can use this hack and do only this (without custom backend):
def user_auth(request):
token = request.GET.get('token')
user = User.objects.get(token=token)
user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)
return redirect('index')
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.
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.