Django - Iniciar sesión con correo electrónico

84

Quiero que django autentique a los usuarios por correo electrónico, no a través de nombres de usuario. Una forma puede ser proporcionar valor de correo electrónico como valor de nombre de usuario, pero no quiero eso. La razón es que tengo una URL /profile/<username>/, por lo tanto, no puedo tener una URL /profile/[email protected]/.

Otra razón es que todos los correos electrónicos son únicos, pero a veces sucede que el nombre de usuario ya está siendo tomado. Por lo tanto, estoy creando automáticamente el nombre de usuario como fullName_ID.

¿Cómo puedo dejar que Django se autentique con el correo electrónico?

Así es como creo un usuario.

username = `abcd28`
user_email = `[email protected]`
user = User.objects.create_user(username, user_email, user_pass)

Así es como me conecto.

email = request.POST['email']
password = request.POST['password']
username = User.objects.get(email=email.lower()).username
user = authenticate(username=username, password=password)
login(request, user)

¿Hay alguna otra forma de iniciar sesión además de obtener el nombre de usuario primero?

PythonEntusiasta
fuente

Respuestas:

103

Debes escribir un backend de autenticación personalizado. Algo como esto funcionará:

from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend

class EmailBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        UserModel = get_user_model()
        try:
            user = UserModel.objects.get(email=username)
        except UserModel.DoesNotExist:
            return None
        else:
            if user.check_password(password):
                return user
        return None

Luego, configura ese backend como tu backend de autenticación en tu configuración:

AUTHENTICATION_BACKENDS = ['path.to.auth.module.EmailBackend']

Actualizado . Hereda de ModelBackendya que implementa métodos como get_user()ya.

Consulte los documentos aquí: https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#writing-an-authentication-backend

mipadi
fuente
6
Usando django 1.9.8 tengo un error: el objeto 'EmailBackend' no tiene atributo 'get_user'. Resuelto agregando el método 'get_user' de acuerdo con este stackoverflow.com/a/13954358/2647009
baltasvejas
Especifique para qué versión de Django puede funcionar este código. Algunos se quejan de que falta el método get_user.
Dr. Younes Henni
2
En lugar de simplemente if user.check_password(password):, probablemente desee incluir lo que Django hace de forma predeterminada a través de ModelBackend: if user.check_password(password) and self.user_can_authenticate(user):para verificar que el usuario tiene is_active=True.
jmq
1
¿No es esto vulnerable a un ataque de tiempo ya que no incluye la mitigación de Django en el código fuente?
Gabriel García
Ahora, el cuerpo de una solicitud debe contener campos como un nombre de usuario y una contraseña. ¿Hay alguna forma de cambiarlo a un correo electrónico y una contraseña?
Karol
55

Si está iniciando un nuevo proyecto, django le recomendó que configure un modelo de usuario personalizado. (ver https://docs.djangoproject.com/en/dev/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project )

y si lo hizo, agregue tres líneas a su modelo de usuario:

class MyUser(AbstractUser):
    USERNAME_FIELD = 'email'
    email = models.EmailField(_('email address'), unique=True) # changes email to unique and blank to false
    REQUIRED_FIELDS = [] # removes email from REQUIRED_FIELDS

Luego authenticate(email=email, password=password)funciona, mientras authenticate(username=username, password=password)deja de funcionar.

Nicolás
fuente
3
Al ejecutar createuperuser, esto en sí mismo arroja un error: TypeError: create_superuser () falta 1 argumento posicional requerido: 'nombre de usuario'. Debe utilizar el administrador de usuarios personalizado: class MyUserManager(BaseUserManager): def create_superuser(self, email, password, **kwargs): user = self.model(email=email, is_staff=True, is_superuser=True, **kwargs) user.set_password(password) user.save() return user
Michal Holub
17
Instrucciones completas aquí: fomfus.com/articles/…
user2061057
1
Por otra parte, los documentos de Django desaconsejan el uso de un modelo de usuario personalizado si está creando una aplicación reutilizable .
djvg
11

Autenticación de correo electrónico para Django 3.x

Para usar correo electrónico / nombre de usuario y contraseña para la autenticación en lugar del nombre de usuario y contraseña predeterminados, necesitamos anular dos métodos de la clase ModelBackend: authenticate () y get_user ():

El método get_user toma un user_id, que podría ser un nombre de usuario, un ID de base de datos o lo que sea, pero debe ser único para su objeto de usuario , y devuelve un objeto de usuario o Ninguno. Si no ha mantenido el correo electrónico como clave única, tendrá que encargarse de los resultados múltiples devueltos para query_set. En el siguiente código, esto se solucionó devolviendo el primer usuario de la lista devuelta.

from django.contrib.auth.backends import ModelBackend, UserModel
from django.db.models import Q

class EmailBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try: #to allow authentication through phone number or any other field, modify the below statement
            user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))
        except UserModel.DoesNotExist:
            UserModel().set_password(password)
        except MultipleObjectsReturned:
            return User.objects.filter(email=username).order_by('id').first()
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

    def get_user(self, user_id):
        try:
            user = UserModel.objects.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None

        return user if self.user_can_authenticate(user) else None

