سی ام اس نویسسی ام اس نویسیاد بگیر، سیستم خودتو بنویس
مجلهتماس با ما
سی ام اس نویسسی ام اس نویس

CMS نویس مرجعی برای آموزش برنامه نویسی وب و PHP، توسعه افزونه‌های اختصاصی وردپرس، طراحی ورک‌فلو و اتوماسیون با n8n و توسعه CMS های اختصاصی است. هدف ما ارائه آموزش‌های کاربردی و راهکارهای حرفه‌ای برای توسعه‌دهندگان، کسب‌وکارها و علاقه‌مندان به دنیای برنامه‌نویسی است.

پیوندهای سریع
محصولات فیزیکیمحصولات دیجیتالدوره‌های آموزشی
سی ام اس نویس
مجله سی ام اس نویستماس با ماپنل کاربری
سی ام اس نویس — تمامی حقوق برای سی ام اس نویس محفوظ است. © 1406 تماس با ما09307711941
Refresh Token در PHP چیست؟ پیاده‌سازی امن Token Rotation و Revocation

Refresh Token در PHP چیست؟ پیاده‌سازی امن Token Rotation و Revocation

ممدیر کل سیستم ۳۰ شهریور ۱۴۰۵ ۲۵ دقیقه مطالعه ۱ بازدید
Refresh Token در PHP چیست؟ پیاده‌سازی امن Token Rotation و Revocation

مقدمه

اگر در پروژه‌های PHP، API یا سیستم‌های مبتنی بر OAuth 2.0 کار کرده باشید، احتمالاً با دو مفهوم زیر برخورد کرده‌اید:


Access Token

Refresh Token

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

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

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

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

در این مقاله می‌خواهیم از صفر بررسی کنیم:

  • Refresh Token چیست؟
  • چه تفاوتی با Access Token دارد؟
  • چرا Access Token باید عمر کوتاهی داشته باشد؟
  • Refresh Token را کجا ذخیره کنیم؟
  • Token Rotation چیست؟
  • Token Revocation چیست؟
  • اگر Refresh Token سرقت شود چه اتفاقی می‌افتد؟
  • چگونه Refresh Token را در PHP به شکل امن پیاده‌سازی کنیم؟
  • چگونه Logout را با Revocation ترکیب کنیم؟
  • چگونه Token Family را مدیریت کنیم؟
  • چه زمانی JWT و چه زمانی Token تصادفی مناسب‌تر است؟

# 1. Access Token چیست؟

Access Token مجوزی است که Client با استفاده از آن به یک Resource Server یا API درخواست می‌فرستد.

مثلاً:


GET /api/profile

Authorization: Bearer ACCESS_TOKEN

سرور Token را بررسی می‌کند و اگر معتبر باشد، درخواست را پردازش می‌کند.

ساختار ساده:


User

 ↓

Login

 ↓

Access Token

 ↓

API Request

 ↓

Resource Server


# 2. چرا Access Token نباید عمر بسیار طولانی داشته باشد؟

فرض کنید Access Token شما یک سال اعتبار داشته باشد.

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

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

مثلاً:


Access Token

Expiration: 15 minutes

یا:


Access Token

Expiration: 1 hour

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

اما یک سؤال ایجاد می‌شود:

اگر Access Token منقضی شد، آیا کاربر باید دوباره Login کند؟

لزومی ندارد.

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


# 3. Refresh Token چیست؟

Refresh Token یک Credential است که Client می‌تواند با استفاده از آن Access Token جدید دریافت کند.

جریان کلی:


Login

  ↓

Access Token

+

Refresh Token

  ↓

Access Token expires

  ↓

Refresh Token

  ↓

New Access Token

بنابراین کاربر مجبور نیست برای هر Access Token جدید دوباره Username و Password خود را وارد کند.


# 4. تفاوت Access Token و Refresh Token

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

یک نکته مهم:

Refresh Token نباید در هر درخواست API ارسال شود.


# 5. یک سناریوی واقعی

فرض کنید کاربر وارد حساب خود شده است.

سرور دو Token صادر می‌کند:


{

    "access_token": "access-token-123",

    "refresh_token": "refresh-token-456",

    "expires_in": 900

}

Access Token:


15 دقیقه

اعتبار دارد.

بعد از 15 دقیقه:


Access Token

     ↓

Expired

Client به جای Login مجدد، Refresh Token را ارسال می‌کند:


POST /oauth/token

Content-Type: application/x-www-form-urlencoded



grant_type=refresh_token&

refresh_token=refresh-token-456

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

اگر معتبر باشد، Token جدید صادر می‌کند.


# 6. چرا Refresh Token حساس‌تر است؟

فرض کنید Access Token شما فقط 15 دقیقه اعتبار دارد.

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

