پیاده سازی Refresh Token در ASP Core WebApi

سلاماینکه JWT Token و JWT Refresh Token چیه برید خودتون بخونید.من فقط میخام پیاده سازیشون در حد سواد خودم یاد بدم. همین.سورس کد آموزش هم اینجاست.اولش هم بگم منبع این آموزش این سایت هستش. اگه انگلیستون خوبه برید از منبع اصلی بخونید بهتر از خوندن چرت و پرت های منه.اول یه پروژه جدید WebApi می سازیم. Authentication Type رو هم فعال کنید.پروژه جدیدیه پوشه Constants درست کنید.توش یه چندتا Role و یه یوزر دیفالت اضافه میکنیم.یه پوشه جدید میسازیم به اسم Entities و یه کلاس توش به اسم RefreshToken اینکه [Owned] چیه برید این مقاله رو بخونید.یه پوشه دیگه به اسم Contexts بسازید.و 2 تا کلاس توش.یه پوشه دیگه واسه مدل ها به اسم Modelsکداش چیزی نیس.حالم ندارم اینجا بزارمشون برید از Github برشون دارید.مهم ترین چیز اینکه ما تو قسمت یوزر لیستی از RefreshToken هایی که بالا کلاسش رو تعریف کردیم اضافه میکنیم.اساس کار اینکه ما یه جدول تو دیتابیس درست میکنیم به اسم RefreshToken .که هر سطر این جدول مال یه یوزر هستش. هروقت یوزر درخواست RefreshToken میده اگه قبلا موجود نباشه تو این جدول، یه نمونه ازش ایجاد مکنیم اگه هم موجود باشه مقدار ستون Revoked اونو درج میکنیم به تاریخ الان و اتوماتیک وار مقدار IsActive برابر null میشه. و اون token دیگه نامعتبر میشه.دوم چیز مهم اینجا کلاس AuthenticationModel که در واقع شناسنامه کابر هستش.و اینکه ما در Json خروجی نباید مقدرا RefreshToken و زمان اعتبار اونو بیارم پس از عبارت [JsonIgnore] استفاده میکنیم. حالا اول Api خیلی ساده تو قسکت کنترلرها میسازیم که دسترسی بخاد.یکیش برای همه یوزرها و یکیش فقط برای Admin.خب حالا قبل هر کاری پکیج رو نصب کنید.Microsoft.AspNetCore.Identity.EntityFrameworkCoreMicrosoft.EntityFrameworkCoreMicrosoft.EntityFrameworkCore.DesignMicrosoft.EntityFrameworkCore.SqlServerMicrosoft.EntityFrameworkCore.Toolsواسه اینکه تاحدی معماری کلین رو رعایت کنیم میایم یه سرویس واسه یوزر میسازیم.اول اینترفیسش بعد خود سرویس.حالا واسه سرویس اصلی چیزایی که نیاز داریم حالا یه متد ساده واسه ثبت نام موقع ایجاد یوزر جدید نقش یوزر دیفلت رو بهش میدیم.اول یه تابع خیلی ساده مینویسم کارمون راحتتر بشه. حالا قسمت اصلی پروژه یعنی CreateJwtTokenخب یه متد که باید مشخصات یه یوزر رو بهش پاس بدیم بعدش ازش Token بگیریم.اول باید Claim و Role یوزر رو بدست میاریم.البته ما اینجا با Role کار مکنیم ولی باید در در حالت کلی چک بشه.بقیش خیلی سادست و از توابع اصلی ایجاد Token استفاده میکنیم. با الگوریتم دلخواه که ما اینجا از SecurityAlgorithms.HmacSha256 استفاده کردیم.و اما تابع CreateRefreshTokenاز تابع RNGCryptoServiceProvider برای بدست آوردن " اعداد تصادفی در رمزنگاری" استفاده میکنیم.تاریخ انقضا هم دست خودتونه.و حالا مهم ترین سرویسمون GetTokenAsync ورودی تابع یه Email و Password هستش که میشه همون مدل TokenRequestModel اول چک کنیم ببینیم یوز اصلا هستش یا نه.حالا میخایم شناسنامه یوزر رو تکمیل کنیم.چون کابر درخواست Token میکنه ، تابع CreateJwtToken رو صدا میزنیم.اکستنشن ConfigureAwait(false) هم برای اینکه برای اینکه این تسک روی تسک Ui تاثیر نزاره و کندش نکنه.اگه تنبلیتون نگرفت این مقاله رو بخونید.حالا اگه تو دیتابیس RefreshTokens فعالی برای این یوزر بود اونو به همون شناسنامه اضافه میکنیم نه که ایجاد مکنیم.در آخر هم شناسنامه رو برمیگردونیم.حالا تابع RefreshTokenAsyncوقتی کاربر درخواست RefreshToken جدیدی رو میده اول باید چک کرد که این کابر دارای RefreshToken معتبر هستش یا نه. در واقع چون ما در همون بار اول ایجاد Token همزمان باهاش RefreshToken هم ایجاد میکنیم و با استفاده از دستور Response.Cookies.Append("refreshToken", refreshToken, cookieOptions); مقدارRefreshToken رو تو کوکی مرورگر ثبت میکنیم. پس اگر کاربری درخواست RefreshToken بده باید ببینم که ایا RefreshToken قبلی همچنان معتبر هست؟ درواقع خود کابر اصلا مجوز داره همچنین درخواستی بکنه یا تاریخ اعتبار مجوزش تموم شده؟اگه RefreshToken معتبر بود ،کافیه مقدار ستون Revoked رو برابر با زمان الان بزاریم که باعث میشه اتوماتیک وار RefreshToken نامعتبر بشه. بعدش RefreshToken جدید واسش درست میکنیم.نکته مهم :محل متداول ذخیره‌ JWT ها در local storage مرورگرها است . فقط باید دقت داشت که local storage یک sandbox است و محدود به Domain جاری برنامه و از طریق برای مثال زیر دامنه‌های آن قابل دسترسی نیست. در این حالت می‌توان JWT را در کوکی‌های ایجاد شده‌ در سمت کاربر نیز ذخیره کرد که چنین محدودیتی را ندارند. اما باید دقت داشت که حداکثر اندازه‌ی حجم کوکی‌ها 4 کیلوبایت است و با افزایش claims ذخیره شده‌ی در یک JWT و انکد شدن آن، این حجم ممکن است از 4 کیلوبایت بیشتر شود. بنابراین باید به این نکات دقت داشت. امکان ذخیره سازی توکن‌ها در session storage مرورگرها نیز وجود دارد. session storage بسیار شبیه است به local storage اما به محض بسته شدن مرورگر پاک می‌شود. اگر از local storage استفاده می‌کنید حملات Cross Site Request Forgery در اینجا دیگر موثر نخواهند بود، اما اگر به حالت استفاده‌ی از کوکی‌ها برای ذخیره‌ی توکن‌ها سوئیچ کنید این مساله همانند قبل خواهد بود و مسیر است، در این حالت بهتر است طول عمر توکن‌ها را تاحد ممکن کوتاه تعریف کنید تا اگر اطلاعات آن‌ها فاش شد به زودی بی‌مصرف شوند.اما یک مسئله دیگه حملات XSS هستش. در local storage :An XSS attack happens when an attacker can run JavaScript on your website. This means that the attacker can just take the access token that you stored in your localStorage.در Cookies :If you're using httpOnly and secure cookies, that means your cookies cannot be accessed using JavaScript. This means, even if an attacker can run JS on your site, they can't read your access token from the cookie.حال نداشتم ترجمه کنم.در نهایت : اگرچه کوکی‌ها هنوز آسیب‌پذیری‌هایی دارند، اما در صورت امکان در مقایسه با محل ذخیره‌سازی محلی ترجیح داده می‌شوند.اگر دلیل خوبی برای قرار دادن JWT خود در فضای local storage ندارید، این کار را نکنید.حوصله داشتید این مقاله و اون مقاله رو بخونید.حداکثر امنیت JWTها را چگونه می‌توان تامین کرد؟1- تمام توکن‌های خود را با یک کلید قوی امضا کنید و این کلید تنها باید بر روی سرور ذخیره شده باشد. هر زمانیکه سرور توکنی را از کاربر دریافت می‌کند، این سرور است که باید کار بررسی اعتبار امضای پیام رسیده را بر اساس کلید قوی خود انجام دهد.2- اگر اطلاعات حساسی را در توکن‌ها قرار می‌دهید، باید از JWE یا JSON Web Encryption استفاده کنید، زیرا JWTها صرفا دارای امضای دیجیتال هستند و نه اینکه رمزنگاری شده باشند.3- بهتر است توکن‌ها را از طریق ارتباطات غیر HTTPS، ارسال نکرد.4- اگر از کوکی‌ها برای ذخیره سازی آن‌ها استفاده می‌کنید، از Secure استفاده کنید تا از Cross-Site Scripting XSS attacks در امان باشید. کوکی احراز هویت فقط برای ارسال بین مشتری و سرور و یک نمونه عالی از کوکی است که همیشه باید به عنوان HttpOnly علامت گذاری شود.از آنجایی که بسیاری از کوکی ها هرگز نیازی به دسترسی به جاوا اسکریپت ندارند، یک راه حل ساده وجود دارد. علامت گذاری کوکی ها به عنوان HttpOnly. همانطور که از نام آن پیداست، کوکی‌های HttpOnly فقط از طریق درخواست HTTP (S!) توسط سرور قابل دسترسی هستند.از طریق Web.config : ویا از طریق #C :علامت گذاری کوکی ها به عنوان Secure و HttpOnly همیشه کافی نیست. تکنیکی به نام Cross-Site Tracing (XST) وجود دارد که در آن یک هکر از روش‌های درخواست TRACE یا TRACK برای دور زدن کوکی‌های علامت‌گذاری شده به عنوان HttpOnly استفاده می‌کند. متد TRACE در اصل برای کمک به اشکال زدایی در نظر گرفته شده است و به مشتری اطلاع می دهد که سرور چگونه یک درخواست را می بیند. این اطلاعات اشکال زدایی در پاسخ برمیگرداند و آن را از مشتری قابل خواندن می کند.اگر وب سرور دریافت کننده درخواست های TRACE را پشتیبانی کند، درخواست شامل متغیرهای سرور، کوکی ها و غیره اکنون در کنسول نوشته می شود. این کوکی احراز هویت را نشان می دهد، حتی اگر به عنوان Secure و HttpOnly علامت گذاری شده باشد.خوشبختانه، مرورگرهای مدرن به کسی اجازه نمی‌دهند درخواست‌های TRACE از جاوا اسکریپت داشته باشند. شما همچنان می خواهید با به روز رسانی Web.config خود، این امکان را از بین ببرید:5- مدت اعتبار توکن‌های صادر شده را منطقی انتخاب کنید.بقیش دیگه خیلی ساده هستش.تو همون سورس کد میتونید ادامه بدید.