102 votes

Django - Connexion avec l'email

Je veux que django authentifie les utilisateurs par e-mail, et non par nom d'utilisateur. Une façon de faire est de fournir la valeur de l'email comme valeur du nom d'utilisateur, mais je ne veux pas cela. La raison en est que j'ai une url /profile/<username>/ donc je ne peux pas avoir d'url /profile/abcd@gmail.com/ .

Une autre raison est que tous les emails sont uniques, mais il arrive parfois que le nom d'utilisateur soit déjà pris. C'est pourquoi je crée automatiquement le nom d'utilisateur en tant que fullName_ID .

Comment puis-je changer le fait que Django s'authentifie avec l'email ?

Voici comment je crée un utilisateur.

username = `abcd28`
user_email = `abcd@gmail.com`
user = User.objects.create_user(username, user_email, user_pass)

C'est ainsi que je me connecte.

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)

Existe-t-il un autre moyen de se connecter, à part obtenir le nom d'utilisateur en premier ?

121voto

mipadi Points 135410

Vous devez écrire un backend d'authentification personnalisé. Quelque chose comme ceci fonctionnera :

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

Ensuite, définissez ce backend comme votre backend d'authentification dans vos paramètres :

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

Mise à jour de . hérité de ModelBackend car il met en œuvre des méthodes comme get_user() déjà.

Voir les documents ici : https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#writing-an-authentication-backend

6 votes

Avec django 1.9.8, j'ai une erreur : l'objet 'EmailBackend' n'a pas d'attribut 'get_user'. Résolu en ajoutant la méthode 'get_user' comme suit stackoverflow.com/a/13954358/2647009

1 votes

Veuillez préciser pour quelle version de Django ce code peut fonctionner. Certains se plaignent de l'absence de la méthode get_user.

3 votes

Au lieu de simplement if user.check_password(password): vous voulez probablement inclure ce que Django fait par défaut via ModelBackend : if user.check_password(password) and self.user_can_authenticate(user): afin de vérifier que l'utilisateur a is_active=True .

66voto

Nicholas Points 133

Si vous démarrez un nouveau projet, django vous recommande vivement de mettre en place un modèle d'utilisateur personnalisé. (voir https://docs.djangoproject.com/en/dev/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project )

et si vous l'avez fait, ajoutez trois lignes à votre modèle d'utilisateur :

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

Puis authenticate(email=email, password=password) fonctionne, tandis que authenticate(username=username, password=password) ne fonctionne plus.

4 votes

Lors de l'exécution de createsuperuser, une erreur est générée : TypeError : create_superuser() missing 1 required positional argument : 'username'. Vous devez utiliser un gestionnaire d'utilisateurs personnalisé : 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

20 votes

Instructions complètes ici : fomfus.com/articles/

2 votes

Mais encore une fois, la documentation de Django à déconseiller en utilisant un modèle d'utilisateur personnalisé si vous créez une réutilisable app.

21voto

Himanshu Singh Points 515

Authentification par courriel pour Django 3.x

Pour utiliser l'email/le nom d'utilisateur et le mot de passe pour l'authentification au lieu de l'authentification par défaut par nom d'utilisateur et mot de passe, nous devons surcharger deux méthodes de la classe ModelBackend : authenticate() et get_user() :

La méthode get_user prend un user_id - qui peut être un nom d'utilisateur, un ID de base de données ou autre, mais doit être unique à votre objet utilisateur - et renvoie un objet utilisateur ou None. Si vous n'avez pas gardé l'email comme clé unique, vous devrez vous occuper des multiples résultats retournés pour le query_set. Dans le code ci-dessous, cela a été pris en charge en retournant le premier utilisateur de la liste retournée.

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

Par défaut, AUTHENTICATION_BACKENDS est défini sur :

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

Dans le fichier settings.py, ajoutez ce qui suit en bas de page pour remplacer la valeur par défaut :

AUTHENTICATION_BACKENDS = ('appname.filename.EmailBackend',)

0 votes

C'est génial, merci. J'ai pratiquement terminé un projet, les modèles, les formulaires, les vues, tout ça, alors recommencer n'est pas très attrayant ! Maintenant que je peux m'authentifier sur l'adresse e-mail, y a-t-il un moyen de supprimer le champ du nom d'utilisateur pour qu'il ne soit pas inclus dans l'authentification et les formulaires rendus dans les modèles ?

2 votes

Pourquoi utiliser ceci au lieu de USERNAME_FIELD variable dans le modèle ?

1 votes

Afin que vous puissiez vous connecter à la fois avec une adresse électronique et un nom d'utilisateur.

12voto

anuragb26 Points 49

J'ai eu une exigence similaire où le nom d'utilisateur et l'email doivent fonctionner pour le champ du nom d'utilisateur. Au cas où quelqu'un cherche le moyen d'authentification en backend pour faire cela, vérifiez le code suivant. Vous pouvez changer le queryset si vous souhaitez seulement l'email.

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

Ajoutez également AUTHENTICATION_BACKENDS = ('path.to.CustomBackend', ) dans settings.py.

0 votes

Cela fonctionnait pour moi jusqu'à ce que je passe de la 1.11 à la 2.1.5. Une idée de la raison pour laquelle cela ne fonctionne pas avec cette version ?

0 votes

@zerohedge ajoute la demande aux paramètres de la méthode d'authentification. Voir docs.djangoproject.com/fr/2.2/topics/auth/customizing/

0 votes

Cela vous laisse également ouvert à une attaque de synchronisation, il vaut la peine d'essayer d'imiter de près l'implémentation de Django pour éviter de telles vulnérabilités : github.com/django/django/blob/master/django/contrib/auth/

6voto

bob Points 305

Django 2.x

Comme mentionné par Ganesh ci-dessus, pour django 2.x, la méthode authenticate requiert maintenant un paramètre de requête.

# 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)

ajoutez votre backend aux paramètres de votre projet

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

Votre modèle d'utilisateur personnalisé devra faire en sorte que les courriels soient uniques pour les utilisateurs actifs et les utilisateurs validés :

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'

Mais pour éviter que quelqu'un ne bloque l'utilisation de son adresse électronique par une autre personne, vous devriez plutôt ajouter la validation de l'adresse électronique et faire en sorte que votre processus d'enregistrement et de connexion tienne compte du fait que les adresses électroniques peuvent ne pas être uniques (et probablement empêcher les nouveaux utilisateurs d'utiliser une adresse électronique existante et validée).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X