I use Django with Django Rest Framework as the backend for my site. Registration is disabled, and login with password is disabled. The only way a user can register and login is with Django Social Auth, that exchanges (in this case Discord) a social token for a Django token, and in that process the user is created if they don't exist for that email.
So in Django, the user exists, with a username and email, but they don't have a password.
How can these users login to the admin panel?
Registration is disabled, and login with password is disabled.
Registration is not the only way of creating users. You can create superuser using createsuperuser shell command. Then you can use it to login into admin site.
Now, the question is if you want to allow all users to admin site. IMHO, you should not. Still, if you want to then you can add a custom auth backend, like this:
class CustomBackend(BaseBackend):
def authenticate(self, request, username=None, password=None):
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
return None
check_token = Token.objects.filter(user=user, token=token).exists()
if check_token:
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
And add the new backend in settings.py:
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend', 'path.to.CustomBackend']
Related
I've been nearly done with my django-react app with all the models, serializers, and APIs. But now I need to change the authentication method to also use email.
class User(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField(unique=True)
# Notice there is no username field, because it is included in AbstractUser
I've looked through some possible solutions but they involve using AbstractBaseUser while some other write a custom authentication backend that omits the username completely. This might break other views and frontend since we have been mainly using username.
I still want to keep the username and use both username and email to authenticate.
Is there any simple idea (preferably kept using AbstractUser) that I wouldn't have to make major change?
This code is taken from the book “Django 3 by Example”, chapter 4, section “Building a custom authentication backend”.
Django provides a simple way to define your own authentication backends. An authentication backend is a class that provides the following two methods: authenticate() and get_user().
Suppose you have the account app in your project to manage the authentication logic. You can create the authentication.py file with the following code:
class EmailAuthBackend(object):
"""
Authenticate using an e-mail address.
"""
def authenticate(self, request, username=None, password=None):
try:
user = User.objects.get(email=username)
if user.check_password(password):
return user
return None
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
The preceding code works as follows:
authenticate(): You try to retrieve a user with the given email address and check the password using the built-in check_password() method of the user model. This method handles the password hashing to compare the given password with the password stored in the database.
get_user(): You get a user through the ID provided in the user_id parameter. Django uses the backend that authenticated the user to retrieve the User object for the duration of the user session.
Edit the settings.py file of your project and add the following setting:
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'account.authentication.EmailAuthBackend',
]
In the preceding setting, you keep the default ModelBackend that is used to authenticate with the username and password and include your own email-based authentication backend.
Django will try to authenticate the user against each of the backends, so now you should be able to log in seamlessly using your username or email account. User credentials will be checked using the ModelBackend authentication backend, and if no user is returned, the credentials will be checked using your custom EmailAuthBackend backend.
I have a model called Van that have 2 fields: Plate and Password. I should use this informations to login in a frontend system (not django admin).
Searching on the internet i just found example using Bultin Login System but i need a custom login using only Van model.
Any suggestions?
You need to implement custom authorization backend.
For example you can modify this:
from django.contrib.auth.backends import ModelBackend
class EmailAuthBackend(ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
try:
user = User.objects.get(email=username)
if user.check_password(password):
return user
except ObjectDoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
User().set_password(password)
Is it possible to use Django allauth with the authentication method set to 'email' when using it on multiple sites?
I'm aiming to allow a user with the email address bob#example.com to create an account at site1.com and a separate account at site2.com.
In order to use email authentication, I need to leave UNIQUE_EMAIL set to True in the settings but this prevents users who already have accounts in one site from creating accounts in the other site.
I am assuming you'd like to allow the same email to be registered separately for each of the sites in your Django setup.
Looking at the allauth code; it appears that it is infeasible to do so at the moment, likely because allauth does not take into account site ID as part of the User signup process.
class AppSettings(object):
class AuthenticationMethod:
USERNAME = 'username'
EMAIL = 'email'
USERNAME_EMAIL = 'username_email'
class EmailVerificationMethod:
# After signing up, keep the user account inactive until the email
# address is verified
MANDATORY = 'mandatory'
# Allow login with unverified e-mail (e-mail verification is
# still sent)
OPTIONAL = 'optional'
# Don't send e-mail verification mails during signup
NONE = 'none'
def __init__(self, prefix):
self.prefix = prefix
# If login is by email, email must be required
assert (not self.AUTHENTICATION_METHOD ==
self.AuthenticationMethod.EMAIL) or self.EMAIL_REQUIRED
# If login includes email, login must be unique
assert (self.AUTHENTICATION_METHOD ==
self.AuthenticationMethod.USERNAME) or self.UNIQUE_EMAIL
One way to do this would be as follows:
- Keep allauth AUTHENTICATION_METHOD as Username
- Store the site alongside the User information, perhaps in a UserProfile or by overriding the User Model.
- Make the combination of Email and Site unique.
- Override the LoginView such that the user enters email; you can translate the combination of Email, Site to a Unique User account and username; which you can pass on to allauth to perform login.
Assuming you use the Sites framework; your code would look something like this:
from allauth.account.views import LoginView
from django.core.exceptions import ObjectDoesNotExist
class CustomLoginView(LoginView):
def get_user():
email = request.POST.get('email')
current_site = Site.objects.get_current()
try:
user = User.objects.get(email=email, site=current_site)
except ObjectDoesNotExist:
pass # Handle Error: Perhaps redirect to signup
return user
def dispatch(self, request, *args, **kwargs):
user = self.get_user()
request.POST = request.POST.copy()
request.POST['username'] = user.username
return super(CustomLoginView, self).dispatch(request, *args, **kwargs)
Then monkey-patch the LoginView with the custom login view:
allauth.account.views.LoginView = CustomLoginView
Related Reading on setting up a Site FK, and custom auth backends:
How to get unique users across multiple Django sites powered by the "sites" framework?
https://docs.djangoproject.com/en/dev/topics/auth/#writing-an-authentication-backend
I am using built-in login in my app. There is some custom backends or packages to handle this. but many of them are not what i am looking.
i made email unique via django-registration while registering. now all i want is to ask email in login page instead of username.
but if i use some custom backends like django email as username it crashes when using with django-registration.
i dont want to change all authentication backend , i just want to change login page.
in the rest of site , i am gonna use username. p.e in my custom admin page when i write:
welcome {{user}}
it must render username. not e-mail.
i need to find the way out from this. i am stuck.
thank you.
By default django.contrib.auth.urls will create a log in page from this pattern
(r'^login/$', 'django.contrib.auth.views.login'),
You need to avoid/override this url then create a new view to handle a new type of login.
e.g.
create a new login url in your urls.py
(r'^emaillogin/$', 'email_login_view'),
create view to support login with email in views.py
# get default authenticate backend
from django.contrib.auth import authenticate, login
from django.contrib.auth.models import User
# create a function to resolve email to username
def get_user(email):
try:
return User.objects.get(email=email.lower())
except User.DoesNotExist:
return None
# create a view that authenticate user with email
def email_login_view(request):
email = request.POST['email']
password = request.POST['password']
username = get_user(email)
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
# Redirect to a success page.
else:
# Return a 'disabled account' error message
else:
# Return an 'invalid login' error message.
Ref : https://docs.djangoproject.com/en/1.4/topics/auth/#django.contrib.auth.login
The above approach does not work anymore on django 1.9. A different approach might be to override the auth form used in the view as:
class EmailLoginForm(AuthenticationForm):
def clean(self):
try:
self.cleaned_data["username"] = get_user_model().objects.get(email=self.data["username"])
except ObjectDoesNotExist:
self.cleaned_data["username"] = "a_username_that_do_not_exists_anywhere_in_the_site"
return super(EmailLoginForm, self).clean()
Then when defining the login url, define as this:
url(r'^login/$', django.contrib.auth.views.login, name="login", kwargs={"authentication_form": EmailLoginForm}),
url(r'^', include('django.contrib.auth.urls')),
The best thing about the above approach you are not really touching anything in the auth process. It's not really a "clean" solution but it's a quick workaround. As you define the login path before including auth.urls, it will be evaluated instead of the base login form
I'm using the default authentication system with django, but I've added on an OpenID library, where I can authenticate users via OpenID. What I'd like to do is log them in, but it seems using the default django auth system, I need their password to authenticate the user. Is there a way to get around this without actually using their password?
I'd like to do something like this...
user = ... # queried the user based on the OpenID response
user = authenticate(user) # function actually requires a username and password
login(user)
I sooner just leave off the authenticate function, but it attaches a backend field, which is required by login.
It's straightforward to write a custom authentication backend for this. If you create yourapp/auth_backend.py with the following contents:
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
class PasswordlessAuthBackend(ModelBackend):
"""Log in to Django without providing a password.
"""
def authenticate(self, username=None):
try:
return User.objects.get(username=username)
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Then add to your settings.py:
AUTHENTICATION_BACKENDS = (
# ... your other backends
'yourapp.auth_backend.PasswordlessAuthBackend',
)
In your view, you can now call authenticate without a password:
user = authenticate(username=user.username)
login(request, user)
This is a bit of a hack but if you don't want to rewrite a bunch of stuff remove the authenticate
user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)
user would be your User object
In order to do authenticate without password, in your settings.py:
AUTHENTICATION_BACKENDS = [
# auth_backend.py implementing Class YourAuth inside yourapp folder
'yourapp.auth_backend.YourAuth',
# Default authentication of Django
'django.contrib.auth.backends.ModelBackend',
]
In your auth_backend.py:
NOTE: If you have custom model for your app then import from .models CustomUser
from .models import User
from django.conf import settings
# requires to define two functions authenticate and get_user
class YourAuth:
def authenticate(self, request, username=None):
try:
user = User.objects.get(username=username)
return user
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
In your Views for custom login request:
# Your Logic to login user
userName = authenticate(request, username=uid)
login(request, userName)
For further reference, use the django documentation here.
You can easily fix this by creating your own authentication backend and adding it to the AUTHENTICATION_BACKENDS setting.
There are some OpenID backends available already, so with a bit of searching you could save yourself the trouble of writing one.