اما Refresh Token ممکن است هفته‌ها یا ماه‌ها اعتبار داشته باشد.

بنابراین:


Refresh Token

      ↓

Long-lived Credential

      ↓

High Security Risk

به همین دلیل نگهداری و مدیریت آن بسیار مهم است.


# 7. Refresh Token را نباید مثل Password ذخیره کرد

یکی از رویکردهای مناسب این است که Refresh Token تصادفی تولید کنیم و نسخه Hash شده آن را در دیتابیس ذخیره کنیم.

مثلاً Token اصلی:


RANDOM_REFRESH_TOKEN

و در دیتابیس:


SHA256(RANDOM_REFRESH_TOKEN)

ذخیره شود.

مشابه رویکردی که برای Reset Token یا Email Verification Token استفاده می‌کنیم.


# 8. تولید Refresh Token امن

از random_bytes() استفاده کنید.

مثلاً:


$refreshToken = bin2hex(

    random_bytes(32)

);

این Token دارای 64 کاراکتر hexadecimal خواهد بود.

سپس Hash آن:


$refreshTokenHash = hash(

    'sha256',

    $refreshToken

);

را ذخیره می‌کنیم.


# 9. چرا از rand() استفاده نکنیم؟

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


$token = rand();

یا:


$token = mt_rand();

برای Credentialهای امنیتی باید از منبع تصادفی رمزنگاری‌شده استفاده کنیم:


random_bytes()


# 10. ساخت جدول Refresh Token

یک جدول ساده می‌تواند چنین ساختاری داشته باشد:


CREATE TABLE refresh_tokens (

    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

    user_id BIGINT UNSIGNED NOT NULL,

    token_hash CHAR(64) NOT NULL UNIQUE,

    expires_at DATETIME NOT NULL,

    revoked_at DATETIME NULL,

    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,



    INDEX idx_user_id (user_id),

    INDEX idx_expires_at (expires_at),



    FOREIGN KEY (user_id)

        REFERENCES users(id)

        ON DELETE CASCADE

);

این جدول اطلاعاتی مانند:


User

Token Hash

Expiration

Revocation

Creation Time

را نگه می‌دارد.


# 11. چرا Raw Token را در Database ذخیره نکنیم؟

فرض کنید دیتابیس شما لو برود.

اگر Refresh Token خام ذخیره شده باشد:


Database Leak

      ↓

Raw Refresh Tokens

      ↓

Potential Account Access

اما اگر فقط Hash ذخیره شده باشد:


Database Leak

      ↓

Token Hashes

مهاجم نمی‌تواند صرفاً با دیدن مقدار Hash، همان Token اصلی را مستقیماً به سرور ارائه کند.

البته Hash کردن جایگزین تمام اقدامات امنیتی دیگر نیست.


# 12. فرآیند صدور Refresh Token

بعد از Login:


$refreshToken = bin2hex(

    random_bytes(32)

);



$tokenHash = hash(

    'sha256',

    $refreshToken

);

سپس:


$stmt = $pdo->prepare(

    'INSERT INTO refresh_tokens

    (user_id, token_hash, expires_at)

    VALUES (?, ?, ?)'

);



$stmt->execute([

    $userId,

    $tokenHash,

    $expiresAt

]);

و Raw Token را فقط در پاسخ به Client قرار می‌دهیم:


echo json_encode([

    'access_token' => $accessToken,

    'refresh_token' => $refreshToken,

]);


# 13. Refresh Endpoint

فرض کنیم Endpoint زیر را داریم:


POST /oauth/token

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


grant_type=refresh_token

refresh_token=...

در PHP:


$refreshToken = $_POST['refresh_token'] ?? null;



if (!$refreshToken) {

    http_response_code(400);

    exit('Refresh token is required.');

}

سپس Hash را محاسبه می‌کنیم:


$tokenHash = hash(

    'sha256',

    $refreshToken

);


# 14. پیدا کردن Refresh Token

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


SELECT *

FROM refresh_tokens

WHERE token_hash = ?

LIMIT 1;

در PHP:


$stmt = $pdo->prepare(

    'SELECT *

     FROM refresh_tokens

     WHERE token_hash = ?

     LIMIT 1'

);



$stmt->execute([

    $tokenHash

]);



$tokenRecord = $stmt->fetch();

اگر Token وجود نداشته باشد:


if (!$tokenRecord) {

    http_response_code(401);

    exit('Invalid refresh token.');

}


# 15. بررسی Expiration

نباید فقط وجود Token را بررسی کنیم.

باید ببینیم منقضی نشده باشد.

مثلاً:


if (

    strtotime($tokenRecord['expires_at'])

    <= time()

) {

    http_response_code(401);

    exit('Refresh token expired.');

}


