JWT یک فناوری است که در چند سال گذشته، از دنیای APIهای مدرن به سمت معماری وردپرس هم آمده و من در پروژه‌های واقعی زیاد دیده‌ام که تیم‌ها آن را به‌عنوان «راه‌حل کامل احراز هویت» برمی‌گزینند، بدون آن‌که بدانند JSON Web Token دقیقاً کجا سبک‌تر است و کجا مسئولیت‌های تازه‌ای روی دوششان می‌گذارد. نخستین تجربه‌ام با JWT به یک سرویس Headless وردپرس برمی‌گردد که در آن، بعد از مهاجرت از Session سنتی به JWT، سرعت پاسخ API دو برابر شد ولی در همان سه ماه اول، دو باگ امنیتی در تیم پیدا کردیم که هر دو ریشه در درک ناقص مکانیزم امضا داشتند.

اگر با مفاهیم پایه احراز هویت آشنایی کمتری دارید، پیش از ادامه احراز هویت چیست و چه انواعی دارد؟ را بخوانید. این نوشته، لایه مکمل آن بحث است: از مکانیزم دقیق JWT و آناتومی سه‌بخشی توکن شروع می‌کنم، به مقایسه با Session سنتی می‌رسم، و بعد به دام‌هایی می‌پردازم که در پروژه‌های واقعی هزینه‌ساز شده‌اند. اگر با افزونه‌ها و اکوسیستم وردپرس آشنایی کمتری دارید، افزونه وردپرس چیست و چگونه افزونه مناسب انتخاب کنیم نقطه شروع خوبی است.

JWT چیست و چه مسئله‌ای را حل می‌کند؟

JWT که مخفف JSON Web Token است و در مرجع فنی وب با همین نام JSON Web Token شناخته می‌شود، یک استاندارد باز (RFC 7519) است که به‌عنوان یک ظرف خودبسنده برای انتقال امن اطلاعات میان دو طرف استفاده می‌شود. برخلاف کوکی سنتی که فقط یک شناسه است و سرور باید به دیتابیس مراجعه کند تا صاحب شناسه را بشناسد، JWT خودش تمام اطلاعات لازم را در درون دارد و به‌همین دلیل به آن «خودبسنده» می‌گویند.

مسئله‌ای که JWT حل می‌کند، در معماری‌های توزیع‌شده خودش را نشان می‌دهد. وقتی یک سیستم در چند سرور اجرا می‌شود و هر سرور، سشن جداگانه دارد، کاربر با هر جابه‌جایی میان سرورها، از سیستم خارج می‌شود — مگر اینکه سرورها سشن مشترک داشته باشند. JWT این مسئله را با حذف کامل سشن سروری حل می‌کند: توکن در سمت کاربر نگه داشته می‌شود، در هر درخواست ارسال می‌شود و سرور بدون هیچ حافظه سمت سرور، اعتبارش را بررسی می‌کند. این طراحی، برای APIهای مدرن و معماری‌های میکروسرویس، یک تغییر بنیادی است.

نکته‌ای که در پروژه‌ها زیاد دیده‌ام و در جلسه‌های مشاوره تکرارش کرده‌ام: JWT تنها راه‌حل مسئله احراز هویت توزیع‌شده نیست، ولی یکی از سبک‌ترین گزینه‌ها است. اگر با مفاهیم کلی «احراز هویت چیست و چه انواعی دارد؟» آشنا هستید، می‌دانید که روش‌های دیگری مثل SAML، OAuth و OpenID Connect هم وجود دارند که هر کدام برای سناریوی خودشان طراحی شده‌اند. JWT به‌عنوان فرمت داده در بسیاری از این پروتکل‌ها هم استفاده می‌شود.

سه ویژگی کلیدی JWT

سه ویژگی، JWT را از کوکی سنتی و توکن‌های تصادفی جدا می‌کند. ویژگی اول: خودبسندگی. تمام اطلاعات لازم برای احراز هویت در خود توکن است و سرور نیازی به مراجعه به دیتابیس ندارد. ویژگی دوم: امضای رمزنگاری‌شده. توکن توسط سرور امضا می‌شود و هر تغییری در آن، امضا را باطل می‌کند. ویژگی سوم: انقضای ذاتی. توکن یک زمان انقضای مشخص (exp) دارد که در خودش ثبت شده و بعد از آن بی‌اعتبار می‌شود.

JWT یک شناسه نیست، یک سند امضاشده است؛ کوکی سنتی می‌گوید «کیستی؟»، JWT می‌گوید «کیستی، تا چه زمانی، و مجاز به چه کاری».

آناتومی سه‌بخشی یک توکن JWT

