توکن JWT را کجا ذخیره کنیم ؟ localStorage یا  sessionStorage ؟

کوکی httpOnlyسلام دوستان . معمولا هنگام توسعه فرانت اند برای لاگین کردن باید از API استفاده کنیم و نهایتا access_token دریافت شده رو ذخیره کنیم تا در درخواست های بعدی ازش استفاده کنیم!روال کار به صورت زیر است :فرض کنید نام کاربری و کلمه عبور را به صورت درخواست POST برای آدرس زیر ارسال کرده ایم :http://example.com/api/v1/loginو سپس درخواست ما با موفقیت صورت گرفته و احراز هویت شده ایم و access_token را به صورت زیر در جیسون دریافت کرده ایم :HTTP/1.1 200 OK {&quotaccess_token&quot: &quoteyJhbGciOiJIUzI1NiIsI.eyJpc3MiOiJodHRwczotcGxlL.mFrs3Zo8eaSNcxiNfvRh9dqKP4F1cB&quot,&quotexpires_in&quot:3600 }حال باید access_token دریافتی را در مرورگر ذخیره کنیم تا در درخواست های بعدی به صورت زیر از آن استفاده کنیم.معمولا سمت کلاینت توکن را در header قرار داده و ارسال میکنیم . به صورت زیر :HTTP/1.1 GET /posts Host: example.com Authorization: Bearer eyJhbGciOiJIUzI1NiIsI.eyJpc3MiOiJodHRwczotcGxlL.mFrs3Zo8eaSNcxiNfvRh9dqKP4F1cBهنگامی که توکن دریافت شد برای ذخیره آن در مرورگر دو انتخاب پیش رو هست : HTML5 Web Storage (localStorage or sessionStorage)Cookiesحالا هر روش را به صورت جداگانه توضیح میدم .1. ذخیره سازی توکن در HTML5 Web Storage :فرض کنید توکن را در قطعه کد زیر دریافت کرده ایم . حال باید آن را ذخیره کنیم اگر بخواهیم در HTML5 Web Storage آن را ذخیره کنیم باید یا در localstorage یا در sessionStorage آن را ذخیره کنیم . به این صورت میتوان عمل کرد : function tokenSuccess(err, response) { if(err){ throw err; } //ذخیره کردن در سشن استوریج window.sessionStorage.accessToken = response.body.access_token; //و یا ذخیره سازی در اوکال استوریج window.localStorage.accessToken = response.body.access_token; }به دلیل اینکه هم localStorage و هم sessionStorage از طریق جاوا اسکریپت قابل خواندن هستند هکر به راحتی میتواند با روش XSS یعنی با تزریق کد javascript در داخل تگ های خاص سایت مقادیر ذخیره شده در داخل آن ها را بخواند و توکن ذخیره شده را بدست بیاورد و از آن سو استفاده کند . برای آشنایی بیشتر در مورد حملات XSS میتوانید به این لینک مراجعه کنید . ذخیره سازی توکن در localstorage و sessionStorage به دلیل این که در مقابل این گونه حملات آسیب پذیر هستند پیشنهاد نمیشود.2. ذخیره سازی توکن در Cookies :فرض کنید توکن را در قطعه کد زیر دریافت کرده ایم . حال باید آن را ذخیره کنیم اگر بخواهیم در cookie آن را ذخیره کنیم باید به این صورت عمل کنیم :function tokenSuccess(err, response) { if(err){ throw err; } //ذخیره کردن در کوکی .access_token=&quoteyJhbGciOiJIUzI1NiIsI.eyJpc3MiOiJodHRwczotcGxlL.mFrs3Zo8eaSNcxiNfvRh9dqKP4F1cB; expires=Thu, 18 Dec 2013 12:00:00 UTC&quot }این روش هم در مقابل حملات XSS آسیب پذیر هست و ذخیره سازی در کوکی هم رد میشود .قبل از این که روشی ایمن را توضیح بدهم در مورد کوکی ها مطالبی رو توضیح میدم پرچم ها و مشخصات یک کوکی:هر کوکی دارای ۷ شاخص یا ویژگی است که دو مورد اولی آن الزامی و بقیه موارد اختیاری هستند. شاخص‌های کوکی عبارت اند از:نام کوکی (الزامی)Set-Cookie: access_token=1s8a65d1as3;محتوای کوکی (الزامی)Set-Cookie: access_token=1s8a65d1as3;تاریخ انقضاءSet-Cookie: access_token=1s8a65d1as3; Expires=Mon, 01 Jun 2014 12:45:30 GMT;مسیر و دامین کوکیSet-Cookie: access_token=1s8a65d1as3; Expires=Mon, 01 Jun 2014 12:45:30 GMT;Domain=hello.example.com; Path=/test;ارسال امن(secure)Set-Cookie: access_token=1s8a65d1as3; Expires=Mon, 01 Jun 2014 12:45:30 GMT;Domain=hello.example.com; Path=/test;Secureمانع از دسترسی سایر پروتکل‌ها (httpOnly)Set-Cookie: access_token=1s8a65d1as3; Expires=Mon, 01 Jun 2014 12:45:30 GMT;Domain=hello.example.com; Path=/test;Secure;HttpOnlyتوجه داشته باشید که فلگ Secure را با HttpOnly اشتباه نگیرید. فلگ HttpOnly کانال انتقال را به پروتکل‌های HTTP و HTTPS محدود می‌کند درحالی که فلگ Secure ارسال کوکی را به کانال‌های امن محدود می‌کند. پس وجود هر دو فلگ امنیت را بیشتر می‌کند.کوکی که به صورت httpOnly ساخته شده باشد نمی توانند توسط هیچ پروتکلی غیر از HTTP استفاده شوند. چنین کوکی هایی اطمینان می دهند که فقط وب سایت هایی که آنها را ایجاد کرده اند می توانند از آن ها استفاده کنند.در واقع این نوع کوکی ها قابل خواندن با جاوااسکریپ نیستند پس در نتیحه در مقابل حملات XSS آسیب پذیر نیستند . بهتر است در سمت کلاینت کاری با توکن نداشته باشید! ذخیره توکن در سمت کلاینت و با هر روشی امنیت کافی رو نداره !توکن باید در داخل یک کوکی و به صورت httpOnly از طرف خود سرور در داخل مرورگر ذخیره شود.در واقع در رخواست لاگین وقتی شما احراز هویت شدید در سمت سرور توکن ایجاد میشه و داخل یک کوکی به صورت httpOnly قرار میگیره و روی مرورگر شما میشینه . حالا بعد از هر درخواست ajax ( با axios و یا fetch و یا به صورت ساده ) این کوکی به صورت خودکار از مرورگر شما ارسال میشه سمت سرور و دیگه شما لازم نیست درگیر ارسال و ذخیره سازی اون باشید.در سمت سرور این کوکی خونده میشه و اگر توکن داخلش معتبر بود ریسپانس رو به شما میده .مثال و پیاده سازی در فریمورک Laravel :‌ به متد login در کنترلر زیر دقت کنید : این متد یک ایمیل و یک پسورد دریافت میکند و سپس در صورتی که این اطلاعات صحیح بود یک توکن ایجاد میکند و یک ریسپانس بر میگرداند .public function login(LoginRequest $request) { $email = $request->input('email'); $password = $request->input('password'); try { $this->oauthLoginAdmin->setEmail($email); $this->oauthLoginAdmin->setPassword($password); $token = $this->oauthLoginAdmin->login(); return response(&quot&quot, 200)->cookie('access_token', $data['access_token'], 8000, null, null, true, true); } catch (\Exception $exception) { return $this->respondUnauthorizedError(); } }وقتی کاربر احراز هویت شد از طریق کد زیر یک کوکی به صورت httpOnly در ریسپانس قرار میگیرد . لازم نیست خود توکن در ریسپانس بادی قرار بگیرد cookie('access_token', $data['access_token'], 8000, null, null, true, true);پارامتر ششم این متد که به صورت true ارسال شده است مشخص میکند که این کوکی باید secure باشد. یعنی این کوکی فقط با پروتکل امن https ارسال و دریافت بشه .پارامتر هفتم این متد که به صورت true ارسال شده است مشخص میکند که این کوکی باید httpOnly باشد.در این کد سرور این کوکی در مرورگر کاربر ذخیره میشود و لازم نیست توکن به صورت دستی توسط کد جاوا اسکریپت ذخیره شود و در دخواست های بعدی به صورت دستی در هدر قرارگیرد و ارسال شود . چون کوکی در تمام درخواست های بعدی ارسال میشود و این ویژگی کوکی هاست که در تمام درخواست ها ارسال میشوند.به کد زیر در سمت کلاینت توجه کنید :در کد زیر سمت کلاینت یک ایمیل و یک پسورد سمت سرور ارسال میشود و در صورتی که احزار هویت انجام شده بود یک درخواست دیگر سمت سرور ارسال میشود و لیست پست ها دریافت میشود. $( document ).ready(function() { $.ajax(&quothttps://example.com/api/v1/login&quot, { method: 'POST', xhrFields: { withCredentials: true }, data: {email: 'admin@admin.com', password: '123456'}, success: function (res,status,xhr){ $.ajax(&quothttps://example.com/api/v1/posts&quot, { method: 'GET', xhrFields: { withCredentials: true }, headers: {'Accept':'application/json'}, success: function (res){ console.log(res) }, }); }, }); });باید دقت کنید که در تمامی درخواست ها xhrFields: { withCredentials: true },را قرار دهید .این قطعه کد به مرورگر میفهماند که باید در درخواست های ajax تمام cookie ها و authentication headers را هم ارسال کند . بررسی توکن سمت سرور :‌برای کنترل کردن این کوکی در سمت سرور یک middleware ایجاد میکنیم . در این middleware ابتدا کوکی را بررسی میکنیم و توکن را از داخل آن دریافت میکنیم و بررسی میکنیم توکن valid هست یا نه .class AuthApiMiddleWare extends ResponseController { public function handle($request, Closure $next) { if ($request->hasCookie('access_token')) { $token = $request->cookie('access_token'); // check if token is valid or invalid // if invalid return 401 . // if valid return$next($request); } } }خلاصه مطلب : کار ارسال و دریافت و بررسی توکن را بر عهده سرور بگذاریم و توکن را در داخل کوکی httpOnly از سمت سرور بر روی مرورگر کاربر ذخیره سازی کنیم . این روش باعث میشود تا سایت در مقابل حملات XSS ایمن باشد . از طرفی چون کوکی به صورت secure در سمت سرور ایجاد میشود فقط از طریق https قابل ارسال و دریافت میشود . به طور کلی :پرچم HttpOnly برای پیشگیری و جلوگیری از حملات Injection و سرقت نشست‌ها استفاده می‌شود. درمجموع می‌توان این پرچم را به‌عنوان لایه‌ای برای ایمن‌سازی کوکی‌ها و نشست‌ها در نظر گرفت.