# 16. بررسی Revocation

همچنین باید بررسی کنیم که Token قبلاً Revoked نشده باشد.


if ($tokenRecord['revoked_at'] !== null) {

    http_response_code(401);

    exit('Refresh token revoked.');

}

پس Token معتبر باید:


Exists

+

Not Expired

+

Not Revoked

باشد.


# 17. Token Rotation چیست؟

یکی از مهم‌ترین مفاهیم امنیتی Refresh Token:

Refresh Token Rotation

است.

در روش ساده، Client بارها از همان Refresh Token استفاده می‌کند:


Refresh Token A

      ↓

Access Token B



Refresh Token A

      ↓

Access Token C



Refresh Token A

      ↓

Access Token D

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

در Rotation، هر بار که Refresh Token مصرف می‌شود:


Old Refresh Token

       ↓

Revoked

       ↓

New Refresh Token

تولید می‌شود.


# 18. Token Rotation چگونه کار می‌کند؟

فرض کنیم ابتدا داریم:


Refresh Token A

Client آن را استفاده می‌کند.

سرور:


A → Revoke

A → Generate B

پاسخ:


{

    "access_token": "new-access-token",

    "refresh_token": "refresh-token-B"

}

از این لحظه:


A = Invalid

B = Valid

اگر Client دوباره A را ارسال کند، باید رد شود.


# 19. مزیت Token Rotation

فرض کنید مهاجم Refresh Token A را سرقت کرده باشد.

کاربر واقعی قبل از مهاجم از A استفاده می‌کند:


A

 ↓

Valid

 ↓

Rotate

 ↓

B

حالا A دیگر معتبر نیست.

اگر مهاجم بعداً A را استفاده کند:


A

 ↓

Revoked

 ↓

Rejected

این موضوع می‌تواند پنجره سوءاستفاده از یک Refresh Token سرقت‌شده را کاهش دهد.


# 20. Refresh Token Family

برای سیستم‌های حرفه‌ای‌تر بهتر است Refresh Tokenها را در قالب یک Token Family مدیریت کنیم.

مثلاً:


Family: F1



Token A

   ↓

Token B

   ↓

Token C

   ↓

Token D

هر Refresh Token به یک Family تعلق دارد.

مثلاً جدول:


ALTER TABLE refresh_tokens

ADD COLUMN family_id CHAR(64) NOT NULL;


# 21. چرا Token Family مهم است؟

فرض کنید:


A

 ↓

B

 ↓

C

و Token B دزدیده شده باشد.

کاربر واقعی از B استفاده کرده و C دریافت کرده است.

مهاجم بعداً B را ارسال می‌کند.

این یک علامت مهم است:


Previously Used Token

        ↓

Reuse Detection

می‌توانیم این وضعیت را به‌عنوان احتمال Token Theft در نظر بگیریم و Family مربوطه را باطل کنیم.


# 22. Token Reuse Detection

یکی از قابلیت‌های مهم سیستم‌های حرفه‌ای:


Refresh Token Reuse Detection

است.

مثلاً:


A → B → C

اگر A قبلاً Rotate شده و دوباره استفاده شود:


A

↓

Already Revoked

↓

Reuse Detected

سرور می‌تواند تمام Tokenهای Family را Revoked کند.


Family F1

 ├── A → Revoked

 ├── B → Revoked

 └── C → Revoked

در نتیجه Sessionهای وابسته به آن Family نیاز به احراز هویت مجدد خواهند داشت.


# 23. طراحی کامل‌تر جدول Token

برای چنین سیستمی می‌توانیم جدول را توسعه دهیم:


CREATE TABLE refresh_tokens (

    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,



    user_id BIGINT UNSIGNED NOT NULL,



    family_id CHAR(64) NOT NULL,



    token_hash CHAR(64) NOT NULL UNIQUE,



    expires_at DATETIME NOT NULL,



    revoked_at DATETIME NULL,



    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,



    INDEX idx_user_id (user_id),

    INDEX idx_family_id (family_id),



    FOREIGN KEY (user_id)

        REFERENCES users(id)

        ON DELETE CASCADE

);


# 24. ایجاد Family

هنگام Login:


$familyId = bin2hex(

    random_bytes(32)

);

سپس Token را ایجاد می‌کنیم:


$refreshToken = bin2hex(

    random_bytes(32)

);

Hash:


$tokenHash = hash(

    'sha256',

    $refreshToken

);

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


$stmt = $pdo->prepare(

    'INSERT INTO refresh_tokens

    (

        user_id,

        family_id,

        token_hash,

        expires_at

    )

    VALUES (?, ?, ?, ?)'

);