برای درک درست JWT، باید بدانید یک توکن از سه بخش تشکیل شده که با نقطه از هم جدا می‌شوند. این سه بخش، در ظاهر یک رشته بی‌معنی به نظر می‌رسند، ولی هر کدام نقش دقیقی دارند. درک این ساختار، پیش‌نیاز دیباگ و رفع مشکلات احراز هویت در پروژه‌های واقعی است.

بخش اول: Header

بخش اول توکن، Header است که اطلاعات مربوط به الگوریتم امضا و نوع توکن را نگه می‌دارد. یک نمونه:

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

این بخش در نهایت با Base64URL کدگذاری می‌شود، ولی توجه کنید که Base64URL کدگذاری است نه رمزنگاری — یعنی هر کسی می‌تواند محتوای Header را بخواند. این نکته در پروژه‌های واقعی زیاد نادیده گرفته می‌شود: بعضی توسعه‌دهندگان تصور می‌کنند محتوای JWT رمزنگاری‌شده است و اطلاعات حساس را در آن قرار می‌دهند. JWT به‌طور پیش‌فرض محتوایش قابل خواندن است؛ تنها امضا دارد که تضمین می‌کند محتوا تغییر نکرده.

بخش دوم: Payload

بخش دوم، Payload یا Claims است که اطلاعات کاربر را نگه می‌دارد. سه دسته Claim در JWT وجود دارد. دسته اول، Registered Claims که استاندارد شده‌اند: iss (صادرکننده)، sub (موضوع)، aud (مخاطب)، exp (زمان انقضا)، nbf (قبل از این زمان معتبر نباشد)، iat (زمان صدور) و jti (شناسه یکتای توکن). دسته دوم، Public Claims که در IANA Registry ثبت شده‌اند. دسته سوم، Private Claims که مختص برنامه شما هستند.

{
  "sub": "1234",
  "name": "Ali Karimi",
  "role": "editor",
  "iat": 1700000000,
  "exp": 1700003600
}

نکته امنیتی کلیدی در Payload: هرگز اطلاعات حساس مثل رمز عبور، کلید API یا اطلاعات کارت بانکی را در Payload قرار ندهید. چون Payload فقط کدگذاری می‌شود، نه رمزنگاری. اگر با اصول نوشتن کد PHP امن آشنا نیستید، نوشتن کد PHP امن برای وردپرس تفاوت کدگذاری و رمزنگاری را مفصل توضیح می‌دهد.

بخش سوم: Signature

بخش سوم، امضا است که بخش‌های اول و دوم را با یک کلید (Secret) ترکیب و هش می‌کند:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

امضا نقش اصلی در امنیت JWT دارد. اگر مهاجم حتی یک کاراکتر در Header یا Payload تغییر دهد، امضا نامعتبر می‌شود و سرور توکن را رد می‌کند. همین ویژگی، JWT را در برابر تغییر محتوا مقاوم می‌کند — ولی همان‌طور که در بخش دام‌های امنیتی خواهم گفت، این مقاومت مشروط به رعایت چند قاعده در سمت سرور است.

نمونه کامل یک JWT

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

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6IkFsaSBLYXJpbWkiLCJyb2xlIjoiZWRpdG9yIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

در نگاه اول، این رشته بی‌معنی به نظر می‌رسد ولی هر سه بخش در آن جای مشخصی دارند. ابزار آنلاین jwt.io برای دیباگ JWT مفید است ولی هشدار مهم: هرگز توکن واقعی کاربران را در ابزارهای آنلاین وارد نکنید، چون توکن به سرور شخص ثالث ارسال می‌شود.

امضای JWT: HMAC در برابر RSA و ECDSA

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

HMAC و الگوریتم HS256

HMAC (Hash-based Message Authentication Code) یک رویکرد متقارن است: یک کلید مشترک بین صادرکننده و بررسی‌کننده توکن وجود دارد. الگوریتم HS256 پرکاربردترین الگوریتم در JWT است و از یک Secret Key در هر دو طرف استفاده می‌کند. مزیتش سرعت بالا و پیاده‌سازی ساده است؛ عیبش این است که اگر کلید در یک سرویس لو برود، همه سرویس‌ها آسیب می‌بینند.

$payload = [
    'sub' => $user_id,
    'iat' => time(),
    'exp' => time() + 3600,
];

$token = JWT::encode( $payload, $secret_key, 'HS256' );

RSA و الگوریتم RS256

RSA یک رویکرد نامتقارن است: کلید خصوصی برای امضا و کلید عمومی برای بررسی استفاده می‌شود. الگوریتم RS256 در سناریوهایی که صادرکننده و بررسی‌کننده توکن، سرویس‌های متفاوتی هستند، انتخاب بهتری است چون کلید خصوصی فقط در سرویس صادرکننده نگه داشته می‌شود و کلید عمومی می‌تواند به‌طور آزاد منتشر شود. مزیتش امنیت بیشتر در معماری‌های توزیع‌شده است؛ عیبش سرعت کمتر و پیچیدگی کلیدگردانی بیشتر.

