JWT واقعاً چگونه کار می‌کند؟ Access Token، Refresh Token و Session

JWT واقعاً چگونه کار می‌کند؟ Access Token، Refresh Token و Session

توسعه‌دهنده بک‌اند

یکی از روش‌های بسیار رایج برای مدیریت Authentication در APIها استفاده از JWT یا JSON Web Token است.

JWT واقعاً چگونه کار می‌کند؟ Access Token، Refresh Token و Session

صارم توکلی

۱۲ دقیقه مطالعه

۰ نفر

۱۴۰۵/۶/۳۰

JWT واقعاً چگونه کار می‌کند؟ Access Token، Refresh Token و Session

مقدمه؛ JWT دقیقاً چه مشکلی را حل می‌کند؟

فرض کنید کاربر وارد یک وب‌سایت می‌شود و با ایمیل و رمز عبور خود Login می‌کند.

سرور باید از این به بعد بداند:

این Request متعلق به کدام کاربر است؟

مثلاً کاربر بعد از Login درخواست زیر را ارسال می‌کند:

GET /api/profile

سرور باید بتواند تشخیص دهد که این Request متعلق به User شماره 42 است.

اینجاست که مفهوم Authentication وارد می‌شود.

یکی از روش‌های بسیار رایج برای مدیریت Authentication در APIها استفاده از JWT یا JSON Web Token است.

اما JWT فقط یک «توکن برای Login» نیست.

برای درک درست آن باید چند مفهوم را کنار هم ببینیم:

Authentication
       ↓
Access Token
       ↓
Refresh Token
       ↓
Session
       ↓
Authorization

در این مقاله از صفر بررسی می‌کنیم JWT چیست، داخل آن چه اطلاعاتی وجود دارد، Access Token و Refresh Token چه تفاوتی دارند و Session دقیقاً کجای این معماری قرار می‌گیرد.


JWT چیست؟

JWT مخفف JSON Web Token است.

JWT یک استاندارد برای انتقال اطلاعات به شکل یک Token قابل انتقال و قابل بررسی است.

ساختار معمول JWT به شکل زیر است:

xxxxx.yyyyy.zzzzz

یعنی JWT از سه بخش تشکیل شده است:

Header.Payload.Signature

برای مثال:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjMiLCJyb2xlIjoidXNlciJ9
.
signature

این سه قسمت عبارت‌اند از:

Header

مشخص می‌کند Token با چه نوع الگوریتمی امضا شده است.

مثلاً:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

اطلاعات یا Claimهای مربوط به Token در این قسمت قرار می‌گیرند.

مثلاً:

{
  "sub": "123",
  "role": "user",
  "iat": 1720000000,
  "exp": 1720003600
}

Signature

Signature برای بررسی صحت Token استفاده می‌شود.

به شکل ساده:

Header
+
Payload
+
Secret / Private Key
        ↓
    Signature

سرور هنگام دریافت Token می‌تواند Signature را بررسی کند تا متوجه شود Token معتبر و دستکاری‌نشده است.


آیا JWT رمزنگاری شده است؟

یکی از رایج‌ترین سوءتفاهم‌ها درباره JWT این است که:

«اطلاعات داخل JWT رمزنگاری شده‌اند.»

در حالت معمول، JWT رمزگذاری نشده است؛ بلکه Base64URL-encoded شده و امضا شده است.

یعنی اگر Payload شامل این اطلاعات باشد:

{
  "userId": 123,
  "role": "admin"
}

نباید فرض کنید این اطلاعات مخفی هستند.

بنابراین نباید اطلاعات حساس مانند:

Password
Credit Card Number
Private Data
Secret Keys

را داخل Payload قرار دهید.

Signature از تغییر محتوا جلوگیری می‌کند، اما به‌تنهایی محتوای JWT را مخفی نمی‌کند.


JWT چگونه Authentication را انجام می‌دهد؟

بیایید کل فرآیند را مرحله‌به‌مرحله ببینیم.

مرحله اول؛ Login

کاربر اطلاعات خود را ارسال می‌کند:

POST /api/login

مثلاً:

{
  "email": "user@example.com",
  "password": "********"
}

Backend اطلاعات کاربر را بررسی می‌کند.

اگر Login موفق باشد، سرور معمولاً یک Access Token ایجاد می‌کند.

User
  ↓
Login
  ↓
Backend
  ↓
Validate Credentials
  ↓
Generate Token
  ↓
Access Token

Access Token چیست؟