$stmt->execute([

    $userId,

    $familyId,

    $tokenHash,

    $expiresAt

]);


# 25. Rotation به شکل Transaction

Rotation باید با دقت انجام شود.

بهتر است عملیات تغییر Token داخل Transaction انجام شود:


$pdo->beginTransaction();



try {



    // Revoke old token



    // Create new refresh token



    // Commit



    $pdo->commit();



} catch (Throwable $e) {



    $pdo->rollBack();



    throw $e;

}

چرا؟

چون نمی‌خواهیم وضعیت ناقصی ایجاد شود.

مثلاً:


Old Token Revoked

New Token Not Created

یا:


New Token Created

Old Token Still Valid


# 26. نمونه Rotation

ابتدا Token قبلی را با قفل مناسب پیدا می‌کنیم.

در MySQL می‌توانیم در یک Transaction از:


SELECT *

FROM refresh_tokens

WHERE token_hash = ?

FOR UPDATE;

استفاده کنیم.

بعد:


if ($tokenRecord['revoked_at'] !== null) {



    // Reuse detected



    $pdo->rollBack();



    http_response_code(401);



    exit('Invalid refresh token.');

}

سپس Token قدیمی را Revoked می‌کنیم:


UPDATE refresh_tokens

SET revoked_at = NOW()

WHERE id = ?

و Token جدید ایجاد می‌کنیم.


# 27. تولید Access Token جدید

بعد از اعتبارسنجی Refresh Token، باید Access Token جدید صادر کنیم.

برای مثال اگر از JWT استفاده می‌کنیم:


sub

iat

exp

iss

aud

scope

می‌توانند Claimهای آن باشند.

مثلاً:


{

    "sub": "123",

    "iat": 1760000000,

    "exp": 1760000900,

    "iss": "https://example.com",

    "aud": "api"

}

توجه کنید که ساخت و امضای JWT باید با الگوریتم مناسب و کتابخانه معتبر انجام شود.


# 28. Refresh Token لازم نیست JWT باشد

یک اشتباه رایج این است که تصور کنیم Refresh Token حتماً باید JWT باشد.

لزومی ندارد.

می‌توانیم Refresh Token را یک مقدار کاملاً تصادفی قرار دهیم:


random opaque token

مثلاً:


8e1c...

و فقط Hash آن را در دیتابیس ذخیره کنیم.

برای بسیاری از سیستم‌ها این روش ساده و قابل کنترل است.


# 29. Opaque Token چیست؟

Opaque Token یعنی Tokenای که اطلاعات قابل استفاده‌ای برای Client یا Resource Server داخل خودش ندارد.

مثلاً:


8a91c4f7...

سرور برای فهمیدن وضعیت Token باید آن را در دیتابیس یا Storage جستجو کند.

در مقابل JWT معمولاً اطلاعاتی مانند:


sub

iss

aud

exp

را داخل Payload دارد.


# 30. JWT Refresh Token یا Opaque Refresh Token؟

این انتخاب به معماری سیستم بستگی دارد.

JWT

مزایا:

  • قابل اعتبارسنجی بدون Lookup دائمی
  • مناسب برخی معماری‌های توزیع‌شده
  • امکان حمل Claims

معایب:

  • Revocation پیچیده‌تر
  • مدیریت Logout پیچیده‌تر
  • در صورت سرقت، کنترل آن دشوارتر

Opaque Token

مزایا:

  • کنترل کامل Server
  • Revocation ساده
  • Rotation ساده
  • ذخیره Hash در Database

معایب:

  • نیاز به Storage Lookup
  • نیاز به طراحی مناسب Database/Cache

برای Refresh Token، Opaque Random Token در بسیاری از طراحی‌ها انتخاب ساده‌ای است.


# 31. Refresh Token در Browser

محل ذخیره Refresh Token در Browser موضوع بسیار مهمی است.

قرار دادن Credential حساس در:


localStorage

می‌تواند در صورت وجود XSS، ریسک سرقت Token را افزایش دهد.

در معماری‌های Browser-based معمولاً باید Storage و Cookie را با توجه به مدل Threat سیستم انتخاب کرد.

اگر Refresh Token را در Cookie قرار می‌دهید، ویژگی‌هایی مانند:


HttpOnly

Secure

SameSite

اهمیت پیدا می‌کنند.

همچنین باید موضوع CSRF را نیز در طراحی در نظر بگیرید.


# 32. HttpOnly چیست؟

اگر Cookie دارای:


HttpOnly

باشد، JavaScript معمولی Browser نمی‌تواند آن را از طریق document.cookie بخواند.

مثلاً:


setcookie(

    'refresh_token',

    $refreshToken,

    [

        'expires' => time() + 2592000,

        'secure' => true,

        'httponly' => true,

        'samesite' => 'Lax',

    ]

);