ECDSA و الگوریتم ES256

ECDSA یک نوع رمزنگاری منحنی بیضوی است که امنیت مشابه RSA را با کلیدهای کوچک‌تر فراهم می‌کند. الگوریتم ES256 در پروژه‌های مدرن که به اندازه توکن اهمیت می‌دهند، انتخاب می‌شود. جالب اینجاست که توکن امضاشده با ES256 در مقایسه با RS256، حدود نیمی از حجم را دارد و همین تفاوت در APIهای پرترافیک محسوس است.

الگوریتمنوع کلیدمناسب برایحجم امضا
HS256متقارنسرویس‌های تک‌طرفهکم
RS256نامتقارنسرویس‌های توزیع‌شدهمتوسط
ES256نامتقارنAPI مدرن و موبایلکم
انتخاب الگوریتم امضا، یک تصمیم معماری است نه یک پیش‌فرض؛ اشتباه در این انتخاب، در روزی که یک سرویس لورفته می‌شود، هزینه‌اش را نشان می‌دهد.

JWT در برابر Session سنتی: مقایسه معماری

برای درک این‌که JWT چطور احراز هویت را سبک‌تر می‌کند، ابتدا باید بفهمیم Session سنتی چه باری روی سرور می‌گذارد. در معماری Session سنتی، وقتی کاربر لاگین می‌کند، سرور یک شناسه تصادفی تولید می‌کند و آن را در دیتابیس یا حافظه سرور ذخیره می‌کند. مرورگر کاربر این شناسه را در کوکی نگه می‌دارد و با هر درخواست ارسال می‌کند. سرور در هر درخواست باید شناسه را بگیرد، از دیتابیس بخواند، داده کاربر را پیدا کند و بعد پردازش را ادامه دهد.

هزینه‌های Session سنتی

این معماری سه هزینه پنهان دارد که در سایت‌های بزرگ محسوس می‌شود. هزینه اول: یک کوئری دیتابیس اضافه در هر درخواست کاربر لاگین‌کرده. در سایتی با هزار کاربر همزمان، این یعنی هزار کوئری اضافه در هر ثانیه. هزینه دوم: وابستگی به حافظه سرور. اگر سرورهای متعدد باشند، Session باید در همه آن‌ها به اشتراک گذاشته شود که خودش یک لایه پیچیدگی است. هزینه سوم: ذخیره‌سازی. در سایت‌های چند میلیون کاربری، جدول Session به یکی از بزرگ‌ترین جداول دیتابیس تبدیل می‌شود.

معماری سبک JWT

JWT این سه هزینه را به‌طور ریشه‌ای حذف می‌کند. کوئری دیتابیس در هر درخواست حذف می‌شود چون توکن خودش تمام اطلاعات را دارد. وابستگی به حافظه سرور حذف می‌شود چون هیچ حالتی در سرور نگه داشته نمی‌شود. ذخیره‌سازی سمت سرور حذف می‌شود چون توکن در سمت کاربر است. این سه حذف، مجموعه‌شان همان چیزی است که به آن «احراز هویت سبک» یا Stateless Authentication می‌گویند.

یک نکته مهم در این مقایسه: سبک‌تر بودن JWT به‌معنی بهتر بودن در همه سناریوها نیست. اگر سایت شما یک سرور دارد و تعداد کاربران لاگین‌کرده محدود است، Session سنتی هم سبک و هم ساده‌تر است. JWT وقتی برنده می‌شود که مقیاس یا توزیع سرور در تصویر باشد. اگر با معماری نشست‌های کاربری آشنایی کمتری دارید، چگونه نشست‌های کاربری را امن کنیم؟ تفاوت‌های این دو رویکرد را از زاویه امنیت بررسی می‌کند.

چطور JWT احراز هویت را سبک‌تر می‌کند؟

حالا که با آناتومی و مقایسه معماری آشنا شدیم، دقیقاً بگوییم JWT از کدام مسیرها بار احراز هویت را سبک‌تر می‌کند. این بخش، هسته این نوشته است.

مسیر اول: حذف کوئری دیتابیس در هر درخواست

مهم‌ترین اثر سبک‌سازی JWT همین است. در معماری Session سنتی، هر درخواست کاربر لاگین‌کرده یک کوئری دیتابیس (یا خواندن از Redis) اضافه دارد. در JWT، توکن در سمت سرور فقط بررسی امضا می‌شود که یک عملیات رمزنگاری سریع است. تفاوت این دو، در سایتی با صد هزار درخواست در ساعت، می‌تواند چندین ثانیه از زمان پردازش کم کند.

