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

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

پیوندهای سریع
محصولات فیزیکیمحصولات دیجیتالدوره‌های آموزشی
سی ام اس نویس
مجله سی ام اس نویستماس با ماپنل کاربری
سی ام اس نویس — تمامی حقوق برای سی ام اس نویس محفوظ است. © 1406 تماس با ما09307711941
امنیت Logout در OpenID Connect با PHP؛ مدیریت چند Session، Revocation و جلوگیری از Logout ناقص

امنیت Logout در OpenID Connect با PHP؛ مدیریت چند Session، Revocation و جلوگیری از Logout ناقص

ممدیر کل سیستم ۴ مهر ۱۴۰۵ ۲۲ دقیقه مطالعه ۱ بازدید
امنیت Logout در OpenID Connect با PHP؛ مدیریت چند Session، Revocation و جلوگیری از Logout ناقص

مقدمه

در یک سیستم ساده، Logout معمولاً با چند خط کد انجام می‌شود:


session_destroy();

اما در یک معماری مبتنی بر OpenID Connect و SSO، این کار به‌تنهایی کافی نیست.

ممکن است یک کاربر هم‌زمان در چند دستگاه و چند اپلیکیشن Session داشته باشد:


Laptop

 ├── Shop Session

 ├── Panel Session

 └── Portal Session



Mobile

 ├── Shop Session

 └── Portal Session



Tablet

 └── Shop Session

در چنین شرایطی اگر کاربر از یک اپلیکیشن Logout کند، باید دقیقاً مشخص باشد:

  • کدام Session باید بسته شود؟
  • آیا Session مربوط به Identity Provider نیز باید بسته شود؟
  • آیا Refresh Token باید Revoked شود؟
  • آیا Access Token هنوز معتبر است؟
  • آیا Sessionهای دستگاه‌های دیگر باید باقی بمانند؟
  • اگر Logout Token دوباره ارسال شد چه اتفاقی می‌افتد؟
  • اگر Session ID قبلی دوباره استفاده شود چه؟
  • اگر Cookie هنوز در Browser وجود داشته باشد چه؟
  • اگر یکی از اپلیکیشن‌ها Logout را دریافت نکند چه؟

بنابراین Logout در یک معماری SSO یک عملیات ساده نیست؛ بلکه بخشی از مدیریت چرخه حیات Session و Token است.

در این مقاله یک معماری امن برای Logout در PHP طراحی می‌کنیم و موضوعاتی مانند Session Fixation، Cookie Security، Multi-Session، Session Revocation، Replay Protection و Token Revocation را بررسی خواهیم کرد.


# چرا Logout از Login ساده‌تر نیست؟

در نگاه اول ممکن است تصور کنیم Login سخت‌ترین قسمت Authentication است.

اما در یک معماری چنداپلیکیشنی، Logout نیز پیچیدگی خاص خود را دارد.

فرض کنید کاربر با شناسه:


user_id = 25

در سیستم وارد شده است.

سه Session دارد:


Session A → Laptop

Session B → Mobile

Session C → Tablet

و هر Session نیز به یک Session در Identity Provider متصل است:


A → OP SID A

B → OP SID B

C → OP SID C

اگر کاربر از Laptop Logout کند، آیا باید Session موبایل و Tablet هم بسته شوند؟

پاسخ به این سؤال به نوع Logout و سیاست امنیتی سیستم بستگی دارد.

این موضوع باید از قبل در معماری مشخص شده باشد.


# Multi-Session چیست؟

Multi-Session یعنی یک کاربر بتواند هم‌زمان چند Session فعال داشته باشد.

مثلاً:


User 25



Session 1

Device: Laptop

IP: ...

Browser: Chrome



Session 2

Device: Mobile

IP: ...

Browser: Android



Session 3

Device: Tablet

IP: ...

Browser: Safari

این مدل برای اکثر سرویس‌های مدرن طبیعی است.

مشکل زمانی ایجاد می‌شود که Sessionها بدون طراحی مناسب مدیریت شوند.


# چرا یک Session ID برای همه دستگاه‌ها مناسب نیست؟