Access Token توکنی است که Client برای دسترسی به منابع محافظت‌شده استفاده می‌کند.

مثلاً:

GET /api/profile
Authorization: Bearer <access-token>

Backend Token را دریافت می‌کند و آن را Validate می‌کند.

اگر معتبر باشد:

Token Valid
    ↓
Identify User
    ↓
Check Authorization
    ↓
Return Resource

Access Token معمولاً عمر کوتاهی دارد.

مثلاً:

5 دقیقه
15 دقیقه
30 دقیقه

مدت دقیق به معماری و سطح امنیت موردنیاز سیستم بستگی دارد.

چرا کوتاه؟

چون اگر Access Token سرقت شود، مدت قابل استفاده بودن آن محدودتر خواهد بود.


Refresh Token چیست؟

اگر Access Token خیلی زود منقضی شود، کاربر مجبور خواهد بود مرتب Login کند.

اینجاست که Refresh Token وارد می‌شود.

Refresh Token برای گرفتن یک Access Token جدید استفاده می‌شود.

معماری ساده:

Access Token
     ↓
Short-lived
     ↓
Expires
     ↓
Refresh Token
     ↓
New Access Token

مثلاً:

Access Token
15 دقیقه

Refresh Token
چند روز / هفته

البته طول عمر واقعی باید بر اساس مدل تهدید و نیاز محصول تعیین شود.


چرا دو Token داشته باشیم؟

ممکن است بپرسید:

چرا یک Token با عمر طولانی نداشته باشیم؟

مثلاً:

JWT
Valid for 30 days

مشکل این است که اگر این Token سرقت شود، مهاجم می‌تواند برای مدت طولانی از آن استفاده کند.

در مدل Access + Refresh:

Access Token
     ↓
کوتاه‌مدت
     ↓
محدود کردن آسیب در صورت سرقت

Refresh Token
     ↓
برای دریافت Access Token جدید
     ↓
نگهداری و کنترل محافظت‌شده‌تر

این جداسازی امکان کنترل بیشتری روی چرخه عمر Authentication ایجاد می‌کند.


Refresh Token دقیقاً چگونه کار می‌کند؟

فرض کنید کاربر Login کرده است.

Backend پاسخ می‌دهد:

{
  "accessToken": "access-token",
  "refreshToken": "refresh-token"
}

Access Token برای درخواست‌های معمول استفاده می‌شود.

GET /api/orders
Authorization: Bearer access-token

بعد از مدتی:

Access Token
     ↓
Expired

Client درخواست Refresh می‌فرستد:

POST /api/auth/refresh

Backend Refresh Token را بررسی می‌کند.

اگر معتبر باشد:

Refresh Token
      ↓
Validate
      ↓
Generate New Access Token
      ↓
Return

کاربر بدون وارد کردن دوباره Password می‌تواند به کار خود ادامه دهد.


Refresh Token را کجا نگه داریم؟

این قسمت یکی از مهم‌ترین بخش‌های طراحی Authentication است.

دو مکان رایج:

localStorage
Cookie

اما این دو از نظر امنیتی یکسان نیستند.

برای Tokenهای حساس، بسیاری از معماری‌های وب ترجیح می‌دهند Refresh Token در Cookie با ویژگی‌های امنیتی مناسب قرار گیرد، مثلاً:

HttpOnly
Secure
SameSite

HttpOnly

JavaScript سمت Client نمی‌تواند مستقیماً Cookie را بخواند.

این ویژگی می‌تواند در برابر برخی سناریوهای سرقت Token از طریق XSS کمک‌کننده باشد.

Secure

Cookie فقط روی HTTPS ارسال می‌شود.

SameSite

به کنترل ارسال Cookie در درخواست‌های Cross-Site کمک می‌کند و می‌تواند ریسک برخی حملات CSRF را کاهش دهد.

اما تنظیم صحیح آن به معماری، دامنه‌ها و نحوه ارتباط Frontend و Backend بستگی دارد.


آیا localStorage کاملاً ممنوع است؟

نه.

اما باید ریسک آن را در نظر گرفت.

اگر Access Token در:

localStorage

ذخیره شود، JavaScript صفحه می‌تواند آن را بخواند.

در صورت وجود یک XSS موفق، مهاجم ممکن است بتواند Token را استخراج کند.

بنابراین انتخاب محل ذخیره Token باید بر اساس مدل تهدید انجام شود، نه صرفاً یک قانون کلی.


Session چیست؟

Session مفهوم متفاوتی از JWT است.

در Session-based Authentication، سرور اطلاعات Session را نگهداری می‌کند.