De forma predeterminada, AUTHENTICATION_BACKENDS está configurado en:

['django.contrib.auth.backends.ModelBackend']

En el archivo settings.py, agregue lo siguiente en la parte inferior para anular el valor predeterminado:

AUTHENTICATION_BACKENDS = ('appname.filename.EmailBackend',)
Himanshu Singh
fuente
10

Tenía un requisito similar en el que el nombre de usuario / correo electrónico debería funcionar para el campo de nombre de usuario. En caso de que alguien esté buscando la forma de autenticación de backend para hacer esto, consulte el siguiente código de trabajo. Puede cambiar el conjunto de consultas si solo desea el correo electrónico.

from django.contrib.auth import get_user_model  # gets the user_model django  default or your own custom
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q


# Class to permit the athentication using email or username
class CustomBackend(ModelBackend):  # requires to define two functions authenticate and get_user

    def authenticate(self, username=None, password=None, **kwargs):
        UserModel = get_user_model()

        try:
            # below line gives query set,you can change the queryset as per your requirement
            user = UserModel.objects.filter(
                Q(username__iexact=username) |
                Q(email__iexact=username)
            ).distinct()

        except UserModel.DoesNotExist:
            return None

        if user.exists():
            ''' get the user object from the underlying query set,
            there will only be one object since username and email
            should be unique fields in your models.'''
            user_obj = user.first()
            if user_obj.check_password(password):
                return user_obj
            return None
        else:
            return None

    def get_user(self, user_id):
        UserModel = get_user_model()
        try:
            return UserModel.objects.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None

También agregue AUTHENTICATION_BACKENDS = ('path.to.CustomBackend',) en settings.py

anuragb26
fuente
Esto funcionó para mí hasta que actualicé de 1.11 a 2.1.5. ¿Alguna idea de por qué no funcionará con esta versión?
zerohedge
@zerohedge agrega una solicitud a los parámetros del método de autenticación. Ver docs.djangoproject.com/en/2.2/topics/auth/customizing/…
Van
También lo deja abierto a un ataque de tiempo, vale la pena intentar imitar de cerca la implementación de Django para evitar tales vulnerabilidades: github.com/django/django/blob/master/django/contrib/auth/…
Gabriel García
Esto también permitirá que los usuarios inactivos se autentiquen.
Gabriel García
5

Django 2.x

Como mencionó Ganesh anteriormente para django 2.x, el método de autenticación ahora requiere un parámetro de solicitud.

# backends.py
from django.contrib.auth import backends, get_user_model
from django.db.models import Q
UserModel = get_user_model()


class ModelBackend(backends.ModelBackend):

    def authenticate(self, request, username=None, password=None, **kwargs):
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        try:
            # user = UserModel._default_manager.get_by_natural_key(username)
            # You can customise what the given username is checked against, here I compare to both username and email fields of the User model
            user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a nonexistent user (#20760).
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user
        return super().authenticate(request, username, password, **kwargs)

agregue su backend a la configuración de su proyecto

# settings.py
AUTHENTICATION_BACKENDS = ['path.to.ModelBackend']

Su modelo de usuario personalizado deberá hacer que los correos electrónicos sean únicos para los usuarios activos y validados. Puede hacerlo simplemente con algo como esto:

from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    objects = UserManager()
    email = models.EmailField(_('email address'), unique=True)

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
        db_table = 'auth_user'
        swappable = 'AUTH_USER_MODEL'

Pero para evitar que alguien bloquee a otra persona para que no use su correo electrónico, debe agregar la validación del correo electrónico y hacer que su proceso de registro e inicio de sesión tenga en cuenta que los correos electrónicos pueden no ser únicos (y probablemente evitar que los nuevos usuarios usen una dirección de correo electrónico existente y validada).

Beto
fuente
4

Autenticación de correo electrónico y nombre de usuario para Django 2.X

Teniendo en cuenta que esta es una pregunta común, aquí hay una implementación personalizada que imita la código fuente de Django pero que autentica al usuario con nombre de usuario o correo electrónico, sin distinción de mayúsculas y minúsculas, manteniendo la protección contra ataques de tiempo y no autenticando a los usuarios inactivos .

from django.contrib.auth.backends import ModelBackend, UserModel
from django.db.models import Q

class CustomBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))
        except UserModel.DoesNotExist:
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

    def get_user(self, user_id):
        try:
            user = UserModel.objects.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None

        return user if self.user_can_authenticate(user) else None

