مقدمه
در یک سیستم ساده، 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 تبدیل میکند.