مسیر دوم: حذف وابستگی به سرور

در معماری میکروسرویس، احراز هویت با Session سنتی یک چالش بنیادی است: هر سرویس باید بتواند کاربر را بشناسد، ولی سشن‌ها در سرور اصلی نگه داشته می‌شوند. JWT این مسئله را حل می‌کند چون توکن خودبسنده است و هر سرویس می‌تواند به‌طور مستقل آن را بررسی کند. تنها چیزی که همه سرویس‌ها نیاز دارند، کلید امضای عمومی است. این معماری، همان چیزی است که در REST API وردپرس هم کاربرد روزافزون پیدا کرده است.

مسیر سوم: تعامل بهتر با کلاینت‌های غیرمرورگر

در معماری Session سنتی، کوکی وابسته به مرورگر است. اگر کلاینت شما یک اپلیکیشن موبایل، یک سرویس IoT یا یک اسکریپت CLI باشد، کوکی کاربرد ندارد و شما به معماری کاملاً متفاوتی نیاز دارید. JWT این مشکل را حل می‌کند چون یک هدر HTTP استاندارد است که در همه کلاینت‌ها یکسان کار می‌کند. این ویژگی، در پروژه‌هایی که هم وب و هم موبایل دارند، به یک صرفه‌جویی بزرگ تبدیل می‌شود.

مسیر چهارم: پخش جغرافیایی و CDN

در سایت‌های بزرگ با سرورهای توزیع‌شده جغرافیایی، Session سنتی یک گلوگاه است چون سشن باید در همه سرورها به اشتراک گذاشته شود. JWT این گلوگاه را حذف می‌کند چون هر سرور می‌تواند مستقل توکن را بررسی کند. این معماری، در سایت‌هایی که از CDN با Edge Computing استفاده می‌کنند، به یک مزیت محسوس تبدیل می‌شود. اگر روی این نوع معماری کار می‌کنید، هاست چیست و چگونه انتخاب درستی داشته باشیم انتخاب‌های زیرساختی مرتبط را توضیح می‌دهد.

مسیر پنجم: کاهش بار روی دیتابیس

در سایت‌های با ترافیک بالا، جدول Session می‌تواند به یکی از بزرگ‌ترین جداول دیتابیس تبدیل شود. هر لاگین یک ردیف، هر تسویه یک حذف و هر بازدید یک کوئری. این چرخه در سایت‌های چند میلیون کاربری به یک فشار دائم روی دیتابیس تبدیل می‌شود. JWT این فشار را حذف می‌کند چون هیچ داده‌ای در سرور ذخیره نمی‌شود. تنها چیزی که در دیتابیس می‌ماند، اطلاعات اصلی کاربران است، نه سشن‌های موقت. تحلیل کامل این اثر روی سرعت در تاثیر دیتابیس بر سرعت سایت چقدر است آمده است.

سبک‌سازی JWT از یک جا نمی‌آید؛ از حذف پنج لایه پنهان می‌آید که در Session سنتی هر کدام باری روی سرور می‌گذارند.

JWT در وردپرس و REST API

وردپرس در چند سال اخیر، با رشد REST API، زمینه استفاده از JWT را فراهم کرده است. ولی پیاده‌سازی JWT در وردپرس، نکات خاص خودش را دارد که در پروژه‌های واقعی زیاد به آن‌ها برمی‌خورم.

الگوی پایه احراز هویت با JWT در وردپرس

الگوی پایه در وردپرس، استفاده از یک endpoint اختصاصی برای تولید توکن و یک فیلتر برای بررسی توکن در درخواست‌های بعدی است. روند کار در سه گام:

// گام 1: تولید توکن بعد از احراز هویت موفق
add_action( 'rest_api_init', function () {
    register_rest_route( 'myplugin/v1', '/token', [
        'methods'  => 'POST',
        'callback' => 'myplugin_generate_token',
    ] );
} );

function myplugin_generate_token( WP_REST_Request $request ) {
    $username = sanitize_user( $request->get_param( 'username' ) );
    $password = $request->get_param( 'password' );

    $user = wp_authenticate( $username, $password );

    if ( is_wp_error( $user ) ) {
        return new WP_Error( 'invalid_credentials', 'Invalid.', [ 'status' => 401 ] );
    }

    $payload = [
        'sub' => $user->ID,
        'iat' => time(),
        'exp' => time() + ( 7 * DAY_IN_SECONDS ),
    ];

    return [ 'token' => JWT::encode( $payload, MYPLUGIN_SECRET, 'HS256' ) ];
}