Recuerde siempre agregarlo a su settings.py el correcto backend de autenticación .

Gabriel García
fuente
1
¿Tengo entendido bien que UserModel().set_password(password)existe para evitar que los atacantes determinen si un usuario existe o no al realizar aproximadamente la misma cantidad de trabajo criptográfico independientemente (supongo que este es el ataque de tiempo al que se refería)?
Grand Phuba
1
@GrandPhuba Tienes toda la razón
Gabriel García
2

Debe personalizar la clase ModelBackend. Mi código simple:

from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model

class YourBackend(ModelBackend):

  def authenticate(self, username=None, password=None, **kwargs):
    UserModel = get_user_model()
    if username is None:
        username = kwargs.get(UserModel.USERNAME_FIELD)
    try:
        if '@' in username:
            UserModel.USERNAME_FIELD = 'email'
        else:
            UserModel.USERNAME_FIELD = 'username'

        user = UserModel._default_manager.get_by_natural_key(username)
    except UserModel.DoesNotExist:
        UserModel().set_password(password)
    else:
        if user.check_password(password) and self.user_can_authenticate(user):
            return user

Y en el archivo settings.py , agregue:

AUTHENTICATION_BACKENDS = ['path.to.class.YourBackend']
Hùng Ng Vi
fuente
Actualice su código para incluir el requestparámetro en el authenticatemétodo para django 2.1.1
Ganesh
2

Autenticación con correo electrónico y nombre de usuario para Django 2.x

from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q

class EmailorUsernameModelBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        UserModel = get_user_model()
        try:
            user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))
        except UserModel.DoesNotExist:
            return None
        else:
            if user.check_password(password):
                return user
        return None

En settings.py, agregue la siguiente línea,

AUTHENTICATION_BACKENDS = ['appname.filename.EmailorUsernameModelBackend']
Chetan
fuente
1
from django.contrib.auth.models import User

from django.db import Q

class EmailAuthenticate(object):

    def authenticate(self, username=None, password=None, **kwargs):
        try:
            user = User.objects.get(Q(email=username) | Q(username=username))
        except User.DoesNotExist:
            return None
        except MultipleObjectsReturned:
            return User.objects.filter(email=username).order_by('id').first()

        if 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

Y luego en settings.py:

AUTHENTICATION_BACKENDS = (
  'articles.backends.EmailAuthenticate',
)

donde los artículos es mi django-app, backends.pyes el archivo de Python dentro de mi aplicación y EmailAuthenticatees la clase de backend de autenticación dentro de mi backends.pyarchivo

Animesh Timsina
fuente
1

Bastante simple. No hay necesidad de clases adicionales.

Cuando crea y actualiza un usuario con un correo electrónico, simplemente configure el campo de nombre de usuario con el correo electrónico.

De esa manera, cuando se autentique, el campo de nombre de usuario tendrá el mismo valor que el correo electrónico.

El código:

# Create
User.objects.create_user(username=post_data['email'] etc...)

# Update
user.username = post_data['email']
user.save()

# When you authenticate
user = authenticate(username=post_data['email'], password=password)
Casman Ridder
fuente
1
Agregue un código de muestra para ayudar a demostrar cómo su respuesta puede ayudar a resolver el problema.
Suit Boy Apps
1
mi respuesta es tan simple que debería ser obvia. No me molestaré. No me importa el sistema de puntos en Stackoverflow. Lo publicaré aquí: User.objects.create_user (username = post_data ['email'] etc ...)
Casman Ridder
1
@CasmanRidder Su respuesta será eliminada si no agrega información adicional.
10 Rep
Dios mío, debo actualizar mi publicación, de lo contrario eliminará mi publicación. Debo cambiar ... Debo Debo Debo Debo Debo LOL
Casman Ridder
0