مثلاً:

Browser
   ↓
Session ID
   ↓
Backend
   ↓
Session Store

فرض کنید:

Session ID = abc123

سرور در Session Store نگه می‌دارد:

{
  "sessionId": "abc123",
  "userId": 42,
  "expiresAt": "..."
}

Browser فقط Session ID را ارسال می‌کند.


JWT و Session چه تفاوتی دارند؟

تفاوت اصلی در این است که وضعیت Authentication کجا نگهداری می‌شود.

در Session:

Client
  ↓
Session ID
  ↓
Server
  ↓
Session Store
  ↓
User Data

در JWT:

Client
  ↓
JWT
  ↓
Server
  ↓
Verify Signature
  ↓
Claims

در JWT معمولاً اطلاعات لازم برای اعتبارسنجی Token در خود Token قرار دارد و سرور می‌تواند بدون lookup معمول Session Store آن را Verify کند.

اما این به معنی «کاملاً Stateless بودن کل سیستم» نیست؛ بسیاری از سیستم‌های JWT برای Revocation، Refresh Tokenها، Session Management یا سایر نیازها همچنان state سمت سرور دارند.


JWT Stateless است؛ اما این جمله نصف واقعیت است

زیاد می‌شنویم:

«JWT یعنی Stateless Authentication.»

این جمله در یک سناریوی ساده می‌تواند درست باشد، اما در سیستم‌های واقعی همیشه به این سادگی نیست.

فرض کنید Refresh Tokenها را در Database نگهداری می‌کنیم:

Refresh Token
      ↓
Database
      ↓
User Session

در این حالت بخشی از Authentication State در Server وجود دارد.

همچنین اگر بخواهیم Token را Revoke کنیم، ممکن است نیاز به نگهداری اطلاعاتی سمت سرور داشته باشیم.

بنابراین بهتر است بگوییم:

JWT امکان اعتبارسنجی بدون Session Lookup برای Access Token را فراهم می‌کند، اما کل سیستم Authentication لزوماً Stateless نیست.


JWT چگونه اعتبار Token را بررسی می‌کند؟

فرض کنید Client این Token را ارسال کرده است:

Authorization: Bearer eyJ...

Backend معمولاً مراحل مختلفی را بررسی می‌کند:

Receive Token
      ↓
Parse Token
      ↓
Verify Signature
      ↓
Check Expiration
      ↓
Check Issuer / Audience
      ↓
Read Claims
      ↓
Authorization

برای مثال:

{
  "sub": "123",
  "role": "editor",
  "exp": 1800000000
}

سرور می‌تواند بررسی کند:

Token expired?
        ↓
Role allowed?
        ↓
User authorized?

نکته مهم:

Authentication و Authorization یکی نیستند.


Authentication vs Authorization

Authentication

یعنی:

«تو چه کسی هستی؟»

مثلاً:

User ID = 123

Authorization

یعنی:

«چه کاری اجازه داری انجام دهی؟»

مثلاً:

User
  → Read Profile ✓
  → Delete User ✗

JWT می‌تواند Claimهایی مانند Role یا Scope داشته باشد، اما وجود یک Role داخل Token به‌تنهایی نباید بدون طراحی درست Authorization به معنی دسترسی قطعی به همه منابع باشد.


Access Token و Refresh Token؛ یک تفاوت مهم

ویژگیAccess TokenRefresh Tokenکاربرددسترسی به APIدریافت Access Token جدیدطول عمرمعمولاً کوتاهمعمولاً طولانی‌تراستفاده در APIبلهمعمولاً خیرحساسیتبالابسیار بالانیاز به کنترل Revocationبسته به معماریمعمولاً مهم‌ترارسال مداومبلهخیر

یکی از اشتباهات رایج این است که Refresh Token را برای تمام APIها ارسال کنیم.

در معماری معمول:

Access Token
     ↓
API Requests

Refresh Token
     ↓
Refresh Endpoint

Refresh Token Rotation چیست؟

در سیستم‌های حساس‌تر می‌توان از Refresh Token Rotation استفاده کرد.

ایده ساده است:

Refresh Token A
      ↓
Refresh
      ↓
Access Token B
+
Refresh Token C

یعنی Refresh Token قبلی دیگر قابل استفاده نباشد.

اگر مهاجم Token قبلی را داشته باشد و تلاش کند دوباره از آن استفاده کند، Backend می‌تواند آن را شناسایی کند و Session مربوطه را بررسی یا مسدود کند.

