<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های Mohammad saleh</title>
        <link>https://virgool.io/feed/@mohammad3a1eh</link>
        <description>مثلا برنامه نویس</description>
        <language>fa</language>
        <pubDate>2026-07-06 06:48:57</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/2732113/avatar/BYHXqU.jpg?height=120&amp;width=120</url>
            <title>Mohammad saleh</title>
            <link>https://virgool.io/@mohammad3a1eh</link>
        </image>

                    <item>
                <title>ساخت سیستم احراز هویت با Simple JWT در Django: گام‌به‌گام تا امنیت کامل</title>
                <link>https://virgool.io/@mohammad3a1eh/%D8%B3%D8%A7%D8%AE%D8%AA-%D8%B3%DB%8C%D8%B3%D8%AA%D9%85-%D8%A7%D8%AD%D8%B1%D8%A7%D8%B2-%D9%87%D9%88%DB%8C%D8%AA-%D8%A8%D8%A7-simple-jwt-%D8%AF%D8%B1-django-%DA%AF%D8%A7%D9%85-%D8%A8%D9%87-%DA%AF%D8%A7%D9%85-%D8%AA%D8%A7-%D8%A7%D9%85%D9%86%DB%8C%D8%AA-%DA%A9%D8%A7%D9%85%D9%84-gg3zu6ubrjc9</link>
                <description>در پروژه‌های مبتنی بر API، یکی از چالش‌های اصلی، پیاده‌سازی احراز هویت امن و قابل اطمینان برای کاربران است؛ مخصوصاً وقتی پای نگهداری توکن‌ها به میان می‌آید.در این مطلب، می‌خواهیم با استفاده از Django REST Framework و کتابخانه‌ی Simple JWT، سیستمی برای مدیریت احراز هویت طراحی کنیم که هم امن باشد، هم از نظر تجربه کاربری روان و بدون دردسر.در مسیر این آموزش با مفاهیمی مثل:تولید و مدیریت توکن‌های JWTذخیره توکن‌ها در کوکیبلک‌لیست کردن رفرش توکن‌هاآشنا خواهیم شد. هدف ما ساختن سیستمی‌ست که بتواند هم نیازهای امنیتی را پوشش دهد و هم در پروژه‌های واقعی و چندفرانتی کاربردی باشد.مرحله اول: ساخت محیط مجازی و فعال سازیوارد پوشه‌ای شوید که می‌خواهید پروژه‌ی جنگو (Django) در آن ساخته شود، سپس دستورات زیر را برای ایجاد محیط مجازی و فعال‌سازی آن اجرا کنید:python -m venv .venv
.\.venv\Scripts\activate.ps1 # powershellمرحله دوم: نصب کتابخانه‌های پایتونpip install django
pip install djangorestframework 
pip install djangorestframework-simplejwt
pip install djangorestframework-simplejwt[token_blacklist]مرحله سوم: ایجاد پروژه جنگو و اپ مدیریت کاربرانdjango-admin startproject config .این دستور باعث می‌شه فایل‌های پروژه در همین مسیر ساخته بشن، نه داخل پوشه‌ی جدید.python manage.py startapp accountsمرحله چهارم: انجام تنظیمات اولیه پروژه جنگو و اعمال تغییرات به پایگاه دادهدر ادامه، باید اپلیکیشن‌هایی که در پروژه استفاده می‌کنیم، از جمله ماژول‌های مربوط به REST و JWT را در تنظیمات Django فعال کنیم.# config\settings.py
INSTALLED_APPS = [
    &#039;rest_framework&#039;,
    &#039;rest_framework_simplejwt.token_blacklist&#039;,
    &#039;accounts&#039;,
]در این مرحله، مشخص می‌کنیم که احراز هویت پیش‌فرض پروژه از نوع JWT باشد تا Django REST Framework توکن‌های JWT را به‌عنوان روش تأیید هویت کاربران بشناسد.# config\settings.py
REST_FRAMEWORK = {
    &#039;DEFAULT_AUTHENTICATION_CLASSES&#039;: (
        &#039;rest_framework_simplejwt.authentication.JWTAuthentication&#039;,
    ),
}در ادامه، با اضافه کردن تنظیمات مربوط به Simple JWT، مشخص می‌کنیم که توکن‌ها چه مدت اعتبار داشته باشند، در زمان رفرش شدن آیا توکن قبلی بلاک شود یا نه، و هدر توکن‌ها با چه پیشوندی ارسال شود.# config\settings.py
from datetime import timedelta