در گام بعدی، فیلتر determine_current_user توکن ارسال‌شده در هدر Authorization را بررسی می‌کند و شناسه کاربر را برمی‌گرداند. اگر توکن معتبر باشد، وردپرس به‌طور خودکار کاربر را شناسایی می‌کند و بقیه درخواست در بستر همان کاربر اجرا می‌شود.

چالش‌های پیاده‌سازی در وردپرس

وردپرس بر پایه کوکی و nonce طراحی شده، نه بر پایه توکن. این تفاوت طراحی، در پیاده‌سازی JWT منبع چند چالش است. چالش اول: مدیریت Nonce. وردپرس در خیلی از endpointها nonce را بررسی می‌کند که در معماری JWT معنایی ندارد. باید در افزونه اختصاصی، این بررسی‌ها را کنار بگذارید یا با بررسی توکن جایگزین کنید. اگر با مکانیزم nonce آشنایی کمتری دارید، نانس وردپرس و نقش آن در امنیت فرم‌ها پیش‌نیاز خوبی است.

چالش دوم: نقش‌ها و سطوح دسترسی. در وردپرس، سطح دسترسی کاربر بر پایه نقش‌ها تعریف می‌شود که در دیتابیس نگه داشته می‌شود. اگر در JWT فقط شناسه کاربر را ذخیره کنید، در هر درخواست باید نقش کاربر از دیتابیس خوانده شود که بخشی از سبک‌سازی را از بین می‌برد. راه‌حل، ذخیره نقش‌های اصلی در Payload توکن و بررسی آن‌ها به‌طور مستقیم است. ولی توجه کنید که این رویکرد، در زمان تغییر نقش کاربر، نیازمند ابطال توکن‌های فعال است.

چالش سوم: ابطال توکن. JWT ذاتاً قابل ابطال نیست چون در سمت سرور نگه داشته نمی‌شود. اگر کاربر رمزش را تغییر داد یا از سیستم خارج شد، توکن قدیمی همچنان معتبر است تا زمان انقضایش. راه‌حل‌های معمول، استفاده از لیست سیاه توکن‌ها (Blacklist) یا ترکیب JWT با Refresh Token است. بررسی دقیق این الگوها در پیاده‌سازی JWT در APIهای مدرن آمده است.

افزونه‌های JWT برای وردپرس

چند افزونه معتبر برای پیاده‌سازی JWT در وردپرس وجود دارد که هر کدام رویکرد خودشان را دارند. اگر با معیار انتخاب افزونه آشنا نیستید، ساختار استاندارد یک افزونه حرفه‌ای وردپرس چک‌لیست خوبی است. نکته مهم در انتخاب این افزونه‌ها، بررسی چرخه به‌روزرسانی، کیفیت کد و رعایت استانداردهای امنیتی است. در بازبینی‌های واقعی، افزونه‌هایی دیده‌ام که الگوریتم امضا را قابل تنظیم از سمت کلاینت می‌گذارند — یک آسیب‌پذیری جدی که در بخش بعدی به آن می‌پردازم.

ساخت API اختصاصی با JWT

اگر پروژه شما به یک API اختصاصی نیاز دارد، JWT می‌تواند لایه احراز هویت سبک و مقیاس‌پذیری باشد. اصول ساخت API اختصاصی در ساخت API اختصاصی برای وردپرس توضیح داده شده و ترکیب آن با JWT، در پروژه‌های Headless WordPress زیاد دیده می‌شود. اگر با مفهوم REST API آشنایی کمتری دارید، REST API در وردپرس نقطه شروع خوبی است.

دام‌های امنیتی JWT در پروژه‌های واقعی

JWT در کتاب‌های آموزشی به‌عنوان یک راه‌حل امنیتی جادویی معرفی می‌شود، ولی در پروژه‌های واقعی، پیاده‌سازی نادرست آن می‌تواند امنیت را از Session سنتی هم ضعیف‌تر کند. در بازبینی‌های پروژه‌ای که چند سال پیش انجام دادم، پنج آسیب‌پذیری رایج در پیاده‌سازی JWT دیده‌ام.

دام اول: پذیرفتن الگوریتم none

یکی از قدیمی‌ترین و خطرناک‌ترین آسیب‌پذیری‌های JWT این است که پیاده‌سازی سمت سرور، الگوریتم اعلام‌شده در Header توکن را می‌پذیرد. اگر مهاجم توکنی با alg: none بسازد و امضا را حذف کند، سرور نادرست آن را معتبر می‌داند. راه‌حل: در سمت سرور، الگوریتم مورد انتظار را صریح مشخص کنید و هرگز به Header توکن اعتماد نکنید. این اصل، در مستندات امنیتی OWASP هم تأکید شده است.

دام دوم: کلید امضای ضعیف