اما HttpOnly به‌تنهایی همه حملات Web را حل نمی‌کند.


# 33. Secure Cookie

ویژگی:


Secure

باعث می‌شود Cookie فقط روی اتصال HTTPS ارسال شود.

در Production:


Secure = true

باید جدی گرفته شود.


# 34. SameSite

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

مقادیر رایج:


Strict

Lax

None

اگر SameSite=None استفاده شود، معمولاً Secure نیز لازم است.

انتخاب مقدار مناسب باید بر اساس معماری Login، Domainها و Cross-Site Flow انجام شود.


# 35. Logout و Revocation

Logout فقط نباید Session محلی را پاک کند.

اگر Refresh Token داریم، بهتر است Token مربوطه نیز Revoked شود.

مثلاً:


UPDATE refresh_tokens

SET revoked_at = NOW()

WHERE id = ?

AND user_id = ?;

در نتیجه:


Logout

 ↓

Destroy Local Session

 ↓

Revoke Refresh Token


# 36. Logout از همه دستگاه‌ها

فرض کنید کاربر روی سه دستگاه Login کرده است:


Laptop

Mobile

Tablet

و سه Refresh Token دارد:


Token A

Token B

Token C

اگر گزینه‌ای مانند:


خروج از همه دستگاه‌ها

داریم، می‌توانیم تمام Tokenهای کاربر را Revoked کنیم:


UPDATE refresh_tokens

SET revoked_at = NOW()

WHERE user_id = ?

AND revoked_at IS NULL;

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


# 37. Logout فقط از یک دستگاه

اگر کاربر فقط از Laptop خارج شود، بهتر است فقط Token مربوط به همان Device یا Session را Revoked کنیم.

به همین دلیل نگهداری Metadata مناسب برای Sessionها می‌تواند مفید باشد.

مثلاً:


device_name

created_at

last_used_at

ip_address

user_agent

البته ذخیره IP و User-Agent باید با نیاز واقعی سیستم و الزامات حریم خصوصی شما هماهنگ باشد.


# 38. Expiration برای Refresh Token

Refresh Token نباید تا ابد معتبر باشد.

مثلاً:


Refresh Token

Expiration:

30 days

یا بر اساس نیاز سیستم:


7 days

30 days

60 days

90 days

مقدار مناسب به مدل امنیتی و تجربه کاربری پروژه بستگی دارد.


# 39. Sliding Sessions

در بعضی سیستم‌ها بعد از هر Refresh موفق، تاریخ انقضای Session تغییر می‌کند.

مثلاً:


Initial:

30 days



Refresh:

+30 days

به این مدل گاهی Sliding Session گفته می‌شود.

اما باید مراقب باشیم که Session عملاً بدون استفاده طولانی‌مدت هیچ‌وقت منقضی نشود.


# 40. Absolute Expiration

یک راهکار بهتر در برخی معماری‌ها این است که علاوه بر عمر هر Refresh Token، برای کل Session یک حداکثر عمر تعیین کنیم.

مثلاً:


Token lifetime:

7 days



Absolute session lifetime:

90 days

در نتیجه حتی اگر کاربر مرتب Refresh کند، بعد از 90 روز نیاز به احراز هویت مجدد خواهد داشت.


# 41. Rate Limiting برای Refresh Endpoint

Endpoint مربوط به Refresh نیز باید Rate Limit داشته باشد.

مثلاً:


POST /oauth/token

نباید بدون محدودیت در دسترس باشد.

می‌توانیم محدودیت‌هایی مانند:


Per IP

Per Client

Per Token Family

داشته باشیم.

اگر تعداد درخواست‌های ناموفق زیاد شد:


429 Too Many Requests

می‌تواند پاسخ مناسبی باشد.


# 42. Logging

برای بررسی مشکلات امنیتی باید Eventهای مهم را Log کنیم.

مثلاً:


refresh_success

refresh_failed

refresh_expired

refresh_revoked

refresh_reuse_detected

logout

اما نباید Raw Refresh Token را Log کنیم.

غلط:


Refresh token: abc123...

درست‌تر:


Refresh attempt failed

user_id=123

family_id=...

reason=expired

اطلاعات Log باید حداقل لازم را داشته باشد.


# 43. Refresh Token Reuse یک Event امنیتی مهم است

اگر Token Rotation فعال باشد و یک Refresh Token قبلاً استفاده‌شده دوباره ارائه شود، این وضعیت می‌تواند نشان‌دهنده استفاده هم‌زمان از یک Credential قدیمی باشد.

مثلاً:


User:

A → B



Attacker:

A → ?

در این حالت می‌توان Family را بررسی و در صورت طراحی سیستم، کل Family را باطل کرد.