SIMPLE_JWT = {
    &#039;ACCESS_TOKEN_LIFETIME&#039;: timedelta(minutes=5),
    &#039;REFRESH_TOKEN_LIFETIME&#039;: timedelta(days=7),
    &#039;ROTATE_REFRESH_TOKENS&#039;: True,
    &#039;BLACKLIST_AFTER_ROTATION&#039;: True,
    &#039;AUTH_HEADER_TYPES&#039;: (&#039;Bearer&#039;,),
    &#039;AUTH_TOKEN_CLASSES&#039;: (&#039;rest_framework_simplejwt.tokens.AccessToken&#039;,),
    &#039;TOKEN_BLACKLIST_ENABLED&#039;: True,
}ابتدا باید تغییرات ایجاد شده در مدل‌ها را با دستور makemigrations به فایل‌های مایگریشن تبدیل کنیم، سپس با اجرای دستور migrate این تغییرات را به پایگاه داده اعمال کنیم.python manage.py makemigrations
python manage.py migrateمرحله پنجم: پیاده‌سازی رجیستر، لاگین، و رفرش توکن با Simple JWT و ذخیره توکن‌ها در کوکی HttpOnlyدر این بخش، یک Serializer برای ثبت‌نام کاربران با استفاده از مدل پیش‌فرض User جنگو می‌سازیم. این Serializer دو فیلد رمز عبور (password) و تکرار آن (password2) را دریافت می‌کند تا صحت مطابقت رمز عبور را بررسی کند. سپس، با متد create کاربر جدید را ساخته و پسورد را به صورت امن (هش شده) ذخیره می‌کند.# accounts\serializers.py
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError as DjangoValidationError
from django.contrib.auth.models import User
from rest_framework_simplejwt.serializers import TokenRefreshSerializer
from rest_framework import serializers