فرض کنید برای یک کاربر فقط یک Session ID داشته باشیم:


user_id = 25

session_id = ABC123

اگر کاربر روی موبایل Login کند، ممکن است Session قبلی جایگزین شود.

در نتیجه:


Laptop → Logout

ممکن است روی:


Mobile

هم اثر بگذارد.

برای Multi-Device Authentication بهتر است هر Login یک Session مستقل ایجاد کند.


# طراحی Session مستقل

ساختار مناسب‌تر:


User 25

│

├── Session A

│   └── Laptop

│

├── Session B

│   └── Mobile

│

└── Session C

    └── Tablet

در Database:


CREATE TABLE user_sessions (

    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

    user_id BIGINT UNSIGNED NOT NULL,

    session_id_hash CHAR(64) NOT NULL,

    device_name VARCHAR(255) NULL,

    user_agent TEXT NULL,

    ip_address VARCHAR(45) NULL,

    created_at TIMESTAMP NULL,

    last_seen_at TIMESTAMP NULL,

    revoked_at TIMESTAMP NULL

);


# چرا Session ID را Hash می‌کنیم؟

Session ID یک Credential حساس است.

اگر مقدار خام Session ID در Database ذخیره شود و Database لو برود، مهاجم ممکن است بتواند از آن سوءاستفاده کند.

به همین دلیل می‌توان آن را Hash کرد:


$sessionHash = hash(

    'sha256',

    $sessionId

);

و فقط مقدار Hash شده را ذخیره کرد.


# Session در OIDC

در یک سیستم OIDC، بهتر است Session محلی را به اطلاعات مربوط به Identity Provider نیز متصل کنیم.

مثلاً:


CREATE TABLE oidc_sessions (

    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

    user_id BIGINT UNSIGNED NOT NULL,

    client_id VARCHAR(255) NOT NULL,

    op_sid VARCHAR(255) NULL,

    local_session_id_hash CHAR(64) NOT NULL,

    created_at TIMESTAMP NULL,

    last_seen_at TIMESTAMP NULL,

    revoked_at TIMESTAMP NULL

);

رابطه:


User

 ↓

Local Session

 ↓

OIDC Client

 ↓

OP Session

این Mapping در Logout بسیار مهم است.


# Session Fixation چیست؟

Session Fixation یکی از حملات مهم مرتبط با Session است.

در این حمله مهاجم تلاش می‌کند Session ID مشخصی را قبل از Authentication کاربر به او تحمیل کند.

اگر بعد از Login همان Session ID حفظ شود، مهاجم ممکن است بتواند آن Session را دنبال کند.


# جلوگیری از Session Fixation در PHP

یکی از اقدامات مهم این است که بعد از Authentication موفق، Session ID را تغییر دهیم.

در PHP:


session_regenerate_id(true);

مثلاً:


if ($authenticated) {



    session_regenerate_id(true);



    $_SESSION['user_id'] = $userId;

}

این کار باید در زمان مناسب و به‌عنوان بخشی از فرآیند Authentication انجام شود.


# چرا session_regenerate_id مهم است؟

قبل از Login:


Session ID = ABC

بعد از Login:


Session ID = XYZ

در نتیجه Session قبلی دیگر همان Session معتبر Authentication شده نیست.


# Logout امن فقط session_destroy نیست

کد زیر:


session_destroy();

به‌تنهایی یک Logout کامل را تضمین نمی‌کند.

چون ممکن است موارد دیگری همچنان فعال باشند:


Local Session

Access Token

Refresh Token

OIDC Session

Remember Me Cookie

Application Session

بنابراین Logout باید به شکل یک فرآیند کامل طراحی شود.


# چرخه کامل Logout

یک Logout امن می‌تواند شامل مراحل زیر باشد:


Logout Request

      ↓

Identify Session

      ↓

Revoke Local Session

      ↓

Delete Session Cookie

      ↓

Revoke Refresh Token

      ↓

Invalidate Remember-Me

      ↓

Notify Identity Provider

      ↓

Audit Event

