در دنیای مدرن وب، ایجاد تعادل بین تجربه کاربری روان و امنیت بالا یک چالش همیشگی است. کاربران دوست ندارند مدام مجبور به لاگین شوند، اما توکنهای دسترسی (Access Tokens) با عمر طولانی، یک حفرهی امنیتی بزرگ ایجاد میکنند. راهحل این معما، استفاده از یک معماری هوشمندانه مبتنی بر دو نوع توکن است: Access Token و Refresh Token. این مقاله به جای تمرکز بر کد، به تشریح جریان کار (Workflow) و مفاهیم امنیتی کلیدی این معماری میپردازد.

تصور کنید کاربر میخواهد وارد حساب خود شود(به مدت طولانی)، فرآیند پشت صحنه به این شکل است:
ورود کاربر (Login): کاربر نام کاربری و رمز عبور خود را وارد میکند، سرور پس از تایید، دو چیز را برمیگرداند:
Access Token: یک توکن با عمر کوتاه (مثلاً ۱۵ دقیقه) که در Response Request قرار دارد.
Refresh Token: یک توکن با عمر طولانی (مثلاً ۳۰ روز) که مستقیماً در یک HttpOnly Cookie امن در مرورگر کاربر Set میشود.
ارسال درخواستها به سرور: فرانتاند Access Token را در حافظه خود (Memory/State) نگه میدارد و برای هر درخواستی که به APIهای محافظتشده میزند، آن را در هدر Authorization قرار میدهد.
انقضای Access Token: پس از ۱۵ دقیقه منفعل بودن کاربر، Access Token منقضی میشود، اگر فرانتاند با این توکن درخواستی ارسال کند، سرور با خطای 401 Unauthorized پاسخ میدهد.
Silent Refresh: اپلیکیشن فرانتاند به صورت هوشمند این خطای 401 را تشخیص میدهد و به صورت خودکار یک درخواست به یک آدرس مشخص در بکاند (مثلاً /api/refresh) ارسال میکند. مرورگر به صورت خودکار Refresh Token را که در کوکی HttpOnly ذخیره شده، به این درخواست ضمیمه میکند. سرور Refresh Token را تایید کرده و یک Access Token کاملاً جدید صادر میکند. اپلیکیشن این توکن جدید را گرفته و درخواست ناموفق قبلی را دوباره، این بار با موفقیت، ارسال میکند. تمام این فرآیند بدون اطلاع کاربر انجام میشود.
برای اینکه این معماری واقعاً امن باشد، باید چند مفهوم حیاتی را درک و پیادهسازی کنیم.
تفاوت با کوکی معمولی: یک کوکی معمولی توسط جاوا اسکریپت قابل خواندن است (با دستور). این یعنی اگر یک هکر بتواند کد مخربی را در سایت ما تزریق کند (حمله XSS)، میتواند تمام کوکیها را بدزدد. اما یک کوکی HttpOnly فقط توسط سرور قابل خواندن است و جاوا اسکریپت هیچ دسترسی به آن ندارد.
نحوه تنظیم و استفاده: HttpOnly Cookie توسط سرور و از طریق هدر Set-Cookie تنظیم میشود. فرانتاند هیچ نقشی در تنظیم یا خواندن آن ندارد. مرورگر به صورت خودکار آن را در تمام درخواستهایی که به همان دامنه ارسال میشود، ضمیمه میکند.