اگر از الگوریتم HS256 استفاده می‌کنید، کلید Secret باید حداقل ۲۵۶ بیت آنتروپی داشته باشد. من در پروژه‌ای به کلید ۱۰ کاراکتری برخورده‌ام که در کمتر از یک ساعت با حمله brute force قابل حدس بود. راه‌حل: کلید را با random_bytes( 32 ) یا ابزارهای معادل تولید کنید و در متغیر محیطی نگه دارید، نه در کد. نگهداری کلید در کد و کامیت آن، یکی از خطاهای رایج در پروژه‌های Git است.

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

یک الگوی رایج در پروژه‌های تک‌صفحه‌ای (SPA) این است که توکن در localStorage مرورگر ذخیره شود. این رویکرد در برابر حمله XSS آسیب‌پذیر است چون هر اسکریپت مخربی می‌تواند توکن را بخواند. راه‌حل، ذخیره توکن در HttpOnly Cookie است که اسکریپت‌های سمت کلاینت نمی‌توانند بخوانند. هر چند این رویکرد خودش چالش CSRF دارد که با ترکیب با CSRF Token حل می‌شود.

دام چهارم: انقضای طولانی یا نامحدود

وقتی تیم توسعه، از ابطال توکن خسته می‌شود، معمولاً انقضا را به چند ماه افزایش می‌دهد یا کلاً حذف می‌کند. این تصمیم، در روزی که توکن لو می‌رود، فاجعه‌ساز است. راه‌حل استاندارد، ترکیب Access Token با انقضای کوتاه (پانزده دقیقه تا یک ساعت) و Refresh Token با انقضای طولانی (هفت تا سی روز) است. این الگو، هم امنیت را حفظ می‌کند و هم تجربه کاربری را.

دام پنجم: نادیده گرفتن Claim‌های استاندارد

بعضی پیاده‌سازی‌ها فقط sub و exp را بررسی می‌کنند و سایر Claim‌ها را نادیده می‌گیرند. اگر توکن شما از یک سرویس دیگر صادر شده باشد، نبود بررسی iss و aud می‌تواند باعث شود یک توکن بیگانه معتبر تلقی شود. راه‌حل: همه Claim‌های مرتبط را در سمت سرور بررسی کنید. یک آسیب‌پذیری جالب که در پروژه‌ای دیده‌ام، همین نبود بررسی aud بود که اجازه می‌داد توکن یک سرویس کم‌حساس، در سرویس حساس پذیرفته شود.

JWT به‌خودی‌خود امن نیست؛ یک قالب امن است. امنیت واقعی از پیاده‌سازی دقیق در سمت سرور می‌آید، نه از خود استاندارد.

JWT در برابر OAuth: انتخاب درست

یکی از سؤالات پرتکرار در پروژه‌های واقعی، این است که «JWT یا OAuth؟» پاسخ کوتاه این است که این دو، دو چیز متفاوتند که در کنار هم کار می‌کنند، نه دو گزینه روی یک محور. JWT یک فرمت داده است و OAuth یک پروتکل احراز هویت. در واقع در معماری OAuth 2.0، Access Token صادرشده اغلب یک JWT است.

کجا JWT کافی است

اگر شما یک سرویس تک‌مالکی دارید و خودتان هم صادرکننده توکن هستید و هم بررسی‌کننده آن، JWT به‌تنهایی کافی است. مثال ساده: یک API اختصاصی که کلاینت موبایل خودتان از آن استفاده می‌کند. اینجاست که سبک‌سازی JWT بیشترین ارزش را دارد و لایه‌های اضافی OAuth، فقط پیچیدگی می‌آورند.

کجا به OAuth نیاز است

اگر در سناریوی «ورود با گوگل» یا «ورود با گیت‌هاب» هستید، OAuth پاسخ درست است چون کاربر شما با یک سرویس خارجی احراز هویت می‌شود. توضیح دقیق این جریان در OAuth در عمل: ورود با گوگل چطور کار می‌کند؟ آمده و مقایسه فنی‌تر در انتخاب بین OAuth و JWT برای پروژه صورت گرفته است. خلاصه تفاوت: JWT برای احراز هویت داخلی، OAuth برای احراز هویت با واسطه سرویس خارجی. غالباً این دو با هم ترکیب می‌شوند.

ترکیب JWT و OAuth در معماری مدرن

در معماری‌های مدرن، اغلب OAuth برای گرفتن دسترسی از سرویس خارجی و JWT برای ادامه احراز هویت داخلی استفاده می‌شود. مثلاً کاربر با گوگل وارد می‌شود (OAuth)، سیستم شناسه کاربر گوگل را می‌گیرد، آن را به کاربر داخلی نگاشت می‌کند و بعد برای کاربر یک JWT داخلی صادر می‌کند. این الگو، در پروژه‌های چندسرویسی رایج است و ترکیب انعطاف‌پذیری فراهم می‌کند.