البته تمام این مراحل در همه معماری‌ها الزامی نیستند و باید با مدل Session و Token سیستم هماهنگ شوند.


# حذف Session محلی

ابتدا Session محلی را باطل می‌کنیم.

مثلاً:


session_start();



$_SESSION = [];



session_regenerate_id(true);



session_destroy();

نحوه دقیق پاک‌سازی باید با Session Handler پروژه هماهنگ باشد.


# حذف Cookie مربوط به Session

اگر Session از Cookie استفاده می‌کند، بهتر است Cookie نیز منقضی شود.

مثلاً:


$params = session_get_cookie_params();



setcookie(

    session_name(),

    '',

    time() - 3600,

    $params['path'],

    $params['domain'],

    $params['secure'],

    $params['httponly']

);

در پروژه‌های جدید بهتر است تنظیمات Cookie به‌صورت صریح و امن مدیریت شوند.


# Secure و HttpOnly

Cookie احراز هویت باید تا حد امکان ویژگی‌های امنیتی مناسب داشته باشد.

مثلاً:


Set-Cookie:

SESSION_ID=abc;

Secure;

HttpOnly;

SameSite=Lax

Secure

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

HttpOnly

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

SameSite

رفتار Cookie در Contextهای Cross-Site را کنترل می‌کند.


# آیا HttpOnly جلوی تمام حملات را می‌گیرد؟

خیر.

HttpOnly باعث می‌شود JavaScript نتواند Cookie را مستقیماً بخواند، اما اگر XSS وجود داشته باشد، مهاجم همچنان ممکن است بتواند درخواست‌هایی را از Browser کاربر ارسال کند.

بنابراین:


HttpOnly

جایگزین:


XSS Protection

CSRF Protection

Content Security Policy

نیست.


# Logout و CSRF

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


POST /logout

اگر Logout عملیاتی باشد که فقط کاربر خودش باید بتواند انجام دهد، می‌توان آن را مانند سایر عملیات حساس Browser-based در برابر CSRF محافظت کرد.

مثلاً:


<input

    type="hidden"

    name="_token"

    value="...">

در Laravel این کار معمولاً توسط Middlewareهای CSRF مدیریت می‌شود.

در PHP خام نیز باید مکانیزم مناسب خودمان را طراحی کنیم.


# آیا GET برای Logout مناسب است؟

می‌توان Endpointهای Logout را به روش‌های مختلف طراحی کرد، اما از نظر امنیتی باید تفاوت بین عملیات Browser-based و Notificationهای OIDC را در نظر گرفت.

مثلاً:


POST /logout

برای Logout فعال توسط کاربر می‌تواند مناسب باشد.

اما Front-Channel Logout مکانیزم متفاوتی دارد و Endpoint آن باید مطابق پروتکل و Registration مربوطه طراحی شود.

همچنین Back-Channel Logout نیز معمولاً از POST Server-to-Server استفاده می‌کند.

بنابراین نباید یک Endpoint واحد برای همه انواع Logout طراحی کنیم.


# Session Revocation چیست؟

Revocation یعنی Session را از نظر منطقی غیرفعال کنیم.

مثلاً:


UPDATE oidc_sessions

SET revoked_at = NOW()

WHERE id = ?;

بعد در هر Request بررسی می‌کنیم:


if ($session->revoked_at !== null) {

    // Session is no longer valid

}

این روش مخصوصاً زمانی مفید است که Sessionها در Database یا یک Session Store مرکزی مدیریت می‌شوند.


# چرا فقط حذف Session کافی نیست؟

فرض کنید Session حذف شد، اما Refresh Token همچنان فعال است.

کاربر یا کلاینت ممکن است بتواند Access Token جدید دریافت کند.

در نتیجه Logout ناقص خواهد بود.

پس اگر سیستم از Refresh Token استفاده می‌کند، سیاست Token Revocation نیز باید در Logout در نظر گرفته شود.


# Logout و Refresh Token

فرض کنید کاربر این Tokenها را دارد:


Access Token

Refresh Token

