حول المحتوى:
شرح كيفية إرسال روابط تفعيل الحساب، تشفير التوكن، تحديد مدة الصلاحية، وإدارة إعادة التفعيل.
نظام Email Verification Django FastAPI هو خطوة أساسية في أي تطبيق يعتمد على حسابات المستخدمين. تفعيل البريد الإلكتروني يحميك من الحسابات الوهمية (Fake Accounts)، يقلل من الرسائل غير المرغوب فيها (Spam)، ويوفر طبقة أمان إضافية قبل السماح بالوصول الكامل للمزايا الحساسة في التطبيق.
في هذا المقال على افهم صح سنشرح خطوة بخطوة كيفية بناء نظام تحقق من البريد الإلكتروني في كل من Django وFastAPI، مع التركيز على:
إذا كنت مهتماً ببناء نظام تسجيل مستخدمين متكامل مع واجهة أمامية، يمكنك أيضاً الاطلاع على: كيفية بناء نظام تسجيل مستخدمين باستخدام Django REST و Next.js.
الفكرة الأساسية بسيطة:
نفس المنطق يمكن تطبيقه في Django وFastAPI مع اختلاف الأدوات وطريقة التنفيذ.
EMAIL_BACKEND وإعداد SMTP أو خدمة إرسال بريد مثل SendGrid أو Mailgun.django.contrib.auth.models.User أو Custom User Model.PyJWT أو python-jose.من المفيد أيضاً الاعتماد على Background Tasks لإرسال البريد في الخلفية وعدم تعطيل الاستجابة للمستخدم. يمكنك الرجوع إلى: التعامل مع Background Tasks في Django وFastAPI.
غالباً سنستخدم حقل is_active لتحديد ما إذا كان حساب المستخدم مفعل أم لا:
# models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
email_verified = models.BooleanField(default=False)
يمكنك استخدام email_verified أو الاعتماد على is_active فقط، حسب احتياجك. في كثير من السيناريوهات:
أبسط طريقة آمنة في Django هي استخدام الـ Signing من django.core.signing، الذي يقوم بالتشفير والتوقيع باستخدام SECRET_KEY:
# tokens.py
from django.core import signing
from django.conf import settings
from datetime import timedelta
EXPIRATION_HOURS = 24
def generate_verification_token(user_id: int) -> str:
data = {"user_id": user_id}
token = signing.dumps(
data,
salt="email-verification",
)
return token
def verify_token(token: str):
try:
data = signing.loads(
token,
salt="email-verification",
max_age=EXPIRATION_HOURS * 3600, # ثواني
)
return data["user_id"]
except signing.BadSignature:
return None
except signing.SignatureExpired:
return None
بهذا نكون قد حققنا:
SECRET_KEY.بعد إنشاء المستخدم في View التسجيل (أو عبر Django REST Framework)، نولّد التوكن، ونبني رابط التفعيل، ثم نرسل البريد:
# views.py
from django.contrib.auth import get_user_model
from django.core.mail import send_mail
from django.shortcuts import render, redirect
from django.urls import reverse
from django.conf import settings
from .tokens import generate_verification_token
User = get_user_model()
def register(request):
if request.method == "POST":
email = request.POST["email"]
password = request.POST["password"]
user = User.objects.create_user(
username=email,
email=email,
password=password,
is_active=False
)
token = generate_verification_token(user.id)
activation_link = request.build_absolute_uri(
reverse("activate-account") + f"?token={token}"
)
subject = "تفعيل حسابك"
message = f"اضغط على الرابط التالي لتفعيل حسابك:\n{activation_link}"
send_mail(
subject,
message,
settings.DEFAULT_FROM_EMAIL,
[user.email],
fail_silently=False,
)
return render(request, "registration/verify_email_sent.html")
return render(request, "registration/register.html")
هنا استخدمنا:
request.build_absolute_uri لبناء رابط كامل مع الدومين.reverse("activate-account") للحصول على URL التفعيل.
# views.py (إضافة)
from django.http import HttpResponseBadRequest, HttpResponse
from .tokens import verify_token
def activate_account(request):
token = request.GET.get("token")
if not token:
return HttpResponseBadRequest("توكن مفقود")
user_id = verify_token(token)
if not user_id:
return render(request, "registration/activation_invalid.html")
try:
user = User.objects.get(id=user_id)
except User.DoesNotExist:
return render(request, "registration/activation_invalid.html")
if not user.is_active:
user.is_active = True
user.email_verified = True
user.save()
return render(request, "registration/activation_success.html")
يمكنك تخصيص الصفحات activation_invalid.html وactivation_success.html بما يناسب واجهتك.
في حالة انتهاء صلاحية التوكن، أو فقدان الرسالة، يمكن إضافة View لإعادة إرسال رابط التفعيل. من المهم وضع حد زمني وعدد مرات محددة لتفادي الإساءة:
# views.py (إعادة إرسال)
from django.utils import timezone
from datetime import timedelta
def resend_activation_email(request):
if request.method == "POST":
email = request.POST["email"]
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
return render(request, "registration/resend_failed.html")
if user.is_active:
return render(request, "registration/already_active.html")
token = generate_verification_token(user.id)
activation_link = request.build_absolute_uri(
reverse("activate-account") + f"?token={token}"
)
send_mail(
"إعادة إرسال رابط تفعيل حسابك",
f"اضغط على الرابط التالي:\n{activation_link}",
settings.DEFAULT_FROM_EMAIL,
[user.email],
)
return render(request, "registration/verify_email_sent.html")
return render(request, "registration/resend_activation.html")
يمكنك هنا استخدام Redis أو قاعدة البيانات لتخزين آخر وقت إرسال، ومنع الإرسال المتكرر خلال ثوانٍ قليلة. لمزيد عن تخفيف الحمل على قاعدة البيانات، راجع: Redis كمخزن مؤقت للتطبيقات باستخدام Django و FastAPI.
# models.py
from sqlalchemy import Column, Integer, String, Boolean
from database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True, nullable=False)
hashed_password = Column(String, nullable=False)
is_active = Column(Boolean, default=False)
email_verified = Column(Boolean, default=False)
في FastAPI غالباً نستخدم JWT. يمكن أن نستخدم python-jose لتوليد التوكن:
# security.py
from datetime import datetime, timedelta
from jose import jwt
SECRET_KEY = "CHANGE_ME_TO_A_STRONG_SECRET"
ALGORITHM = "HS256"
EMAIL_TOKEN_EXPIRE_HOURS = 24
def create_email_token(user_id: int) -> str:
expire = datetime.utcnow() + timedelta(hours=EMAIL_TOKEN_EXPIRE_HOURS)
to_encode = {"sub": str(user_id), "exp": expire, "scope": "email_verification"}
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def verify_email_token(token: str):
from jose import JWTError
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
if payload.get("scope") != "email_verification":
return None
user_id = int(payload.get("sub"))
return user_id
except JWTError:
return None
إضافة scope تساعد على التفريق بين توكنات التفعيل وتوكنات الدخول العادية (Auth Tokens).
يمكن استخدام أي مكتبة SMTP (مثل aiosmtplib أو smtplib). مثال بسيط متزامن (Synchronous) مع Task في الخلفية:
# email_utils.py
import smtplib
from email.message import EmailMessage
SMTP_HOST = "smtp.example.com"
SMTP_PORT = 587
SMTP_USER = "[email protected]"
SMTP_PASSWORD = "secret"
def send_verification_email(to_email: str, activation_link: str):
msg = EmailMessage()
msg["Subject"] = "تفعيل حسابك"
msg["From"] = SMTP_USER
msg["To"] = to_email
msg.set_content(f"اضغط على الرابط التالي لتفعيل حسابك:\n{activation_link}")
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
server.starttls()
server.login(SMTP_USER, SMTP_PASSWORD)
server.send_message(msg)
# main.py
from fastapi import FastAPI, Depends, BackgroundTasks, Request, HTTPException
from sqlalchemy.orm import Session
from database import SessionLocal
from models import User
from security import create_email_token, verify_email_token
from email_utils import send_verification_email
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/register")
def register_user(email: str, password: str, background_tasks: BackgroundTasks, request: Request, db: Session = Depends(get_db)):
# التحقق من عدم وجود المستخدم
user = db.query(User).filter(User.email == email).first()
if user:
raise HTTPException(status_code=400, detail="البريد مستخدم بالفعل")
new_user = User(
email=email,
hashed_password=password, # في الواقع يجب تخزينه Hashed
is_active=False,
email_verified=False,
)
db.add(new_user)
db.commit()
db.refresh(new_user)
token = create_email_token(new_user.id)
activation_link = str(request.url_for("activate_email")) + f"?token={token}"
background_tasks.add_task(send_verification_email, new_user.email, activation_link)
return {"message": "تم إنشاء الحساب، تفقد بريدك الإلكتروني لتفعيل الحساب."}
هنا استخدمنا BackgroundTasks لإرسال البريد دون حجب استجابة API.
@app.get("/activate", name="activate_email")
def activate_email(token: str, db: Session = Depends(get_db)):
user_id = verify_email_token(token)
if not user_id:
raise HTTPException(status_code=400, detail="رابط التفعيل غير صالح أو منتهي")
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="المستخدم غير موجود")
if not user.is_active:
user.is_active = True
user.email_verified = True
db.add(user)
db.commit()
return {"message": "تم تفعيل بريدك الإلكتروني وحسابك بنجاح."}
@app.post("/resend-activation")
def resend_activation(email: str, background_tasks: BackgroundTasks, request: Request, db: Session = Depends(get_db)):
user = db.query(User).filter(User.email == email).first()
if not user:
raise HTTPException(status_code=404, detail="لا يوجد مستخدم بهذا البريد")
if user.is_active:
return {"message": "الحساب مفعل بالفعل"}
token = create_email_token(user.id)
activation_link = str(request.url_for("activate_email")) + f"?token={token}"
background_tasks.add_task(send_verification_email, user.email, activation_link)
return {"message": "تم إعادة إرسال رابط التفعيل إلى بريدك."}
يمكنك ربط هذا الـ Endpoint بواجهة Frontend بسيطة تطلب من المستخدم إدخال بريده ثم تظهر له النتيجة.
نظام التفعيل بالبريد لا يعمل بمعزل عن بقية أجزاء التطبيق. غالباً ستحتاج إلى دمجه مع:
بناء نظام Email Verification Django FastAPI ليس معقداً، لكنه ضروري لأي تطبيق جاد يعتمد على حسابات المستخدمين. الأفكار الأساسية مشتركة بين Django وFastAPI:
بتطبيق الخطوات والأكواد التي استعرضناها في هذا المقال، يمكنك بناء نظام تحقق بريد إلكتروني مرن، آمن، وقابل للتطوير في كل من Django وFastAPI، وضمان أن المستخدمين الذين يصلون لنظامك هم مستخدمون حقيقيون ببريد إلكتروني فعّال.
شرح كيفية إرسال روابط تفعيل الحساب، تشفير التوكن، تحديد مدة الصلاحية، وإدارة إعادة التفعيل.
مساحة اعلانية