Para Django 2

username = get_object_or_404(User, email=data["email"]).username
        user = authenticate(
            request, 
            username = username, 
            password = data["password"]
        )
        login(request, user)
Juan Sebastián Parada Celis
fuente
0

Autenticación con correo electrónico para Django 2.x

def admin_login(request):
if request.method == "POST":
    email = request.POST.get('email', None)
    password = request.POST.get('password', None)
    try:
        get_user_name = CustomUser.objects.get(email=email)
        user_logged_in =authenticate(username=get_user_name,password=password)
        if user_logged_in is not None:
            login(request, user_logged_in)
            messages.success(request, f"WelcomeBack{user_logged_in.username}")
            return HttpResponseRedirect(reverse('backend'))
        else:
            messages.error(request, 'Invalid Credentials')
            return HttpResponseRedirect(reverse('admin_login'))
    except:
        messages.warning(request, 'Wrong Email')
        return HttpResponseRedirect(reverse('admin_login'))

else:
    if request.user.is_authenticated:
        return HttpResponseRedirect(reverse('backend'))
    return render(request, 'login_panel/login.html')
Shakil Ahmmed
fuente
¿Puede agregar un poco de texto para explicar qué hace su respuesta y cómo ayuda a responder la pregunta?
Jaquez
Editado. Gracias
Shakil Ahmmed
0

Si creó una base de datos personalizada, desde allí, si desea validar su identificación de correo electrónico y contraseña.

  1. Obtenga el ID de correo electrónico y la contraseña con models.objects.value_list('db_columnname').filter(db_emailname=textbox email)

2.asignar en la lista recuperada object_query_list

3.Convertir lista en cadena

Ej .:

  1. Tome el HTML Email_idy los Passwordvalores enViews.py

    u_email = request.POST.get('uemail')

    u_pass = request.POST.get('upass')

  2. Obtener el ID de correo electrónico y la contraseña de la base de datos

    Email = B_Reg.objects.values_list('B_Email',flat=True).filter(B_Email=u_email)

    Password = B_Reg.objects.values_list('Password',flat=True).filter(B_Email=u_email)

  3. Tome los valores de ID de correo electrónico y contraseña en la lista del Queryconjunto de valores

    Email_Value = Email[0]

    Password_Value=Password[0]

  4. Convertir lista en cadena

    string_email = ''.join(map(str, Email_Value))

    string_password = ''.join(map(str, Password_Value))

Finalmente su condición de inicio de sesión

if (string_email==u_email and string_password ==u_pass)
sagar rathod
fuente
0

He creado un ayudante para eso: función authenticate_user(email, password).

from django.contrib.auth.models import User


def authenticate_user(email, password):
    try:
        user = User.objects.get(email=email)
    except User.DoesNotExist:
        return None
    else:
        if user.check_password(password):
            return user

    return None

class LoginView(View):
    template_name = 'myapp/login.html'

    def get(self, request):
        return render(request, self.template_name)

    def post(self, request):
        email = request.POST['email']
        password = request.POST['password']
        user = authenticate_user(email, password)
        context = {}

        if user is not None:
            if user.is_active:
                login(request, user)

                return redirect(self.request.GET.get('next', '/'))
            else:
                context['error_message'] = "user is not active"
        else:
            context['error_message'] = "email or password not correct"

        return render(request, self.template_name, context)
Wariored
fuente
0

Parece que el método para hacer esto se ha actualizado con Django 3.0.

Un método de trabajo para mí ha sido:

authentication.py # <: coloqué esto en una aplicación (no funcionó en la carpeta del proyecto junto con settings.py

from django.contrib.auth import get_user_model
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User

class EmailBackend(BaseBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        UserModel = get_user_model()
        try:
            user = UserModel.objects.get(email=username)
        except UserModel.DoesNotExist:
            return None
        else:
            if user.check_password(password):
                return user
        return None

    def get_user(self, user_id):
        UserModel = get_user_model()
        try:
            return UserModel.objects.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None

Luego agregó esto al archivo settings.py

AUTHENTICATION_BACKENDS = (
    'appname.authentication.EmailBackend',
)
Sr. Mulla
fuente