Access Token ممکن است کوتاه‌عمر باشد:


10 minutes

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

اگر فقط Session محلی حذف شود:


Local Session → Revoked

Refresh Token → Active

ممکن است هنوز امکان دریافت Token جدید وجود داشته باشد.


# Revocation در Logout

می‌توان Refresh Token مربوط به Session را Revoked کرد:


UPDATE oauth_refresh_tokens

SET revoked_at = NOW()

WHERE session_id = ?;

به این ترتیب:


Session

  ↓

Revoke

  ↓

Refresh Token

  ↓

Revoke


# Token Family

اگر Refresh Token Rotation دارید، بهتر است Tokenها را به یک Family مرتبط کنید.

مثلاً:


Family A

 ├── Refresh Token 1

 ├── Refresh Token 2

 └── Refresh Token 3

اگر Session Logout شود:


Family A → Revoked

این کار مدیریت Token Lifecycle را ساده‌تر می‌کند.


# Replay Attack در Logout

فرض کنید Logout Token دارای:


jti = logout-123

باشد.

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

اگر Endpoint ما هر بار آن را پردازش کند، ممکن است مشکلاتی ایجاد شود.

به همین دلیل باید Tokenهای پردازش‌شده قابل شناسایی باشند.


# جدول Logout Event

مثلاً:


CREATE TABLE oidc_logout_events (

    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

    jti VARCHAR(255) NOT NULL,

    client_id VARCHAR(255) NOT NULL,

    processed_at TIMESTAMP NOT NULL,

    UNIQUE KEY unique_logout_event (jti, client_id)

);

سپس:


$exists = $db->fetchOne(

    'SELECT id

     FROM oidc_logout_events

     WHERE jti = ?

     AND client_id = ?',

    [$jti, $clientId]

);

اگر وجود داشت، Token قبلاً پردازش شده است.


# Idempotent Logout

Logout باید تا حد امکان Idempotent باشد.

یعنی:


Logout

Logout

Logout

نباید باعث خراب شدن وضعیت Session شود.

برای مثال:


Active

 ↓

Revoked

 ↓

Revoked

 ↓

Revoked

نه:


Active

 ↓

Deleted

 ↓

Error

 ↓

500

این موضوع مخصوصاً در سیستم‌های توزیع‌شده و Retryهای شبکه اهمیت زیادی دارد.


# مدیریت Logout در چند دستگاه

فرض کنید:


User 25



Laptop → Session A

Mobile → Session B

Tablet → Session C

اگر کاربر فقط از Laptop Logout کند:


Session A → Revoked

Session B → Active

Session C → Active

این یک مدل کاملاً معتبر است.

اما ممکن است سیستم امنیتی دیگری داشته باشیم که گزینه:


Logout from all devices

را ارائه کند.

در آن حالت:


Session A → Revoked

Session B → Revoked

Session C → Revoked

می‌شود.

این دو عملیات نباید با یکدیگر اشتباه گرفته شوند.


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

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


UPDATE oidc_sessions

SET revoked_at = NOW()

WHERE user_id = ?

AND revoked_at IS NULL;

و سپس Tokenهای مرتبط را نیز Revoked کرد.


# Logout یک دستگاه خاص

برای یک Session مشخص:


UPDATE oidc_sessions

SET revoked_at = NOW()

WHERE id = ?

AND user_id = ?;

وجود user_id در شرط مهم است.

چون نباید کاربر بتواند Session شخص دیگری را با تغییر یک ID ببندد.


# Session Enumeration

فرض کنید URL این باشد:


/logout/session/123

اگر فقط ID را بگیریم:


$session = Session::find(

    $_GET['id']

);

ممکن است کاربر بتواند Session متعلق به کاربر دیگری را هدف قرار دهد.

باید مالکیت را نیز بررسی کنیم:


$session = Session::where('id', $id)

    ->where('user_id', $currentUserId)

    ->firstOrFail();


# Logout Token و اعتبارسنجی

در Back-Channel Logout، هرگز نباید فقط Payload را Decode کنیم.

مراحل صحیح:


Receive Token

     ↓