پرسش‌های پرتکرار درباره JWT و احراز هویت سبک

JWT چیست و چرا به آن «احراز هویت سبک» می‌گویند؟ JWT یک استاندارد باز برای انتقال امن اطلاعات میان دو طرف است که در احراز هویت کاربرد دارد. به آن «سبک» می‌گویند چون سمت سرور هیچ حالتی نگه نمی‌دارد و در هر درخواست، فقط امضای توکن بررسی می‌شود. این با Session سنتی که در هر درخواست یک کوئری دیتابیس اضافه دارد، تفاوت بنیادی دارد.

آیا JWT واقعاً از کوکی امن‌تر است؟ این سؤال رایج، پاسخ دوگانه‌ای دارد. JWT در برابر CSRF ذاتاً مقاوم‌تر است چون در هدر ارسال می‌شود نه در کوکی. ولی در برابر XSS اگر در localStorage ذخیره شود، آسیب‌پذیرتر است. انتخاب صحیح، JWT در HttpOnly Cookie است که هر دو مزیت را ترکیب می‌کند.

چرا JWT را نمی‌توان به‌سادگی ابطال کرد؟ چون سمت سرور هیچ حالتی نگه نمی‌دارد، توکن تا زمان انقضایش معتبر است حتی اگر کاربر رمزش را تغییر دهد. راه‌حل‌های استاندارد، استفاده از لیست سیاه یا ترکیب با Refresh Token است. اگر ابطال فوری برای شما حیاتی است، ممکن است احراز هویت با Session سنتی انتخاب بهتری باشد.

تفاوت Access Token و Refresh Token چیست؟ Access Token توکنی است که در هر درخواست به سرور فرستاده می‌شود و انقضای کوتاهی دارد (پانزده دقیقه تا یک ساعت). Refresh Token توکنی است که فقط برای دریافت Access Token جدید استفاده می‌شود و انقضای طولانی دارد (هفت تا سی روز). این تفکیک، تعادل بین امنیت و تجربه کاربری را فراهم می‌کند.

آیا JWT برای سایت‌های وردپرسی هم مناسب است؟ بله، اگر از REST API استفاده می‌کنید یا سایت Headless دارید. برای سایت‌های معمولی وردپرسی که از پیشخوان و قالب استاندارد استفاده می‌کنند، JWT مزیت مستقیمی ندارد و مکانیزم کوکی داخلی وردپرس کافی است. برای آشنایی با مکانیزم داخلی وردپرس، کار با User Meta در کدنویسی وردپرس نقطه شروع خوبی است.

آیا می‌توانم JWT را برای احراز هویت دو مرحله‌ای هم استفاده کنم؟ بله. رایج‌ترین الگو این است که JWT فقط بعد از تأیید مرحله دوم صادر شود. کاربر اول با رمز عبور و بعد با کد دوم احراز هویت می‌شود و در آن لحظه JWT صادر می‌شود. لایه امنیتی دو مرحله‌ای در احراز هویت دو مرحله‌ای چگونه امنیت را افزایش می‌دهد؟ مفصل توضیح داده شده است.

چرا در بعضی پروژه‌ها JWT افت سرعت ایجاد می‌کند؟ اگر در Payload توکن، اطلاعات سنگین قرار دهید یا از الگوریتم نامتقارن با کلید بزرگ استفاده کنید، بررسی امضا می‌تواند کند شود. راه‌حل: Payload را کوچک نگه دارید (فقط شناسه و نقش) و الگوریتم مناسب انتخاب کنید. تفاوت HS256 و RS256 در همین‌جا خودش را نشان می‌دهد.

آیا استفاده از JWT در فرم‌های وردپرس منطقی است؟ نه، فرم‌های وردپرس باید از مکانیزم nonce استفاده کنند، نه JWT. JWT برای درخواست‌های API طراحی شده، نه برای فرم‌های HTML. خلط این دو، هم امنیت را تضعیف می‌کند و هم تجربه کاربری را پیچیده می‌کند.

چگونه مطمئن شوم پیاده‌سازی JWT من امن است؟ چک‌لیستی که در پروژه‌ها استفاده می‌کنم: اول، الگوریتم امضا را صریحاً در سمت سرور مشخص کنید و هرگز به Header توکن اعتماد نکنید. دوم، کلید Secret حداقل ۲۵۶ بیت آنتروپی داشته باشد. سوم، همه Claimهای استاندارد را بررسی کنید. چهارم، Access Token را کوتاه نگه دارید. پنجم، توکن را در HttpOnly Cookie ذخیره کنید نه localStorage.