این رفتار باید به‌صورت مشخص در معماری سیستم تعریف شود و صرفاً به‌عنوان یک خطای معمولی نادیده گرفته نشود.


# 44. معماری پیشنهادی کامل

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


                    ┌──────────────┐

                    │     User     │

                    └──────┬───────┘

                           │

                         Login

                           │

                           ▼

                    ┌──────────────┐

                    │ Auth Server  │

                    └──────┬───────┘

                           │

                 Access + Refresh Token

                           │

                           ▼

                    ┌──────────────┐

                    │    Client    │

                    └──────┬───────┘

                           │

                     API Request

                           │

                           ▼

                    ┌──────────────┐

                    │ Resource API │

                    └──────────────┘



Access Token Expired

          │

          ▼

   Refresh Endpoint

          │

          ▼

 Validate Refresh Token

          │

          ├── Invalid ──► 401

          │

          ▼

   Revoke Old Token

          │

          ▼

 Generate New Token

          │

          ▼

 Generate New Refresh Token

          │

          ▼

      Response


# 45. نمونه پاسخ Refresh موفق

می‌توانیم پاسخ را به شکل زیر طراحی کنیم:


{

    "token_type": "Bearer",

    "access_token": "new-access-token",

    "expires_in": 900,

    "refresh_token": "new-refresh-token"

}

Client باید Refresh Token جدید را جایگزین Token قبلی کند.


# 46. نمونه Endpoint

در ساده‌ترین حالت:


POST /oauth/token

Body:


grant_type=refresh_token

refresh_token=...

پاسخ موفق:


HTTP/1.1 200 OK

Content-Type: application/json


{

    "token_type": "Bearer",

    "access_token": "...",

    "expires_in": 900,

    "refresh_token": "..."

}

پاسخ Token نامعتبر:


HTTP/1.1 401 Unauthorized


# 47. ترتیب منطقی اعتبارسنجی

در Endpoint Refresh بهتر است فرآیند را به مراحل مشخص تقسیم کنیم:


1. دریافت Request

2. اعتبارسنجی grant_type

3. دریافت Refresh Token

4. Hash کردن Token

5. پیدا کردن Record

6. بررسی Revocation

7. بررسی Expiration

8. بررسی User

9. بررسی Client

10. بررسی Token Family

11. Rotation

12. صدور Access Token جدید

13. صدور Refresh Token جدید

14. ثبت Event

15. ارسال Response

این تفکیک باعث می‌شود Debug و نگهداری سیستم ساده‌تر شود.


# 48. Refresh Token و OAuth 2.0

در OAuth 2.0، Refresh Token یک نوع Credential برای دریافت Access Token جدید است.

اما نحوه دقیق صدور، نگهداری، Rotation و Revocation به معماری Authorization Server و Client بستگی دارد.

بنابراین نباید یک پیاده‌سازی ساده را بدون بررسی Threat Model به تمام پروژه‌ها تعمیم دهیم.


# 49. Refresh Token و OpenID Connect

در OpenID Connect نیز ممکن است در بعضی Flowها Refresh Token دریافت شود.

اما نقش آن همچنان مربوط به دریافت Token جدید است.

ID Token و Refresh Token را نباید با یکدیگر اشتباه گرفت:


ID Token

→ اطلاعات هویت



Access Token

→ دسترسی به Resource/API



Refresh Token

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


# 50. آیا Refresh Token باید در JWT باشد؟

خیر.

یک Refresh Token تصادفی و Opaque در بسیاری از سیستم‌ها انتخاب مناسبی است.

مثلاً:


$refreshToken = bin2hex(

    random_bytes(32)

);

و:


$hash = hash(

    'sha256',

    $refreshToken

);

در دیتابیس ذخیره می‌شود.

این روش به Server اجازه می‌دهد به‌صورت مستقیم وضعیت Token را کنترل کند.


# 51. نکته مهم درباره Hash کردن Token

Refresh Token معمولاً مقدار تصادفی و با Entropy بالا است.

بنابراین Hash کردن SHA-256 برای ذخیره Token تصادفی یک روش رایج است.

این موضوع با Password متفاوت است.

برای Password معمولاً از:


password_hash()

و الگوریتم‌های مخصوص Password Hashing استفاده می‌کنیم.

برای یک Token تصادفی پرEntropy، SHA-256 می‌تواند برای ذخیره یک اثر انگشت Token استفاده شود.


# 52. پاک‌سازی Tokenهای قدیمی

اگر سیستم مدت زیادی کار کند، جدول Refresh Token می‌تواند بزرگ شود.

پس باید Job یا Cron برای پاک‌سازی Tokenهای قدیمی داشته باشیم.