این روش می‌تواند سطح کنترل و تشخیص سوءاستفاده از Refresh Token را افزایش دهد.


JWT Expiration چیست؟

JWT معمولاً Claimای به نام:

exp

دارد.

این Claim زمان انقضای Token را مشخص می‌کند.

مثلاً:

{
  "sub": "42",
  "exp": 1800000000
}

پس Backend می‌تواند بررسی کند:

Current Time > exp
       ↓
Token Expired

در این حالت Access Token باید دوباره با فرآیند Refresh دریافت شود.


چرا نباید Access Token را بیش از حد طولانی کنیم؟

فرض کنید:

Access Token
Valid for 30 days

اگر Token لو برود، مهاجم ممکن است برای مدت طولانی از آن استفاده کند.

اگر:

Access Token
Valid for 10 minutes

باشد، پنجره زمانی سوءاستفاده محدودتر است.

اما اینجا یک Trade-off وجود دارد:

Short Lifetime
     ↓
Security ↑
Refresh Frequency ↑

در مقابل:

Long Lifetime
     ↓
Refresh Frequency ↓
Potential Exposure Window ↑

بنابراین طول عمر Token باید متناسب با کاربرد و مدل تهدید انتخاب شود.


JWT در معماری Frontend و Backend

یک معماری رایج می‌تواند چیزی شبیه این باشد:

┌──────────────┐
│   Frontend   │
└──────┬───────┘
       │
       │ Login
       ↓
┌──────────────┐
│   Backend    │
└──────┬───────┘
       │
       ├── Access Token
       │
       └── Refresh Token

سپس:

Frontend
   │
   │ Authorization: Bearer AccessToken
   ↓
Backend API
   │
   ↓
Verify Token
   │
   ↓
Return Data

و هنگام Expire شدن:

Access Token Expired
        ↓
Refresh Endpoint
        ↓
Refresh Token
        ↓
New Access Token

آیا JWT برای همه پروژه‌ها مناسب است؟

خیر.

JWT یک ابزار است، نه یک الزام.

برای بعضی پروژه‌ها Session-based Authentication انتخاب ساده‌تر و مناسبی است.

برای برخی معماری‌های API، SPA، Mobile Application یا سیستم‌های توزیع‌شده، Token-based Authentication می‌تواند مناسب‌تر باشد.

تصمیم باید بر اساس مواردی مانند:

Architecture
Security Requirements
Client Types
Scalability
Session Management
Revocation Requirements
Infrastructure

گرفته شود.


اشتباهات رایج در استفاده از JWT

۱. قرار دادن اطلاعات حساس داخل Payload

JWT را نباید به عنوان محل امن برای نگهداری Secretها در نظر گرفت.


۲. Access Token با عمر بسیار طولانی

Token طولانی‌مدت در صورت سرقت، پنجره سوءاستفاده بزرگ‌تری ایجاد می‌کند.


۳. نگهداری Token حساس بدون بررسی XSS

اگر Token در محلی قرار گیرد که JavaScript بتواند آن را بخواند، یک XSS موفق می‌تواند خطرناک‌تر شود.


۴. نداشتن Expiration

Token بدون Expiration می‌تواند مدت بسیار زیادی معتبر بماند.


۵. اعتماد مستقیم به Payload

اینکه Payload می‌گوید:

{
  "role": "admin"
}

به معنی معتبر بودن آن نیست.

Backend باید Signature و سایر شرایط اعتبار Token را بررسی کند.


۶. استفاده از یک Secret ضعیف

اگر الگوریتم و کلید امضای JWT درست مدیریت نشوند، امنیت کل Authentication می‌تواند تحت تأثیر قرار گیرد.

Secretها باید:

  • قوی باشند

  • امن نگهداری شوند

  • داخل Repository قرار نگیرند

  • به شکل مناسب Rotate شوند


۷. ارسال Refresh Token برای همه APIها

Refresh Token معمولاً باید فقط در فرآیند Refresh استفاده شود.


JWT در برابر Session؛ کدام بهتر است؟

به جای اینکه بگوییم:

JWT بهتر است.

یا:

Session بهتر است.

بهتر است معماری را مقایسه کنیم.

موضوعJWTSessionنگهداری State سمت سروربرای Access Token می‌تواند کمتر باشدمعمولاً داردRevocation فوریپیچیده‌ترمعمولاً ساده‌ترمناسب برای APIهای توزیع‌شدهمی‌تواند مناسب باشدنیازمند Session Store مشترک در معماری چندسروریپیچیدگیبسته به معماری می‌تواند بالا باشدمعمولاً ساده‌ترمدیریت Refreshمعمولاً نیازمند طراحی دقیقبسته به پیاده‌سازیStateless Access Validationامکان‌پذیرمعمولاً خیر