Parse Header

     ↓

Find kid

     ↓

Load JWKS

     ↓

Verify Signature

     ↓

Validate iss

     ↓

Validate aud

     ↓

Validate events

     ↓

Validate sid/sub

     ↓

Check jti

     ↓

Revoke Session

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


# Key Rotation و Logout Token

اگر Identity Provider از RSA برای امضای JWT استفاده می‌کند، ممکن است کلیدها Rotation شوند.

بنابراین RP باید بتواند kid جدید را از JWKS دریافت کند.

مثلاً:


JWT Header



{

    "alg": "RS256",

    "kid": "key-2026-09"

}

سپس:


JWKS

 ↓

Find kid

 ↓

Public Key

 ↓

Verify Signature

اگر قبلاً مقاله JWT Key Rotation و JWKS را مطالعه کرده باشید، اینجا همان معماری در اعتبارسنجی Logout Token نیز کاربرد دارد.


# Logout و Session Fixation

Logout نیز باید Session را در وضعیت مشخصی قرار دهد.

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


Authenticated Session

        ↓

Logout

        ↓

Revoke

        ↓

Destroy

        ↓

New Anonymous Session

به‌خصوص نباید Session قبلی Authentication شده بعد از Logout دوباره به شکل غیرمنتظره مورد استفاده قرار گیرد.


# Remember Me و Logout

یکی از مواردی که معمولاً فراموش می‌شود:


Remember Me

فرض کنید Session حذف شده، اما Cookie مربوط به Remember Me همچنان معتبر است.

کاربر ممکن است دوباره بدون Login کامل وارد شود.

پس Logout باید سیاست مشخصی برای Remember-Me Token داشته باشد.

مثلاً:


UPDATE remember_tokens

SET revoked_at = NOW()

WHERE user_id = ?;

یا در حالت Logout یک دستگاه خاص، فقط Token همان Session را باطل کند.


# Logout و Password Change

در بسیاری از سیستم‌های حساس، تغییر Password می‌تواند باعث Logout Sessionهای دیگر شود.

مثلاً:


Password Change

      ↓

Revoke All Sessions

      ↓

Revoke Refresh Tokens

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

اما باید به‌عنوان یک Policy مشخص در سیستم طراحی شود.


# Logout و Account Disable

اگر حساب کاربر غیرفعال شود:


User disabled

نباید Sessionهای قبلی همچنان معتبر بمانند.

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


if (!$user->is_active) {

    revokeSession($session);

}

و Tokenهای مرتبط نیز باید مطابق Policy سیستم مدیریت شوند.


# Logout و Access Tokenهای JWT

اگر Access Token از نوع JWT باشد و Stateless باشد، این کد:


revokeSession();

لزوماً JWT قبلی را بی‌اعتبار نمی‌کند.

چون JWT ممکن است تا زمان Expiration معتبر باقی بماند.

برای همین سیستم‌هایی که نیاز به Revocation فوری دارند ممکن است از روش‌هایی مانند:

  • کوتاه کردن عمر Access Token
  • Introspection
  • Token Revocation
  • Token Versioning
  • Server-side Session Check

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


# Logout فوری با Token Introspection

در معماری‌هایی که Resource Server وضعیت Token را از Authorization Server می‌پرسد:


API

 ↓

Introspection

 ↓

Authorization Server

 ↓

Active?

بعد از Revocation:


active = false

می‌شود.

در نتیجه Logout می‌تواند سریع‌تر روی دسترسی API اثر بگذارد.


# Session Revocation در Database

یک روش مناسب این است که Session دارای وضعیت باشد:


active

revoked

expired

مثلاً:


SELECT *

FROM oidc_sessions

WHERE id = ?

AND revoked_at IS NULL;

اگر نتیجه‌ای وجود نداشت:


Session Invalid


# Session Expiration و Logout

Session ممکن است بدون Logout نیز منقضی شود.

مثلاً:


created_at = 10:00

last_seen_at = 12:00

و Policy بگوید بعد از مدت مشخصی Session باید منقضی شود.