Secure: یعنی کوکی فقط روی پروتکل امن HTTPS ارسال شود.
SameSite=Strict: یک لایه دفاعی قوی در برابر حملات CSRF است.
این یک تکنیک پیشرفته برای افزایش امنیت است. ایده اصلی این است که هر Refresh Token فقط یک بار قابل استفاده است.
چطور کار میکند؟
کاربر با Refresh-Token-A یک Access Token جدید درخواست میکند.
سرور یک Access Token جدید و یک Refresh-Token-B کاملاً جدید به کاربر میدهد.
سرور فوراً Refresh-Token-A را باطل میکند.
چرا این کار مفید است؟ این تکنیک به ما کمک میکند سرقت توکن را تشخیص دهیم. فرض کنید هکری Refresh-Token-A را دزدیده است. اگر هکر سعی کند از آن استفاده کند، سرور درخواست او را میپذیرد و یک جفت توکن جدید به هکر میدهد، اما همزمان Refresh-Token-A را در Black list خود قرار میدهد (معمولاً برای چند دقیقه). حالا وقتی کاربر واقعی (که هنوز از Refresh-Token-A میخواهد استفاده کند ) سعی کند یک توکن جدید بگیرد، سرور متوجه میشود که کسی در حال تلاش برای استفاده از یک توکن دزدیده شده و باطل است. در این لحظه، سرور برای امنیت کامل، تمام Sessionهای فعال آن کاربر را باطل کرده و او را مجبور به لاگین مجدد با رمز عبور میکند.
XSS (Cross-Site Scripting): تصور کنید یک نفر روی دیوار وبسایت شما یک کد مخرب به عنوان نظر ثبت میکند. هر کسی که آن صفحه را ببیند، آن کد در مرورگرش اجرا میشود. این کد میتواند توکنهایی که در localStorage یا کوکیهای معمولی ذخیره شدهاند را بدزدد. HttpOnly Cookie بهترین دفاع در برابر این نوع سرقت است.
CSRF (Cross-Site Request Forgery):
به طور مثال شما در وبسایت بانک خود (my-bank.com) لاگین کردهاید و توکن دسترسی شما در یک کوکی HttpOnly ذخیره شده است.
هکر برای شما یک ایمیل میفرستد که در آن یک عکس بامزه از گربه وجود دارد و شما روی آن کلیک میکنید.
کد HTML آن عکس این شکلی است: <img src="https://my-bank.com/api/transfer?to=hacker&amount=1000000" width="1" height="1" />
مرورگر شما برای بارگذاری این "عکس"، یک درخواست به سرور بانک شما میفرستد. از آنجایی که این درخواست به دامنه my-bank.com است، مرورگر با خوشنیتی و به صورت خودکار، تمام کوکیهای مربوط به آن سایت (از جمله کوکی HttpOnly که توکن دسترسی شما در آن است) را به درخواست ضمیمه میکند.
سرور بانک درخواست را میبیند، توکن معتبر را در کوکی پیدا میکند و فکر میکند این درخواست از طرف خود شماست. در نتیجه یک میلیون تومان به حساب هکر منتقل میکند! (توجه کنید این اتفاق در صورتی اتفاق میوفتد که Access Token در Http Only Cookie ذخیره شده باشه که اگر از این مکانیزم استفاده میکنید حتما حتما باید از Anti CSRF Token استفاده کنید که در ادامه بهش میپردازیم.)
این توکن، سلاح ما برای مقابله با حمله CSRF است، به خصوص وقتی از کوکیها برای احراز هویت استفاده میکنیم.
توجه: اگر Access Token شما در Memory ذخیره شده و هربار قبل از درخواست به Server در Request Header قرارش میدید لازم نیست از Anti-CSRF Token استفاده کنید، Acess Token ای که دارید استفاده میکنید همین کار رو براتون انجام میده.
چطور کار میکند؟
وقتی کاربر لاگین میکند، سرور علاوه بر توکنها، یک رشته تصادفی و مخفی دیگر به نام Anti-CSRF Token تولید میکند.
این توکن مخفی را در بدنه پاسخ به فرانتاند میفرستد. (برخلاف Refresh Token، این یکی در کوکی نیست و نکته همینجاست که اگر در کوکی باشه دوباره به صورت اتوماتیک توسط مرورگر ضمیمه درخواست هایی میشه که از Origin ما زده میشه و این مجددا باعث CSRF attacks میشه!).
فرانتاند این توکن را در حافظه خود نگه میدارد.
برای هر درخواست مهمی که وضعیت را تغییر میدهد (مثل POST, PUT, DELETE)، فرانتاند باید این توکن مخفی را در یک هدر سفارشی (مثلاً X-CSRF-TOKEN) قرار دهد.
سرور قبل از انجام هر کاری، چک میکند که آیا توکن موجود در این هدر با توکن مخفی که برای آن کاربر صادر کرده، مطابقت دارد یا خیر.
چرا این کار امن است؟ سایت هکر که میخواهد حمله CSRF را انجام دهد، به این توکن مخفی دسترسی ندارد و نمیتواند آن را در هدر درخواست جعلی خود قرار دهد. در نتیجه، سرور درخواست را به دلیل نداشتن هدر معتبر X-CSRF-TOKEN رد میکند و حمله شکست میخورد.
بیایید همه این مفاهیم را در یک داستان واقعی ببینیم:
ورود امن: "سارا" در اپلیکیشن شما لاگین میکند. سرور یک Access Token (۱۵ دقیقهای) در پاسخ JSON و یک Refresh Token (۳۰ روزه) به نام RT-1 در یک HttpOnly Cookie برایش ارسال میکند. همچنین یک Anti-CSRF Token به نام Anti-CSRF-XYZ را در پاسخ JSON قرار میدهد (البته اینجا حیاتی نیست چون Access Token به صورت JSON در Response body ارسال شده پس یعنی ما باید آن را در حافظه خودمون ذخیره کنیم و همانطور که قبلا توضیح دادم قبل از هر درخواست به سرور اون رو ارسال کنیم و عملا همون کار Anti-CSRF Token رو انجام میده برامون). فرانتاند Access Token و Anti-CSRF-XYZ را در حافظه نگه میدارد.
حمله CSRF: سارا در حالی که لاگین است، به اشتباه وارد سایت hacker.com میشود. این سایت سعی میکند یک درخواست POST برای حذف حساب کاربری سارا به سرور شما بفرستد. مرورگر سارا به صورت خودکار کوکی RT-1 را به درخواست ضمیمه میکند. اما، سایت هکر به توکن Anti-CSRF-XYZ دسترسی ندارد و نمیتواند هدر X-CSRF-TOKEN را به درستی تنظیم کند. سرور شما درخواست را به دلیل نداشتن این هدر معتبر، رد میکند. حمله CSRF شکست خورد.
انقضا و چرخش توکن (Refresh Token Rotation): Access Token سارا منقضی میشود. اپلیکیشن به صورت خودکار با استفاده از RT-1 یک توکن جدید درخواست میکند. سرور درخواست را تایید کرده، یک Access Token جدید و یک Refresh Token جدید به نام RT-2 برای سارا صادر میکند. سپس RT-1 را در لیست "اخیراً استفاده شده" (همون Black listای که قبلا صحبت کردیم) قرار میدهد.
سرقت و شناسایی: حالا فرض کنید هکری به طریقی توانسته RT-1 را درست بعد از استفاده سارا بدزدد. هکر با خوشحالی سعی میکند با RT-1 یک Access Token برای خودش بگیرد. سرور شما RT-1 را در Black list خود پیدا میکند و فوراً متوجه یک تلاش برای استفاده مجدد (Replay Attack) میشود. سرور به عنوان یک اقدام امنیتی، بلافاصله تمام Refresh Token های معتبر سارا (از جمله RT-2) را از دیتابیس خود حذف میکند. دفعه بعدی که اپلیکیشن سارا بخواهد از RT-2 استفاده کند، با خطا مواجه شده و سارا مجبور به لاگین مجدد با رمز عبور میشود. حمله هکر نه تنها شکست خورد، بلکه شناسایی و خنثی شد.