class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, required=True, style={&#039;input_type&#039;: &#039;password&#039;})
    password2 = serializers.CharField(write_only=True, required=True, style={&#039;input_type&#039;: &#039;password&#039;})

    class Meta:
        model = User
        fields = (&#039;username&#039;, &#039;email&#039;, &#039;password&#039;, &#039;password2&#039;)

    def validate(self, data):
        if data[&#039;password&#039;] != data[&#039;password2&#039;]:
            raise serializers.ValidationError({&quot;password2&quot;: &quot;Passwords must match.&quot;})
        try:
            validate_password(data[&#039;password&#039;])
        except DjangoValidationError as e:
            raise serializers.ValidationError({&#039;password&#039;: list(e.messages)})
        return data

    def create(self, validated_data):
        user = User.objects.create(
            username=validated_data[&#039;username&#039;],
            email=validated_data.get(&#039;email&#039;, &#039;&#039;)
        )
        user.set_password(validated_data[&#039;password&#039;])
        user.save()
        return user

class CookieTokenRefreshSerializer(TokenRefreshSerializer):
    pass
با این روش، ما امنیت رمز عبور را تضمین می‌کنیم و همچنین از ورود رمزهای عبور ناسازگار جلوگیری می‌کنیم. همچنین چون از مدل پیش‌فرض User استفاده شده، نیازی به تعریف مدل اختصاصی نیست و می‌توانیم به سرعت فرآیند ثبت‌نام را پیاده کنیم.در این مرحله، یک view برای ثبت‌نام کاربران می‌سازیم که داده‌های ورودی رو از طریق RegisterSerializer پردازش می‌کنه و در صورت اعتبارسنجی موفق، یک کاربر جدید در دیتابیس ایجاد می‌شه. چون این endpoint برای کاربران مهمان (غیر لاگین‌شده) قابل دسترسه، سطح دسترسی اون رو AllowAny قرار می‌دیم.# accounts\views.py
from rest_framework import generics, permissions, status
from rest_framework.response import Response
from .serializers import RegisterSerializer
from rest_framework_simplejwt.tokens import RefreshToken

ACCESS_TOKEN_LIFETIME_SECONDS = 5 * 60
REFRESH_TOKEN_LIFETIME_SECONDS = 7 * 24 * 60 * 60

class RegisterView(generics.CreateAPIView):
    serializer_class = RegisterSerializer
    permission_classes = [permissions.AllowAny]

    def post(self, request, *args, **kwargs):
        # Register user
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()

        # Generate tokens
        refresh = RefreshToken.for_user(user)
        access = refresh.access_token

        # Response object
        res = Response({&#039;detail&#039;: &#039;User registered and logged in.&#039;}, status=status.HTTP_201_CREATED)

        # Set tokens in HttpOnly cookies
        res.set_cookie(
            key=&#039;access_token&#039;,
            value=str(access),
            httponly=True,
            secure=True,
            samesite=&#039;Lax&#039;,
            max_age=ACCESS_TOKEN_LIFETIME_SECONDS
        )

        res.set_cookie(
            key=&#039;refresh_token&#039;,
            value=str(refresh),
            httponly=True,
            secure=True,
            samesite=&#039;Lax&#039;,
            max_age=REFRESH_TOKEN_LIFETIME_SECONDS
        )

        return resدر این view، بعد از اینکه کاربر جدید با موفقیت ثبت‌نام می‌کنه، بلافاصله یک جفت توکن JWT (شامل Access و Refresh Token) برای او ایجاد می‌شه.به‌جای اینکه توکن‌ها رو در بدنه‌ی پاسخ برگردونیم (که امنیت کمتری داره)، اون‌ها رو با استفاده از set_cookie در کوکی‌های HttpOnly قرار دادیم. این یعنی:توکن‌ها از طریق JavaScript قابل دسترسی نیستند (جلوگیری از حملات XSS).با تنظیم گزینه‌ی secure=True، این کوکی‌ها فقط در کانکشن‌های HTTPS ارسال می‌شن.گزینه‌ی samesite=&#039;Lax&#039; یا &#039;Strict&#039; کمک می‌کنه از حملات CSRF جلوگیری بشه.همچنین زمان انقضای کوکی‌ها با max_age مشخص شده که باید با تنظیمات SIMPLE_JWT هماهنگ باشه.با این کار، کاربر پس از ثبت‌نام به‌صورت خودکار لاگین هم می‌شه و توکن‌ها به‌صورت امن در مرورگر ذخیره می‌شن، بدون نیاز به دخالت مستقیم فرانت‌اند برای ذخیره‌سازی دستی توکن.اما در حالت عادی، کتابخانه‌ی Simple JWT انتظار داره که توکن JWT در هدر Authorization هر درخواست ارسال بشه.ولی وقتی ما توکن JWT رو به‌صورت امن (با HttpOnly) در کوکی ذخیره می‌کنیم، فرانت‌اند نمی‌تونه مستقیماً این توکن رو بخونه و داخل هدر بذاره.برای حل این مشکل، یک middleware سفارشی می‌نویسیم که قبل از رسیدن request به viewها، کوکی حاوی توکن را بررسی کند و در صورت وجود، توکن را در هدر Authorization قرار دهد. اینطوری Simple JWT بدون نیاز به هیچ تغییر خاصی توکن را شناسایی می‌کند.# config\middleware\jwt_cookie_auth.py
class JWTTokenCookieMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        access_token = request.COOKIES.get(&#039;access_token&#039;)

        if access_token:
            request.META[&#039;HTTP_AUTHORIZATION&#039;] = f&#039;Bearer {access_token}&#039;

        return self.get_response(request)حالا باید این میدل‌ور رو قبل از میدل‌ور AuthenticationMiddleware اضافه کنیم.# config\settings.py
MIDDLEWARE = [
    &#039;config.middleware.jwt_cookie_auth.JWTTokenCookieMiddleware&#039;,
    &#039;django.middleware.security.SecurityMiddleware&#039;,
    &#039;django.contrib.sessions.middleware.SessionMiddleware&#039;,
]خب الان که بعد از ساخت حساب کاربری توکن‌ها رو توی کوکی تنظیم کردیم باید همین کار رو برای ویوهای رفرش توکن و لاگین انجام بدیم تا همه چیز درست کار کنه. اول رفرش توکن:# accounts\views.py
from rest_framework_simplejwt.views import TokenRefreshView
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
from rest_framework.response import Response
from rest_framework import status
from datetime import timedelta

ACCESS_TOKEN_LIFETIME_SECONDS = 5 * 60
REFRESH_TOKEN_LIFETIME_SECONDS = 7 * 24 * 60 * 60

class CustomTokenRefreshView(APIView):
    def post(self, request, *args, **kwargs):
        refresh_token = request.COOKIES.get(&#039;refresh_token&#039;)
        if not refresh_token:
            return Response({&quot;refresh&quot;: [&quot;Refresh token not found in cookies.&quot;]}, status=400)

        serializer = CookieTokenRefreshSerializer(data={&#039;refresh&#039;: refresh_token}, context={&#039;request&#039;: request})

        try:
            serializer.is_valid(raise_exception=True)
        except TokenError as e:
            raise InvalidToken(e.args[0])

        access_token = serializer.validated_data.get(&quot;access&quot;)
        refresh_token = serializer.validated_data.get(&quot;refresh&quot;)

        response = Response({&quot;detail&quot;: &quot;Tokens refreshed and stored in cookies.&quot;})

        response.set_cookie(
            key=&#039;access_token&#039;,
            value=access_token,
            httponly=True,
            secure=True,
            samesite=&#039;Lax&#039;,
            max_age=ACCESS_TOKEN_LIFETIME_SECONDS
        )

        response.set_cookie(
            key=&#039;refresh_token&#039;,
            value=refresh_token,
            httponly=True,
            secure=True,
            samesite=&#039;Lax&#039;,
            max_age=REFRESH_TOKEN_LIFETIME_SECONDS
        )

        return responseو بعد لاگین:# accounts\views.py
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
from rest_framework.response import Response
from rest_framework import status

ACCESS_TOKEN_LIFETIME_SECONDS = 5 * 60
REFRESH_TOKEN_LIFETIME_SECONDS = 7 * 24 * 60 * 60

class CustomTokenObtainPairView(TokenObtainPairView):
    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)

        try:
            serializer.is_valid(raise_exception=True)
        except TokenError as e:
            raise InvalidToken(e.args[0])

        access_token = serializer.validated_data.get(&#039;access&#039;)
        refresh_token = serializer.validated_data.get(&#039;refresh&#039;)

        response = Response({&quot;detail&quot;: &quot;Login successful. Tokens stored in cookies.&quot;}, status=status.HTTP_200_OK)

        response.set_cookie(
            key=&#039;access_token&#039;,
            value=access_token,
            httponly=True,
            secure=True,
            samesite=&#039;Lax&#039;,
            max_age=ACCESS_TOKEN_LIFETIME_SECONDS 
        )

        response.set_cookie(
            key=&#039;refresh_token&#039;,
            value=refresh_token,
            httponly=True,
            secure=True,
            samesite=&#039;Lax&#039;,
            max_age=REFRESH_TOKEN_LIFETIME_SECONDS 
        )

        return responseمرحله ششم: پیاده‌سازی لاگ اوتبرای پیاده‌سازی عملیات لاگ‌اوت در سیستم JWT که توکن‌ها در کوکی‌های HttpOnly ذخیره شده‌اند، لازم است توکن‌های ذخیره شده در کوکی حذف شوند و همچنین توکن رفرش بلاک‌لیست شود تا دیگر امکان استفاده مجدد از آن وجود نداشته باشد. این کار باعث می‌شود امنیت سیستم بالاتر رفته و کاربر عملاً از سیستم خارج شود.ویوی زیر این مراحل را به صورت کامل انجام می‌دهد: ابتدا توکن رفرش را از کوکی دریافت می‌کند، سپس آن را در لیست سیاه قرار می‌دهد و در نهایت کوکی‌های مربوط به توکن‌ها را حذف می‌کند.# accounts\views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework_simplejwt.token_blacklist.models import BlacklistedToken, OutstandingToken
from rest_framework.permissions import IsAuthenticated

class LogoutView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        try:
            refresh_token = request.COOKIES.get(&#039;refresh_token&#039;)
            if refresh_token is None:
                return Response({&quot;detail&quot;: &quot;No refresh token found&quot;}, status=status.HTTP_400_BAD_REQUEST)

            token = OutstandingToken.objects.get(token=refresh_token)
            BlacklistedToken.objects.create(token=token)

            response = Response({&quot;detail&quot;: &quot;Logged out successfully&quot;}, status=status.HTTP_200_OK)
            response.delete_cookie(&#039;access_token&#039;)
            response.delete_cookie(&#039;refresh_token&#039;)
            return response

        except OutstandingToken.DoesNotExist:
            return Response({&quot;detail&quot;: &quot;Invalid refresh token&quot;}, status=status.HTTP_400_BAD_REQUEST)با اجرای این ویو، توکن رفرش کاربر به لیست سیاه اضافه می‌شود و کوکی‌های توکن از مرورگر حذف می‌شوند. این باعث می‌شود که دیگر نتوان از توکن‌های قبلی برای دسترسی یا رفرش استفاده کرد و کاربر به طور کامل از سیستم خارج می‌شود.این روش یکی از امن‌ترین راه‌ها برای پیاده‌سازی لاگ‌اوت در سیستم‌های مبتنی بر JWT است که توکن‌ها را در کوکی HttpOnly ذخیره می‌کنند.مرحله هفتم: تعریف و تنظیم مسیرهای API در Django برای احراز هویت JWTبعد از اینکه ویوهای مربوط به رجیستر، لاگین، رفرش توکن و لاگ‌اوت را نوشتیم، باید آن‌ها را در فایل urls.py تعریف کنیم تا بتوانیم از طریق آدرس‌های مشخص شده، این عملیات‌ها را فراخوانی کنیم. معمولاً یک فایل urls.py در اپ مربوط به احراز هویت می‌سازیم و مسیرهای مورد نیاز را آنجا قرار می‌دهیم.# accounts\urls.py
from django.urls import path
from accounts.views import RegisterView, CustomTokenObtainPairView, CustomTokenRefreshView, LogoutView

urlpatterns = [
    path(&#039;register/&#039;, RegisterView.as_view(), name=&#039;register&#039;),
    path(&#039;login/&#039;, CustomTokenObtainPairView.as_view(), name=&#039;login&#039;),
    path(&#039;refresh/&#039;, CustomTokenRefreshView.as_view(), name=&#039;token_refresh&#039;),
    path(&#039;logout/&#039;, LogoutView.as_view(), name=&#039;logout&#039;),
]# config\urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path(&#039;admin/&#039;, admin.site.urls),
    path(&#039;api/&#039;, include(&#039;accounts.urls&#039;)), 
]با این تنظیمات، API ما مسیرهای مشخصی برای عملیات‌های اصلی احراز هویت دارد. این آدرس‌ها را فرانت‌اند می‌تواند برای ارسال درخواست‌های HTTP به کار ببرد.حالا مسیرهای API به این شکل خواهند بود:http://127.0.0.1:8000/api/register/
http://127.0.0.1:8000/api/login/
http://127.0.0.1:8000/api/refresh/
http://127.0.0.1:8000/api/logout/مرحله هشتم: تست سیستم و اجرای سیستم لوکالبا دستور runserver پروژه رو اجرا می‌کنیم و پروژه معمولا روی پورت 8000 منتظر دریافت درخواست است. python manage.py runserverاگه ادرس http://127.0.0.1:8000 رو توی مرورگر باز کنیم با همچین صفحه ای روبه‌رو می‌شید.در ادامه نمونه درخواست‌های ارسال شده با نرم‌افزار HTTPie رو قرار می‌بینید.درخواست رجیستردرخواست لاگ اوتدرخواست رفرش توکندرخواست لاگیناگر هنگام اجرا با خطایی مواجه شدید یا بخشی از آموزش برایتان مبهم بود، می‌توانید به کد کامل اون رو دسترسی داشته باشید: github.com/mohammad3a1eh/django-api-simple-jwtتمام فایل‌ها و تنظیمات به‌صورت یکپارچه در آن‌جا قرار داده شده‌اند.در نهایت، همان‌طور که از عنوان مطلب «ساخت سیستم احراز هویت با Simple JWT در Django: گام‌به‌گام تا امنیت کامل» برمی‌آید، هدف این آموزش فراهم کردن یک مسیر شفاف و کاربردی برای پیاده‌سازی این سیستم است. اما طبیعی‌ست که امنیت کامل، نیازمند بررسی دقیق‌تر، تست‌های امنیتی و تنظیمات نهایی متناسب با شرایط محیط اجرایی باشد.موفق باشید.</description>
                <category>Mohammad saleh</category>
                <author>Mohammad saleh</author>
                <pubDate>Wed, 07 Jan 2026 11:55:37 +0330</pubDate>
            </item>
            </channel>
</rss>