مثلاً:


DELETE FROM refresh_tokens

WHERE expires_at < NOW()

AND revoked_at IS NOT NULL;

همچنین باید سیاست نگهداری Logها و Eventهای امنیتی را جداگانه مشخص کنیم.


# 53. ایندکس‌های دیتابیس

برای سیستم‌های بزرگ، Index مناسب مهم است.

مثلاً:


CREATE INDEX idx_refresh_token_user

ON refresh_tokens(user_id);

و:


CREATE INDEX idx_refresh_token_family

ON refresh_tokens(family_id);

همچنین:


UNIQUE(token_hash)

باعث می‌شود Token Hash تکراری ثبت نشود.


# 54. Race Condition در Rotation

یکی از نکات پیشرفته، Race Condition است.

فرض کنید Client به دلیل Retry دو درخواست Refresh تقریباً هم‌زمان ارسال کند:


Request A ──┐

            ├── Refresh Token X

Request B ──┘

اگر Transaction و Lock مناسب نداشته باشیم، ممکن است هر دو درخواست Token X را معتبر ببینند.

بنابراین Rotation باید با Transaction و در صورت نیاز Row Locking طراحی شود.

مثلاً:


SELECT *

FROM refresh_tokens

WHERE token_hash = ?

FOR UPDATE;

و سپس Revocation و ایجاد Token جدید انجام شود.


# 55. معماری حرفه‌ای‌تر

در یک سیستم بزرگ‌تر می‌توانیم Service جداگانه داشته باشیم:


AuthService

RefreshTokenService

TokenService

SessionService

UserIdentityService

مثلاً:


$tokens = $refreshTokenService->rotate(

    $refreshToken

);

و Service مسئول موارد زیر باشد:


Validate

Lock

Revoke

Detect Reuse

Create New Token

Create Access Token

این ساختار از قرار دادن تمام منطق Authentication داخل Controller جلوگیری می‌کند.


# 56. Refresh Token در Laravel

اگر همین معماری را در Laravel پیاده کنیم، می‌توانیم مدل‌هایی مانند:


User

RefreshToken

داشته باشیم.

و Service:


class RefreshTokenService

{

    public function rotate(string $token)

    {

        // Validate

        // Revoke

        // Generate

        // Return

    }

}

همچنین می‌توانیم از:


Database Transactions

Queues

Cache

Rate Limiting

Events

برای ساخت سیستم حرفه‌ای‌تر استفاده کنیم.


# 57. چک‌لیست امنیت Refresh Token

قبل از Production این موارد را بررسی کنید:


☑ Refresh Token تصادفی و Cryptographically Secure است

☑ Raw Token در Database ذخیره نمی‌شود

☑ Token دارای Expiration است

☑ Token قابلیت Revocation دارد

☑ Token Rotation فعال است

☑ Reuse Detection در نظر گرفته شده

☑ Token Family در معماری‌های حساس در نظر گرفته شده

☑ Access Token عمر کوتاه‌تری دارد

☑ Refresh Endpoint Rate Limit دارد

☑ HTTPS فعال است

☑ Refresh Token در Log ذخیره نمی‌شود

☑ Session Cookie امن است

☑ XSS و CSRF در معماری Browser بررسی شده‌اند

☑ Logout باعث Revocation می‌شود

☑ Logout All قابل پیاده‌سازی است

☑ Race Condition در Rotation بررسی شده

☑ Database Transaction استفاده شده

☑ Tokenهای منقضی پاک‌سازی می‌شوند

☑ Client و User در صورت نیاز اعتبارسنجی می‌شوند


# 58. اشتباهات رایج

اشتباه ۱: عمر بسیار طولانی Access Token


Access Token = 1 year

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


اشتباه ۲: استفاده دائمی از یک Refresh Token

اگر هر بار همان Token را استفاده کنیم:


Refresh Token A

↓

A

↓

A

↓

A

کنترل سرقت و Reuse دشوارتر می‌شود.


اشتباه ۳: ذخیره Raw Token


refresh_tokens.token = RAW_TOKEN

رویکرد مناسبی برای Storage امن نیست.


اشتباه ۴: قرار دادن Refresh Token در URL

مثلاً:


https://example.com/refresh?token=...

Credential حساس نباید در URL قرار بگیرد، چون URL ممکن است در History، Logها یا سایر مکانیزم‌ها ثبت شود.


اشتباه ۵: نبود Revocation

اگر Token را نتوانیم باطل کنیم، Logout و پاسخ به برخی رخدادهای امنیتی دشوارتر می‌شود.


اشتباه ۶: نبود Rate Limiting

Refresh Endpoint نیز یک Endpoint امنیتی است و نباید بدون محدودیت رها شود.


