Pour implémenter un système de vérification de numéro de téléphone avec Django REST Framework (DRF), vous pouvez suivre les étapes suivantes. Ce système permettra aux utilisateurs de fournir leur numéro de téléphone, de recevoir un code de vérification par SMS (par exemple via Twilio), et de valider ce code pour vérifier leur numéro.
Étapes Principales :
- Installer les dépendances nécessaires
- Modifier le modèle utilisateur pour inclure le numéro de téléphone
- Créer un modèle pour stocker les codes de vérification
- Configurer un service d'envoi de SMS (ex. Twilio)
- Créer des sérialiseurs DRF
- Créer des vues et des routes API
- Gérer la logique de vérification et la sécurité
1. Installer les Dépendances Nécessaires
Tout d'abord, assurez-vous d'avoir installé les bibliothèques nécessaires :
- Django REST Framework : Si ce n'est pas déjà fait.
- Twilio : Pour l'envoi de SMS.
- django-phonenumber-field : Pour la validation et le formatage des numéros de téléphone.
Installez-les via pip :
pip install djangorestframework twilio django-phonenumber-field
Ajoutez phonenumber_field
et rest_framework
à votre INSTALLED_APPS
dans settings.py
:
# settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
'phonenumber_field',
# ...
]
2. Modifier le Modèle Utilisateur pour Inclure le Numéro de Téléphone
Si vous utilisez un modèle utilisateur personnalisé, ajoutez un champ pour le numéro de téléphone et un indicateur de vérification.
# models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.db import models
from phonenumber_field.modelfields import PhoneNumberField
class UserManager(BaseUserManager):
def create_user(self, email, username, phone_number, password=None):
if not email:
raise ValueError('Les utilisateurs doivent avoir une adresse email')
if not phone_number:
raise ValueError('Les utilisateurs doivent avoir un numéro de téléphone')
user = self.model(
email=self.normalize_email(email),
username=username,
phone_number=phone_number,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, username, phone_number, password=None):
user = self.create_user(
email,
username,
phone_number,
password=password,
)
user.is_admin = True
user.save(using=self._db)
return user
class CustomUser(AbstractBaseUser):
email = models.EmailField(verbose_name='adresse email', max_length=255, unique=True)
username = models.CharField(max_length=50, unique=True)
phone_number = PhoneNumberField(unique=True, null=False, blank=False)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_phone_verified = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'phone_number']
def __str__(self):
return self.email
@property
def is_staff(self):
return self.is_admin
Remarque : Si vous avez déjà un modèle utilisateur, assurez-vous d'ajouter le champ phone_number
et is_phone_verified
de manière appropriée.
3. Créer un Modèle pour Stocker les Codes de Vérification
Ce modèle stockera les codes de vérification envoyés aux utilisateurs.
# models.py
import random
import string
from django.utils import timezone
from datetime import timedelta
class PhoneVerification(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='phone_verifications')
code = models.CharField(max_length=6)
created_at = models.DateTimeField(auto_now_add=True)
is_verified = models.BooleanField(default=False)
def is_expired(self):
return self.created_at < timezone.now() - timedelta(minutes=10) # Expire après 10 minutes
def __str__(self):
return f"Vérification de {self.user.email} - {'Validé' if self.is_verified else 'En attente'}"
4. Configurer un Service d'Envoi de SMS (Ex. Twilio)
Vous pouvez utiliser Twilio pour envoyer des SMS. Commencez par créer un compte Twilio et obtenir les identifiants nécessaires (ACCOUNT_SID
, AUTH_TOKEN
, FROM_NUMBER
).
Ajoutez ces configurations à votre settings.py
:
# settings.py
TWILIO_ACCOUNT_SID = 'votre_account_sid'
TWILIO_AUTH_TOKEN = 'votre_auth_token'
TWILIO_FROM_NUMBER = '+1234567890' # Numéro Twilio
Créez un fichier utils.py
pour gérer l'envoi de SMS :
# utils.py
from django.conf import settings
from twilio.rest import Client
def send_sms(to, message):
client = Client(settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN)
message = client.messages.create(
body=message,
from_=settings.TWILIO_FROM_NUMBER,
to=str(to)
)
return message.sid
5. Créer des Sérialiseurs DRF
Créez des sérialiseurs pour gérer la demande de vérification et la validation du code.
# serializers.py
from rest_framework import serializers
from .models import CustomUser, PhoneVerification
from phonenumber_field.serializerfields import PhoneNumberField
class PhoneVerificationRequestSerializer(serializers.Serializer):
phone_number = PhoneNumberField()
def validate_phone_number(self, value):
if CustomUser.objects.filter(phone_number=value).exists():
raise serializers.ValidationError("Ce numéro de téléphone est déjà utilisé.")
return value
class PhoneVerificationCodeSerializer(serializers.Serializer):
phone_number = PhoneNumberField()
code = serializers.CharField(max_length=6)
def validate(self, data):
phone_number = data.get('phone_number')
code = data.get('code')
try:
user = CustomUser.objects.get(phone_number=phone_number)
except CustomUser.DoesNotExist:
raise serializers.ValidationError("Utilisateur non trouvé avec ce numéro de téléphone.")
try:
verification = PhoneVerification.objects.filter(user=user, code=code, is_verified=False).latest('created_at')
except PhoneVerification.DoesNotExist:
raise serializers.ValidationError("Code de vérification invalide.")
if verification.is_expired():
raise serializers.ValidationError("Le code de vérification a expiré.")
data['user'] = user
data['verification'] = verification
return data
6. Créer des Vues et des Routes API
Créez des vues pour gérer les demandes de vérification et la validation des codes.
# views.py
from rest_framework import generics, status
from rest_framework.response import Response
from .serializers import PhoneVerificationRequestSerializer, PhoneVerificationCodeSerializer
from .models import CustomUser, PhoneVerification
from .utils import send_sms
import random
import string
from django.utils import timezone
from rest_framework.permissions import AllowAny
class PhoneVerificationRequestView(generics.GenericAPIView):
serializer_class = PhoneVerificationRequestSerializer
permission_classes = [AllowAny]
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
phone_number = serializer.validated_data['phone_number']
# Générer un code de 6 chiffres
code = ''.join(random.choices(string.digits, k=6))
try:
user = CustomUser.objects.get(phone_number=phone_number)
# Si l'utilisateur existe déjà, ne pas permettre la création d'un nouveau
return Response({"detail": "Ce numéro de téléphone est déjà associé à un utilisateur."}, status=status.HTTP_400_BAD_REQUEST)
except CustomUser.DoesNotExist:
pass # Permettre la création si nécessaire
# Créer une instance de PhoneVerification
verification = PhoneVerification.objects.create(user=None, code=code) # user=None pour l'instant
# Envoyer le code par SMS
try:
send_sms(phone_number, f"Votre code de vérification est : {code}")
except Exception as e:
return Response({"detail": "Erreur lors de l'envoi du SMS."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response({"detail": "Code de vérification envoyé."}, status=status.HTTP_200_OK)
class PhoneVerificationCodeView(generics.GenericAPIView):
serializer_class = PhoneVerificationCodeSerializer
permission_classes = [AllowAny]
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
verification = serializer.validated_data['verification']
# Marquer la vérification comme validée
verification.is_verified = True
verification.save()
# Mettre à jour l'utilisateur
user.is_phone_verified = True
user.save()
return Response({"detail": "Numéro de téléphone vérifié avec succès."}, status=status.HTTP_200_OK)
Remarque : Vous pourriez ajuster ces vues selon vos besoins, notamment si vous souhaitez créer un utilisateur lors de la vérification ou gérer différemment les utilisateurs existants.
7. Configurer les Routes API
Ajoutez les routes correspondantes dans votre urls.py
.
# urls.py
from django.urls import path
from .views import PhoneVerificationRequestView, PhoneVerificationCodeView
urlpatterns = [
path('api/verify-phone/request/', PhoneVerificationRequestView.as_view(), name='phone-verification-request'),
path('api/verify-phone/verify/', PhoneVerificationCodeView.as_view(), name='phone-verification-verify'),
]
8. Ajouter des Logiques Supplémentaires (Optionnel)
a. Génération de Code Unique par Utilisateur
Modifiez la vue de demande pour associer le code à un utilisateur spécifique ou créer un nouvel utilisateur.
b. Limiter le Nombre de Demandes
Pour éviter les abus, limitez le nombre de demandes de vérification par utilisateur ou par numéro de téléphone.
# views.py (ajout dans PhoneVerificationRequestView)
from django.utils import timezone
from datetime import timedelta
class PhoneVerificationRequestView(generics.GenericAPIView):
# ... code existant ...
def post(self, request, *args, **kwargs):
# ... code existant ...
# Vérifier le nombre de demandes récentes
recent_verifications = PhoneVerification.objects.filter(
phone_number=phone_number,
created_at__gte=timezone.now() - timedelta(minutes=15)
)
if recent_verifications.count() >= 5:
return Response({"detail": "Nombre maximum de demandes atteint. Réessayez plus tard."}, status=status.HTTP_429_TOO_MANY_REQUESTS)
# ... code existant ...
c. Gestion des Utilisateurs lors de la Vérification
Vous pouvez décider de créer un utilisateur après la vérification ou associer le numéro à un utilisateur existant.
9. Tests et Validation
Assurez-vous de tester votre système en environnement de développement avant de le déployer en production. Vérifiez que :
- Les SMS sont correctement envoyés.
- Les codes sont générés et stockés de manière sécurisée.
- Les vérifications expirent après le délai défini.
- Les erreurs sont correctement gérées et communiquées à l'utilisateur.
Exemple Complet de Implémentation
Pour vous donner une vue d'ensemble, voici un exemple complet des fichiers concernés.
models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.db import models
from phonenumber_field.modelfields import PhoneNumberField
from django.utils import timezone
from datetime import timedelta
class UserManager(BaseUserManager):
def create_user(self, email, username, phone_number, password=None):
if not email:
raise ValueError('Les utilisateurs doivent avoir une adresse email')
if not phone_number:
raise ValueError('Les utilisateurs doivent avoir un numéro de téléphone')
user = self.model(
email=self.normalize_email(email),
username=username,
phone_number=phone_number,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, username, phone_number, password=None):
user = self.create_user(
email,
username,
phone_number,
password=password,
)
user.is_admin = True
user.save(using=self._db)
return user
class CustomUser(AbstractBaseUser):
email = models.EmailField(verbose_name='adresse email', max_length=255, unique=True)
username = models.CharField(max_length=50, unique=True)
phone_number = PhoneNumberField(unique=True, null=False, blank=False)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_phone_verified = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'phone_number']
def __str__(self):
return self.email
@property
def is_staff(self):
return self.is_admin
class PhoneVerification(models.Model):
phone_number = PhoneNumberField()
code = models.CharField(max_length=6)
created_at = models.DateTimeField(auto_now_add=True)
is_verified = models.BooleanField(default=False)
def is_expired(self):
return self.created_at < timezone.now() - timedelta(minutes=10)
def __str__(self):
return f"Vérification de {self.phone_number} - {'Validé' if self.is_verified else 'En attente'}"
serializers.py
from rest_framework import serializers
from .models import CustomUser, PhoneVerification
from phonenumber_field.serializerfields import PhoneNumberField
class PhoneVerificationRequestSerializer(serializers.Serializer):
phone_number = PhoneNumberField()
def validate_phone_number(self, value):
if CustomUser.objects.filter(phone_number=value).exists():
raise serializers.ValidationError("Ce numéro de téléphone est déjà utilisé.")
return value
class PhoneVerificationCodeSerializer(serializers.Serializer):
phone_number = PhoneNumberField()
code = serializers.CharField(max_length=6)
def validate(self, data):
phone_number = data.get('phone_number')
code = data.get('code')
try:
verification = PhoneVerification.objects.filter(phone_number=phone_number, code=code, is_verified=False).latest('created_at')
except PhoneVerification.DoesNotExist:
raise serializers.ValidationError("Code de vérification invalide.")
if verification.is_expired():
raise serializers.ValidationError("Le code de vérification a expiré.")
data['verification'] = verification
return data
views.py
from rest_framework import generics, status
from rest_framework.response import Response
from .serializers import PhoneVerificationRequestSerializer, PhoneVerificationCodeSerializer
from .models import CustomUser, PhoneVerification
from .utils import send_sms
import random
import string
from django.utils import timezone
from rest_framework.permissions import AllowAny
from datetime import timedelta
class PhoneVerificationRequestView(generics.GenericAPIView):
serializer_class = PhoneVerificationRequestSerializer
permission_classes = [AllowAny]
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
phone_number = serializer.validated_data['phone_number']
# Vérifier le nombre de demandes récentes
recent_verifications = PhoneVerification.objects.filter(
phone_number=phone_number,
created_at__gte=timezone.now() - timedelta(minutes=15)
)
if recent_verifications.count() >= 5:
return Response({"detail": "Nombre maximum de demandes atteint. Réessayez plus tard."}, status=status.HTTP_429_TOO_MANY_REQUESTS)
# Générer un code de 6 chiffres
code = ''.join(random.choices(string.digits, k=6))
# Créer une instance de PhoneVerification
PhoneVerification.objects.create(phone_number=phone_number, code=code)
# Envoyer le code par SMS
try:
send_sms(phone_number, f"Votre code de vérification est : {code}")
except Exception as e:
return Response({"detail": "Erreur lors de l'envoi du SMS."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response({"detail": "Code de vérification envoyé."}, status=status.HTTP_200_OK)
class PhoneVerificationCodeView(generics.GenericAPIView):
serializer_class = PhoneVerificationCodeSerializer
permission_classes = [AllowAny]
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
verification = serializer.validated_data['verification']
# Marquer la vérification comme validée
verification.is_verified = True
verification.save()
# Mettre à jour ou créer l'utilisateur
user, created = CustomUser.objects.get_or_create(
phone_number=verification.phone_number,
defaults={
'email': f"user_{verification.phone_number}@example.com",
'username': f"user_{verification.phone_number}",
'is_phone_verified': True
}
)
if not created:
user.is_phone_verified = True
user.save()
return Response({"detail": "Numéro de téléphone vérifié avec succès."}, status=status.HTTP_200_OK)
urls.py
from django.urls import path
from .views import PhoneVerificationRequestView, PhoneVerificationCodeView
urlpatterns = [
path('api/verify-phone/request/', PhoneVerificationRequestView.as_view(), name='phone-verification-request'),
path('api/verify-phone/verify/', PhoneVerificationCodeView.as_view(), name='phone-verification-verify'),
]
utils.py
from django.conf import settings
from twilio.rest import Client
def send_sms(to, message):
client = Client(settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN)
message = client.messages.create(
body=message,
from_=settings.TWILIO_FROM_NUMBER,
to=str(to)
)
return message.sid
10. Sécuriser et Optimiser
Limiter les Tentatives de Vérification : Implémentez un système pour limiter le nombre de tentatives de vérification afin d'éviter les attaques par force brute.
Chiffrer les Codes : Pour plus de sécurité, vous pouvez chiffrer les codes de vérification dans la base de données.
Utiliser des Tâches Asynchrones : Pour améliorer les performances, utilisez des tâches asynchrones (par exemple avec Celery) pour envoyer les SMS sans bloquer les requêtes API.
Configurer HTTPS : Assurez-vous que votre API est accessible via HTTPS pour sécuriser les communications.
Top comments (0)