پس دو مفهوم داریم:


Expiration

Revocation

Expiration به پایان عمر طبیعی Session مربوط است.

Revocation یعنی Session به‌صورت فعال باطل شده است.


# Idle Timeout و Absolute Timeout

دو Policy مهم:

Idle Timeout

اگر کاربر برای مدت مشخصی فعالیت نکند، Session منقضی می‌شود.

مثلاً:


30 minutes inactivity

Absolute Timeout

حتی اگر کاربر فعال باشد، Session بعد از مدت مشخصی منقضی می‌شود.

مثلاً:


8 hours

ترکیب این دو Policy می‌تواند امنیت Session را بهتر کنترل کند.


# Logout و Audit Log

در سیستم‌های حساس، ثبت رویداد Logout مفید است.

مثلاً:


User 25

Action: logout

Session: 982

Client: shop

Time: 2026-09-26 08:20

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


Access Token

Refresh Token

Session ID

Logout Token

را در Log ذخیره کنیم.


# نمونه Audit Log


CREATE TABLE security_events (

    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

    user_id BIGINT UNSIGNED NULL,

    event_type VARCHAR(100) NOT NULL,

    client_id VARCHAR(255) NULL,

    session_id_hash CHAR(64) NULL,

    created_at TIMESTAMP NOT NULL

);

سپس:


logSecurityEvent(

    'oidc_logout',

    $userId,

    $clientId

);


# Logout در معماری چند اپلیکیشنی

فرض کنیم:


                Identity Provider

                       │

             ┌─────────┼─────────┐

             │         │         │

             ▼         ▼         ▼

           Shop      Panel     Portal

             │         │         │

             ▼         ▼         ▼

          Session   Session   Session

هر RP باید Session خودش را مدیریت کند.

OP نباید فرض کند که با حذف Session خودش، Sessionهای RP نیز خودکار حذف شده‌اند.

برای این کار از مکانیزم‌های Logout مانند:


RP-Initiated Logout

Front-Channel Logout

Back-Channel Logout

Session Management

استفاده می‌شود.


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

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


                         Identity Provider

                                │

                       Central OP Session

                                │

                 ┌──────────────┼──────────────┐

                 │              │              │

                 ▼              ▼              ▼

              Shop RP       Panel RP       Portal RP

                 │              │              │

                 ▼              ▼              ▼

            Local Session  Local Session  Local Session

                 │              │              │

                 ▼              ▼              ▼

            Refresh Token  Refresh Token  Refresh Token

هر Session باید قابلیت:


Create

Validate

Refresh

Revoke

Expire

Destroy

داشته باشد.


# Logout Service

بهتر است منطق Logout در یک Service مرکزی قرار بگیرد.

مثلاً:


class LogoutService

{

    public function logoutSession(

        OidcSession $session

    ): void {



        $this->revokeLocalSession($session);



        $this->revokeRefreshTokens(

            $session

        );



        $this->invalidateRememberMe(

            $session

        );



        $this->writeAuditLog(

            $session

        );

    }

}

در این حالت Controller فقط درخواست را دریافت می‌کند.


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

Service دیگری می‌تواند:


public function logoutAllDevices(

    int $userId

): void {



    $sessions = $this->sessionRepository

        ->activeForUser($userId);



    foreach ($sessions as $session) {

        $this->logoutSession($session);

    }

}

در پروژه Production بهتر است این عملیات برای تعداد زیاد Sessionها به شکل Batch یا Queue انجام شود.


# مدیریت هم‌زمانی

فرض کنید دو Request هم‌زمان برای Logout یک Session ارسال شوند:


Request A → Logout

Request B → Logout

هر دو ممکن است بخواهند Session را تغییر دهند.

طراحی باید Idempotent باشد و در صورت نیاز از Transaction یا Lock مناسب استفاده شود.

مثلاً:


UPDATE oidc_sessions

SET revoked_at = NOW()

WHERE id = ?

AND revoked_at IS NULL;

این Query از نظر منطقی برای عملیات Idempotent مناسب‌تر است.


# Logout در سیستم Distributed