# 59. پرسش‌های متداول

آیا Refresh Token از Access Token مهم‌تر است؟

از نظر مدیریت امنیتی، Refresh Token معمولاً حساسیت بسیار بالایی دارد، چون می‌تواند برای دریافت Access Tokenهای جدید استفاده شود.


آیا Refresh Token باید JWT باشد؟

خیر.

می‌تواند یک Token تصادفی و Opaque باشد.


آیا Refresh Token را می‌توان در localStorage ذخیره کرد؟

از نظر فنی امکان‌پذیر است، اما در Applicationهای Browser-based باید ریسک XSS و سایر Threatها را در نظر گرفت. برای Credentialهای حساس، انتخاب Storage باید بخشی از طراحی امنیتی سیستم باشد.


آیا با Logout باید Refresh Token را حذف کنیم؟

بهتر است آن را Revoked کنیم یا وضعیت غیرقابل‌استفاده برای آن ثبت کنیم. حذف فیزیکی نیز در بعضی معماری‌ها ممکن است، اما داشتن سابقه Revocation می‌تواند برای Audit و تشخیص Reuse مفید باشد.


آیا Refresh Token باید یک سال اعتبار داشته باشد؟

عدد ثابت و مناسبی برای همه پروژه‌ها وجود ندارد. مدت اعتبار باید بر اساس نوع Application، سطح ریسک، تجربه کاربری و سیاست امنیتی تعیین شود.


آیا Rotation اجباری است؟

برای همه معماری‌ها نمی‌توان یک پاسخ یکسان داد، اما Rotation و Reuse Detection می‌توانند در کاهش ریسک استفاده مجدد از Refresh Tokenهای سرقت‌شده نقش مهمی داشته باشند.


# جمع‌بندی

Refresh Token یکی از بخش‌های مهم سیستم‌های Authentication مبتنی بر OAuth 2.0 است.

معماری پایه:


Login

 ↓

Access Token + Refresh Token

 ↓

Access Token Expired

 ↓

Refresh Token

 ↓

Validate

 ↓

Rotate

 ↓

New Access Token

+

New Refresh Token

اما یک سیستم Production نباید فقط به صدور Token جدید فکر کند.

مواردی مانند:


Token Rotation

Token Revocation

Token Family

Reuse Detection

Expiration

Rate Limiting

Secure Storage

Transaction

Race Condition

Session Security

همگی باید در طراحی نهایی در نظر گرفته شوند.

یکی از مهم‌ترین اصول این است:

> Refresh Token را مانند یک Credential بسیار حساس در نظر بگیرید، نه یک رشته ساده برای نگهداری در دیتابیس.

اگر این معماری درست طراحی شود، می‌توانیم Access Tokenهای کوتاه‌عمر داشته باشیم و در عین حال کاربر را مجبور نکنیم مرتباً Login کند.

مقاله بعدی

بعد از یادگیری Refresh Token، مرحله بعدی می‌تواند ساخت یک OAuth 2.0 Authorization Server در PHP باشد؛ یعنی به‌جای اینکه PHP فقط به Google یا یک Provider خارجی متصل شود، خودمان یک Server بسازیم که Clientها بتوانند از آن Authorization Code، Access Token و Refresh Token دریافت کنند.

این موضوع می‌تواند یکی از مهم‌ترین مقالات بخش OAuth و API Security در PHP باشد و مباحثی مثل Client، Scope، Authorization Code، PKCE، Token Endpoint، Consent و Revocation را در یک پروژه واقعی کنار هم قرار دهد.

نظرات (0)

نظرات پس از تأیید مدیر نمایش داده می‌شوند

هنوز نظری ثبت نشده است؛ اولین نفر باشید!

مقالات مرتبط

ساخت CMS با Laravel 12؛ آموزش ساخت سیستم مدیریت محتوا از صفر

ساخت CMS با Laravel 12؛ آموزش ساخت سیستم مدیریت محتوا از صفر

۲۴ دقیقه
ساخت فروشگاه اینترنتی با Laravel 12؛ آموزش پروژه‌محور از صفر تا طراحی هسته فروشگاه

ساخت فروشگاه اینترنتی با Laravel 12؛ آموزش پروژه‌محور از صفر تا طراحی هسته فروشگاه

۲۲ دقیقه
ساخت Marketplace با Laravel؛ آموزش طراحی فروشگاه چندفروشندگی

ساخت Marketplace با Laravel؛ آموزش طراحی فروشگاه چندفروشندگی

۲۲ دقیقه
مدیریت Queueهای سنگین در Laravel؛ آموزش پردازش کارهای طولانی

مدیریت Queueهای سنگین در Laravel؛ آموزش پردازش کارهای طولانی

۲۲ دقیقه