توکنهای وب JSON (JWT) به استاندارد احراز هویت بدون حالت در برنامههای وب و موبایل مدرن تبدیل شدهاند. آنها راهی فشرده و خودکفا برای انتقال ادعاها بین طرفین ارائه میدهند. با این حال، این راحتی اغلب حس امنیت کاذب ایجاد میکند. بسیاری از توسعهدهندگان JWT را بدون درک کامل از حفرههای امنیتی احتمالی، مانند سرقت توکن، حملات تکراری و سردرگمی الگوریتم پیادهسازی میکنند. در این راهنما، فراتر از پیادهسازی پایه حرکت کرده و اقدامات امنیتی حیاتی مورد نیاز برای محافظت از لایه احراز هویت خود را بررسی خواهیم کرد.
1. هرگز JWTها را در LocalStorage ذخیره نکنید
شایعترین اشتباه در پیادهسازی JWT، ذخیره توکنها در localStorage یا sessionStorage مرورگر است. اگرچه این مکانیزمهای ذخیرهسازی در دسترس هستند، اما در برابر حملات تزریق کد بینسایتی (XSS) آسیبپذیرند. اگر مهاجمی کد جاوااسکریپت مخرب را به برنامه شما تزریق کند، میتواند به راحتی توکن را از localStorage خوانده و خود را به جای کاربر جا بزند.
راه حل: JWTها را در کوکیهای httpOnly، Secure و SameSite=Strict ذخیره کنید. این کار تضمین میکند که توکن فقط با درخواستهای ارسال شده به همان دامنه ارسال شود و برای جاوااسکریپت سمت کلاینت غیرقابل دسترسی باشد. این امر سرقت توکن مبتنی بر XSS را به طور مؤثر خنثی میکند.
2. توکنهای دسترسی با عمر کوتاه پیادهسازی کنید
توکنهای دسترسی با عمر طولانی مانند یک بمب ساعتی عمل میکنند. اگر یک توکن دسترسی دزدیده شود، مهاجر پنجره زمانی طولانی برای سوءاستفاده از آن دارد. با کوتاه نگه داشتن عمر یک توکن دسترسی (مثلاً ۵ تا ۱۵ دقیقه)، خطر مرتبط با سرقت را به حداقل میرسانید.
راه حل: از استراتژی توکن تازهسازی (Refresh Token) استفاده کنید. توکنهای دسترسی باید عمر کوتاهی داشته باشند، در حالی که توکنهای تازهسازی میتوانند عمر طولانیتری داشته باشند (مثلاً ۷ روز). توکن تازهسازی به طور ایمن در یک کوکی ذخیره میشود و زمانی که توکن دسترسی منقضی میشود، کلاینت از توکن تازهسازی برای دریافت یک توکن دسترسی جدید بدون نیاز به ورود مجدد کاربر استفاده میکند.
مثال: تنظیم انقضای توکن در Node.js/Express
const jwt = require('jsonwebtoken');
// توکن دسترسی با عمر کوتاه
const accessToken = jwt.sign(
{ userId: user.id, role: user.role },
process.env.ACCESS_TOKEN_SECRET,
{ expiresIn: '15m' }
);
// توکن تازهسازی با عمر طولانی (این را به طور ایمن در پایگاه داده و کوکی ذخیره کنید)
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: '7d' }
);
3. از الگوریتمهای امضای قوی استفاده کنید
JWTها برای اطمینان از یکپارچگی به امضای رمزنگاری متکی هستند. توسعهدهندگان باید مطمئن شوند که از الگوریتمهای نامتقارن مانند RS256 یا ES256، یا الگوریتمهای متقارن قوی مانند HS256 با رمزهای پیچیده استفاده میکنند. از استفاده از الگوریتم none که تأیید را کاملاً غیرفعال میکند، خودداری کنید و در برابر حملات سردرگمی الگوریتم (algorithm confusion) که در آن مهاجر الگوریتم را از RS256 به HS256 تغییر میدهد تا توکنها را جعل کند، محتاط باشید.
راه حل: الگوریتمهای مجاز را به صراحت در منطق اعتبارسنجی خود تعریف کنید. هرگز به هدر alg ارسال شده توسط کلاینت اعتماد نکنید.
مثال: اعتبارسنجی ایمن JWT
// در Node.js با استفاده از jsonwebtoken
const verified = jwt.verify(token, publicKey, {
algorithms: ['RS256'] // فقط RS256 را به صراحت مجاز کنید
});
4. لغو توکن را پیادهسازی کنید
یکی از انتقادات اصلی به JWTها این است که بدون حالت هستند، که لغو آنها را دشوار میسازد. با این حال، برای برنامههای حساس به امنیت، باید بتوانید توکنها را لغو کنید، به ویژه اگر کاربر خارج شود یا حساب او مورد تجاوز قرار گیرد.
راه حل: یک لیست سیاه از شناسههای توکن لغو شده (JTI - شناسه JWT) را نگهداری کنید. زمانی که کاربر خارج میشود یا یک حادثه امنیتی رخ میدهد، JTI توکن را به یک ذخیرهسازی سریعالوصول مانند Redis با زمان انقضای برابر با عمر باقیمانده توکن اضافه کنید. JTI را در طول اعتبارسنجی درخواست در برابر این لیست سیاه بررسی کنید.
نتیجهگیری
ایمنسازی احراز هویت JWT تنها به معنای امضای یک توکن نیست؛ بلکه نیازمند رویکردی جامع شامل ذخیرهسازی ایمن، عمرهای کوتاه، شیوههای امضای قوی و مکانیزمهای لغو است. با دنبال کردن این بهترین شیوهها، میتوانید سطح حمله برنامه خود را به طور قابل توجهی کاهش دهید و تجربه ایمنتری برای کاربران خود فراهم کنید. به یاد داشته باشید، امنیت یک فرآیند مداوم است، بنابراین جریان احراز هویت خود را به طور منظم ممیزی کنید و با آخرین استانداردهای رمزنگاری بهروز بمانید.