اگر چند Server داشته باشیم:


Server A

Server B

Server C

نباید Session فقط در RAM یکی از Serverها نگهداری شود.

بهتر است از Session Store مشترک مانند:


Redis

Database

استفاده شود.

در غیر این صورت ممکن است:


Logout on Server A

ولی:


Request → Server B

هنوز Session را معتبر ببیند.


# Cache و Logout

اگر وضعیت Session Cache شده باشد، هنگام Revocation باید Cache نیز مدیریت شود.

مثلاً:


Database → Revoked

Redis → Active

نباید این وضعیت باقی بماند.

می‌توان:


Cache::forget(

    'oidc_session:' . $sessionId

);

را بعد از Revocation انجام داد.


# Logout و Race Condition

یک Race Condition ممکن است زمانی رخ دهد که:


Request 1 → Validate Session

Request 2 → Logout Session

Request 1 → Continue

Request اول Session را قبل از Logout معتبر دیده است.

برای کاهش این مشکل، عملیات حساس باید طراحی مناسبی برای Session Validation و Revocation داشته باشند.

در سیستم‌های با نیاز امنیتی بالا، صرفاً Cache کردن وضعیت Session بدون درنظر گرفتن Revocation می‌تواند مشکل‌ساز باشد.


# Logout امن در PHP؛ چک‌لیست نهایی

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

  • [ ] بعد از Login موفق Session ID تغییر می‌کند.
  • [ ] Session ID خام در Database ذخیره نمی‌شود.
  • [ ] هر دستگاه Session مستقل دارد.
  • [ ] Session دارای وضعیت Revoked است.
  • [ ] Logout Session محلی را باطل می‌کند.
  • [ ] Cookie Session منقضی می‌شود.
  • [ ] Remember-Me Token مدیریت می‌شود.
  • [ ] Refresh Token هنگام Logout طبق Policy Revoked می‌شود.
  • [ ] Logout Token امضا و Claimهای آن بررسی می‌شوند.
  • [ ] iss بررسی می‌شود.
  • [ ] aud بررسی می‌شود.
  • [ ] jti برای Replay Protection مدیریت می‌شود.
  • [ ] Logout Idempotent است.
  • [ ] Logout یک Session با Logout همه Sessionها اشتباه گرفته نمی‌شود.
  • [ ] Password Change Policy مشخص دارد.
  • [ ] Account Disable باعث بی‌اعتبار شدن Sessionهای لازم می‌شود.
  • [ ] Session Expiration مشخص است.
  • [ ] Idle Timeout در نظر گرفته شده است.
  • [ ] Absolute Timeout در نظر گرفته شده است.
  • [ ] Audit Log وجود دارد.
  • [ ] Tokenهای حساس در Log ذخیره نمی‌شوند.
  • [ ] Cache بعد از Revocation مدیریت می‌شود.
  • [ ] Session Store در معماری چندسروره مشترک است.
  • [ ] Race Conditionهای مهم بررسی شده‌اند.

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

فقط session_destroy() کردن


session_destroy();

این ممکن است فقط یک بخش از Logout باشد.


فراموش کردن Refresh Token

اگر Refresh Token همچنان معتبر باشد، Logout ممکن است کامل نباشد.


Logout همه دستگاه‌ها هنگام خروج از یک دستگاه

این کار ممکن است UX نامناسبی ایجاد کند و باید یک Policy آگاهانه باشد.


استفاده از Email برای Session Mapping

Email شناسه مناسب Session نیست.


ذخیره Session ID خام

در صورت افشای Database، ریسک Credential Exposure ایجاد می‌کند.


Cache کردن Session بدون Revocation

ممکن است Cache وضعیت قدیمی را نگه دارد.


Log کردن Token

این کار می‌تواند Credential حساس را وارد Log کند.


نداشتن Replay Protection

Logout Token تکراری باید قابل شناسایی باشد.


# سوالات متداول

آیا Logout یک دستگاه باید تمام دستگاه‌ها را Logout کند؟

