كيفية بناء نظام Email Verification في Django وFastAPI

كيفية بناء نظام Email Verification في Django وFastAPI (دليل عملي كامل)

نظام Email Verification Django FastAPI هو خطوة أساسية في أي تطبيق يعتمد على حسابات المستخدمين. تفعيل البريد الإلكتروني يحميك من الحسابات الوهمية (Fake Accounts)، يقلل من الرسائل غير المرغوب فيها (Spam)، ويوفر طبقة أمان إضافية قبل السماح بالوصول الكامل للمزايا الحساسة في التطبيق.

في هذا المقال على افهم صح سنشرح خطوة بخطوة كيفية بناء نظام تحقق من البريد الإلكتروني في كل من Django وFastAPI، مع التركيز على:

  • فكرة عمل نظام التفعيل عبر البريد الإلكتروني.
  • توليد توكن آمن مشفّر.
  • تحديد مدة صلاحية للتوكن (Expiration Time).
  • إرسال رابط التفعيل عبر البريد.
  • إدارة إعادة إرسال رابط التفعيل.
  • أفضل الممارسات الأمنية والتقنية.

إذا كنت مهتماً ببناء نظام تسجيل مستخدمين متكامل مع واجهة أمامية، يمكنك أيضاً الاطلاع على: كيفية بناء نظام تسجيل مستخدمين باستخدام Django REST و Next.js.

1. الفكرة العامة لنظام Email Verification

الفكرة الأساسية بسيطة:

  1. المستخدم يسجل بحسابه (بريد إلكتروني + كلمة مرور + بيانات أخرى).
  2. نقوم بإنشاء توكن Verification Token يحتوي على معلومات المستخدم (أو معرفه) مع تاريخ صلاحية.
  3. نرسل بريد إلكتروني للمستخدم يحتوي على رابط تفعيل فيه هذا التوكن كـ Query Parameter.
  4. عند فتح الرابط، يتصل المتصفح بواجهة Back-end، نتحقق من التوكن:
    • إن كان سليم وغير منتهي الصلاحية → نفعّل الحساب.
    • إن كان منتهي أو غير صالح → نرفض الطلب ونعرض رسالة مناسبة (مع خيار إعادة إرسال رابط التفعيل).

نفس المنطق يمكن تطبيقه في Django وFastAPI مع اختلاف الأدوات وطريقة التنفيذ.

2. المتطلبات الأساسية

Django

  • مشروع Django معد مسبقاً.
  • تفعيل EMAIL_BACKEND وإعداد SMTP أو خدمة إرسال بريد مثل SendGrid أو Mailgun.
  • نظام مستخدمين: إما django.contrib.auth.models.User أو Custom User Model.
  • مكتبة لتوليد التوكن (يمكن استخدام أدوات Django المدمجة أو JWT أو itsdangerous).

FastAPI

  • مشروع FastAPI مع Uvicorn.
  • إعداد ارسال بريد عبر SMTP أو خدمة خارجية.
  • نموذج مستخدم في قاعدة البيانات عبر SQLAlchemy أو غيره.
  • مكتبة JWT مثل PyJWT أو python-jose.

من المفيد أيضاً الاعتماد على Background Tasks لإرسال البريد في الخلفية وعدم تعطيل الاستجابة للمستخدم. يمكنك الرجوع إلى: التعامل مع Background Tasks في Django وFastAPI.

3. بناء Email Verification في Django

3.1. نموذج المستخدم وحقل is_active

غالباً سنستخدم حقل 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 فقط، حسب احتياجك. في كثير من السيناريوهات:

  • is_active = False حتى يتم تفعيل البريد.
  • بعد التفعيل → is_active = True و/أو email_verified = True.

3.2. إنشاء توكن التفعيل في Django

أبسط طريقة آمنة في 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.
  • مدة صلاحية ثابتة (24 ساعة هنا كمثال).

3.3. إرسال رابط التفعيل بعد التسجيل

بعد إنشاء المستخدم في 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 التفعيل.

3.4. View تفعيل الحساب


# 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 بما يناسب واجهتك.

3.5. إعادة إرسال رابط التفعيل

في حالة انتهاء صلاحية التوكن، أو فقدان الرسالة، يمكن إضافة 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.

4. بناء Email Verification في FastAPI

4.1. نموذج المستخدم (SQLAlchemy مثالاً)


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

4.2. توليد توكن JWT مع مدة صلاحية

في 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).

4.3. إرسال بريد التفعيل