بنابراین انتخاب بین JWT و Session بیشتر یک تصمیم معماری است تا یک انتخاب تکنولوژی.


یک معماری متداول و قابل اتکا

برای بسیاری از Web Applicationها می‌توان چنین الگویی را در نظر گرفت:

                    LOGIN
                      │
                      ↓
                ┌───────────┐
                │  Backend  │
                └─────┬─────┘
                      │
             ┌────────┴────────┐
             ↓                 ↓
      Access Token       Refresh Token
       Short-lived        Longer-lived
             │                 │
             │                 ↓
             │           Secure Cookie
             ↓
        API Requests
             │
             ↓
       Verify Token
             │
             ↓
       Protected API

و وقتی Access Token منقضی شد:

API Request
     ↓
401 Unauthorized
     ↓
Refresh Endpoint
     ↓
Refresh Token
     ↓
New Access Token
     ↓
Retry Request

البته Client نباید هر 401 را کورکورانه Refresh کند؛ باید شرایط و خطاهای Authentication را از سایر خطاها تفکیک کند تا وارد چرخه‌های بی‌نهایت نشود.


یک نکته مهم درباره Logout

در یک سیستم JWT ساده، Logout همیشه به معنی «حذف JWT از سرور» نیست؛ چون Access Token ممکن است تا زمان Expiration معتبر باقی بماند.

برای کنترل بهتر Logout می‌توان معماری‌هایی مانند:

Short-lived Access Token
+
Revocable Refresh Token
+
Refresh Token Rotation

را به کار برد.

در این مدل، می‌توان Session یا Refresh Token را در Backend باطل کرد و از صدور Access Tokenهای جدید جلوگیری کرد.


JWT را به عنوان یک سیستم کامل ببینید

اشتباه رایج این است که Authentication را فقط این بدانیم:

Login
 ↓
JWT
 ↓
Done

در یک سیستم واقعی، چرخه کامل بیشتر شبیه این است:

Login
  ↓
Authentication
  ↓
Issue Tokens
  ↓
Access API
  ↓
Access Token Expiration
  ↓
Refresh
  ↓
Token Rotation
  ↓
Logout / Revoke

در کنار آن باید موارد دیگری مانند:

Authorization
Rate Limiting
CSRF Protection
XSS Protection
HTTPS
Secret Management
Session Management
Audit Logging

نیز متناسب با معماری در نظر گرفته شوند.


جمع‌بندی

JWT در اصل یک روش استاندارد برای انتقال Claimهای امضاشده است؛ اما چیزی که در معماری Authentication اهمیت دارد، نحوه استفاده از آن است.

یک معماری Token-based می‌تواند به شکل زیر باشد:

Login
  ↓
Access Token + Refresh Token
  ↓
Access Token → API
  ↓
Expire
  ↓
Refresh Token
  ↓
New Access Token

Access Token معمولاً برای دسترسی کوتاه‌مدت به API استفاده می‌شود و Refresh Token برای گرفتن Access Token جدید.

در مقابل، Session-based Authentication معمولاً Session را سمت سرور نگهداری می‌کند و Client یک Session Identifier دریافت می‌کند.

بنابراین سؤال اصلی این نیست که:

«JWT بهتر است یا Session؟»

بلکه باید پرسید:

«با توجه به معماری پروژه، سطح امنیت، نوع Clientها، نیاز به Revocation و نحوه مدیریت Session، کدام مدل مناسب‌تر است؟»

اگر JWT انتخاب شود، امنیت آن فقط به خود Token وابسته نیست؛ محل نگهداری Token، طول عمر، Rotation، Revocation، Cookie Configuration، HTTPS و نحوه پیاده‌سازی Authentication و Authorization همگی بخشی از سیستم امنیتی هستند.

خلاصه مفاهیم

JWT
→ ساختار Token

Access Token
→ دسترسی کوتاه‌مدت به API

Refresh Token
→ دریافت Access Token جدید

Session
→ نگهداری وضعیت Authentication سمت سرور

Authentication
→ تشخیص هویت کاربر

Authorization
→ تعیین سطح دسترسی کاربر

و مهم‌تر از همه:

JWT خودش یک سیستم Authentication کامل نیست؛ JWT یکی از اجزای یک معماری Authentication است.