خیر. این دو عملیات باید از نظر منطقی جدا باشند. می‌توان Logout یک Session خاص و Logout تمام Sessionهای کاربر را به‌صورت دو عملیات متفاوت طراحی کرد.

آیا session_destroy برای Logout کافی است؟

در سیستم ساده ممکن است بخش اصلی Logout باشد، اما در معماری OIDC معمولاً کافی نیست. Session، Refresh Token، Remember-Me Token و Session مربوط به Identity Provider ممکن است نیاز به مدیریت جداگانه داشته باشند.

آیا Access Token هنگام Logout باید Revoked شود؟

بستگی به نوع Token و معماری دارد. Access Tokenهای JWT ممکن است تا زمان Expiration معتبر بمانند، در حالی که Tokenهای Opaque یا سیستم‌های دارای Introspection می‌توانند Revocation فوری‌تری داشته باشند.

آیا Logout Token باید ذخیره شود؟

معمولاً نیازی به ذخیره کامل Token نیست. برای Replay Protection می‌توان jti و اطلاعات حداقلی لازم برای رویداد را ذخیره کرد.

آیا Session ID باید در Database ذخیره شود؟

بهتر است مقدار خام Session ID ذخیره نشود. در صورت نیاز می‌توان Hash آن را ذخیره کرد.

آیا Logout باید با Queue انجام شود؟

برای عملیات گسترده مانند Logout از چندین RP یا Revocation تعداد زیادی Session و Token، Queue می‌تواند مناسب باشد. اما عملیات حیاتی Session محلی باید طوری طراحی شود که وضعیت امنیتی کاربر بلافاصله مشخص باشد.


# جمع‌بندی

امنیت Logout در یک معماری OpenID Connect بسیار فراتر از اجرای:


session_destroy();

است.

یک سیستم حرفه‌ای باید چرخه کامل Session و Token را مدیریت کند:


Login

  ↓

Create Session

  ↓

Create Token

  ↓

Use Session

  ↓

Refresh Token

  ↓

Logout

  ↓

Revoke Session

  ↓

Revoke Token

  ↓

Destroy Cookie

  ↓

Audit

در معماری چنددستگاهی نیز هر Session باید هویت و چرخه عمر مستقلی داشته باشد:


User

├── Laptop Session

├── Mobile Session

└── Tablet Session

همچنین باید بین:


Logout Current Session

و:


Logout All Sessions

تفاوت مشخصی وجود داشته باشد.

در کنار این موارد، Session Fixation، Replay Attack، Cookie Security، Refresh Token Revocation، Cache، Race Condition و Session Expiration نیز باید در طراحی لحاظ شوند.

در نهایت، Logout موفق زمانی اتفاق می‌افتد که سیستم بتواند با اطمینان مشخص کند:

کدام Session، متعلق به کدام کاربر، در کدام اپلیکیشن و با کدام Token باید باطل شود.

این همان نقطه‌ای است که Logout را از یک عملیات ساده PHP به یک بخش مهم از معماری امنیتی Authentication تبدیل می‌کند.

نظرات (0)

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

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

مقالات مرتبط

نصب n8n؛ آموزش کامل راه‌اندازی n8n از صفر تا اجرا

نصب n8n؛ آموزش کامل راه‌اندازی n8n از صفر تا اجرا

۱۲ دقیقه
معایب n8n چیست؟ بررسی ۱۰ محدودیت و مشکل مهم قبل از استفاده

معایب n8n چیست؟ بررسی ۱۰ محدودیت و مشکل مهم قبل از استفاده

۲۱ دقیقه
مزایای n8n چیست؟ ۱۲ دلیل برای استفاده از n8n در اتوماسیون کسب‌وکار

مزایای n8n چیست؟ ۱۲ دلیل برای استفاده از n8n در اتوماسیون کسب‌وکار

۲۲ دقیقه
n8n یا Power Automate؛ کدام ابزار اتوماسیون برای کسب‌وکار شما بهتر است؟

n8n یا Power Automate؛ کدام ابزار اتوماسیون برای کسب‌وکار شما بهتر است؟

۲۲ دقیقه