آیا برای پروژه‌های کوچک هم JWT ارزش پیاده‌سازی دارد؟ اگر پروژه شما یک سایت معمولی وردپرسی است، نه. اگر پروژه شما یک API اختصاصی یا Headless CMS است که از چند کلاینت متفاوت استفاده می‌شود، بله. تصمیم بر اساس مقیاس و نیاز آینده انجام می‌شود.

آیا JWT با احراز هویت مبتنی بر پارتیشن‌بندی سازگار است؟ این سؤال در معماری‌های جدید مطرح می‌شود. در سیستم‌های توزیع‌شده جغرافیایی، JWT مزیت محسوسی دارد چون هر پارتیشن می‌تواند توکن را مستقل بررسی کند. اینجاست که سبک‌سازی JWT از یک مزیت نظری به یک مزیت عملیاتی تبدیل می‌شود.

چرا JWT در مرورگرها مشکلات خاص دارد؟ چون مرورگر برای ارسال کوکی به‌طور خودکار عمل می‌کند ولی هدر Authorization نیازمند کد جاوااسکریپت است. این یعنی در معماری JWT، کد سمت کلاینت باید در هر درخواست، هدر Authorization را تنظیم کند. این تفاوت، در پیاده‌سازی‌های ساده مشکلی ایجاد نمی‌کند ولی در پروژه‌های بزرگ با چند کلاینت، نیازمند لایه انتزاعی مدیریت درخواست است.

آیا JWT با کش CDN سازگار است؟ بله، این یکی از مزیت‌های اصلی است. در معماری Session سنتی، پاسخ سرور برای هر کاربر متفاوت است و نمی‌تواند کش شود. در معماری JWT، اگر JWT در هدر ارسال شود، می‌توان کش را طوری تنظیم کرد که پاسخ‌ها بر اساس توکن جدا شوند. این ویژگی، در سایت‌های پربازدید به صرفه‌جویی محسوسی در پهنای باند منجر می‌شود.

JWT، یک تصمیم معماری نه یک مُد فناوری

در پایان این مسیر، یک حقیقت را باید بپذیریم: JWT یکی از آن فناوری‌هایی است که در چند سال گذشته به یک مُد تبدیل شده و همان‌طور که با هر مُد فناوری دیگر اتفاق می‌افتد، تعداد پروژه‌هایی که بدون نیاز واقعی از آن استفاده کرده‌اند، بیشتر از پروژه‌هایی است که واقعاً به آن نیاز داشته‌اند. JWT یک انتخاب معماری است، نه یک پیش‌فرض. اگر معماری شما از Session سنتی پشتیبانی می‌کند و مقیاس فعلی هم اجازه می‌دهد، تغییر به JWT فقط برای «مدرن بودن» یک هزینه اضافه است.

در پروژه‌های خودم، سه سؤال را قبل از انتخاب JWT از خودم می‌پرسم. اول، آیا سایت در چند سرور یا چند سرویس توزیع می‌شود؟ اگر نه، Session سنتی کافی است. دوم، آیا کلاینت‌های غیرمرورگر مثل موبایل یا CLI وجود دارند؟ اگر بله، JWT مزیت محسوس دارد. سوم، آیا مقیاس فعلی یا آتی به حدی است که کوئری اضافه در هر درخواست مشکلی ایجاد کند؟ اگر بله، JWT ارزش پیاده‌سازی دارد. این سه سؤال، تصمیم را از مُد جدا می‌کند.

اگر در ابتدای مسیر یادگیری هستید، سه تمرین را پیشنهاد می‌کنم. اول، یک توکن JWT را در ابزار jwt.io بسازید و سه بخش آن را جداگانه بررسی کنید تا ساختار را در ذهن تثبیت کنید. دوم، در یک پروژه تستی، یک API ساده با JWT بسازید و از یک کلاینت خارجی درخواست بزنید تا تفاوت با کوکی سنتی را حس کنید. سوم، در محیط staging، یک آسیب‌پذیری «الگوریتم none» را عمداً ایجاد کنید و ببینید کد شما چطور باید آن را رد کند. این سه تمرین، در چند ساعت، درک عمیقی از JWT به شما می‌دهد که هیچ مقاله‌ای جایگزینش نمی‌شود.

اگر تجربه‌ای از پیاده‌سازی JWT در پروژه‌ای واقعی دارید — چه با موفقیت، چه با دام‌های امنیتی که فقط در آتش دیده می‌شوند — در دیدگاه بنویسید. آن تجربه برای کسی که همین امروز تصمیم می‌گیرد احراز هویت پروژه‌اش را از Session سنتی به JWT مهاجرت دهد یا نه، ارزشمندتر از هر مستند رسمی است. 🎫