يمكن استخدام أي مكتبة 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)

4.4. Endpoint التسجيل وإرسال رابط التفعيل


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

4.5. Endpoint تفعيل البريد


@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": "تم تفعيل بريدك الإلكتروني وحسابك بنجاح."}

4.6. إعادة إرسال رابط التفعيل في FastAPI


@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 بسيطة تطلب من المستخدم إدخال بريده ثم تظهر له النتيجة.

5. تأمين نظام Email Verification وأفضل الممارسات

5.1. تحديد مدة صلاحية مناسبة

  • مدة 24 ساعة غالباً مناسبة لتفعيل البريد.
  • يمكن جعلها أقصر (مثل 2–4 ساعات) إذا كانت طبيعة التطبيق حساسة.

5.2. حماية من إعادة استخدام التوكن

  • بعد تفعيل الحساب، حتى لو استُخدم التوكن مرة أخرى، يجب أن يكون التفعيل Idempotent (لا يسبب أخطاء، فقط يعيد نجاح التفعيل أو يخبر أن الحساب مفعل).

5.3. عدم كشف إن كان البريد موجوداً صراحةً

  • في رسائل الخطأ عند التسجيل أو إعادة الإرسال، حاول عدم كشف إن كان البريد موجوداً أم لا، لتفادي User Enumeration.
  • مثلاً: “إذا كان البريد صحيحاً، سيتم إرسال رابط التفعيل إليه”.

5.4. تخزين محاولات الإرسال والRate Limiting

  • استخدم Redis أو قاعدة البيانات لتسجيل آخر وقت أرسل فيه رابط التفعيل لكل مستخدم.
  • اسمح بإرسال جديد كل عدد دقائق معين (مثلاً كل 5 دقائق)، وأقصى عدد محاولات في اليوم.

5.5. استخدام قنوات آمنة (HTTPS)

  • يجب أن يكون رابط التفعيل على HTTPS وليس HTTP لتفادي هجمات Man-in-the-middle.

5.6. ربط التفعيل مع نظام الدخول

  • عند محاولة تسجيل الدخول بحساب غير مفعل، أرسل رسالة واضحة للمستخدم:
    • “حسابك غير مفعل، تحقق من بريدك أو اضغط هنا لإعادة إرسال رابط التفعيل”.

6. دمج Email Verification مع مكوّنات أخرى في Django وFastAPI

نظام التفعيل بالبريد لا يعمل بمعزل عن بقية أجزاء التطبيق. غالباً ستحتاج إلى دمجه مع:

  • Authentication/Authorization: مثل OAuth2 أو JWT أو Session Auth.
    يمكنك قراءة: تنفيذ OAuth2 في Django و FastAPI: دليل عملي كامل.
  • CORS والواجهات الأمامية (Frontends): إن كنت تستخدم React أو Next.js أو غيرها، تأكد من إعداد CORS بشكل صحيح حتى تعمل روابط التفعيل من المتصفح بدون مشاكل.
    راجع: التعامل مع CORS بشكل صحيح في Django وFastAPI.
  • Background Tasks & Queues: لإرسال البريد في الخلفية واستخدام Celery أو RQ مع Django، وBackgroundTasks أو أدوات أخرى مع FastAPI.

7. خلاصة

بناء نظام Email Verification Django FastAPI ليس معقداً، لكنه ضروري لأي تطبيق جاد يعتمد على حسابات المستخدمين. الأفكار الأساسية مشتركة بين Django وFastAPI:

  • توليد توكن آمن يحتوي على معرف المستخدم ووقت انتهاء.
  • إرسال رابط تفعيل عبر البريد مع هذا التوكن.
  • Endpoint/ View لاستقبال التوكن والتحقق منه وتفعيل الحساب.
  • تقييد عدد مرات إعادة إرسال الرابط، ومنع استغلال النظام.

بتطبيق الخطوات والأكواد التي استعرضناها في هذا المقال، يمكنك بناء نظام تحقق بريد إلكتروني مرن، آمن، وقابل للتطوير في كل من Django وFastAPI، وضمان أن المستخدمين الذين يصلون لنظامك هم مستخدمون حقيقيون ببريد إلكتروني فعّال.

حول المحتوى:

شرح كيفية إرسال روابط تفعيل الحساب، تشفير التوكن، تحديد مدة الصلاحية، وإدارة إعادة التفعيل.

هل كان هذا مفيدًا لك؟

أضف تعليقك