احراز هویت (JWT, JWS, JWE)

اول از همه بگم: JWT مخفف JSON Web Token و JWS مخفف JSON Web Signature و JWE مخفف JSON Web Encryption هست.

تو فارسی به jwt می‌گم جوت تا تایپ و تلفظ‌ش ساده باشه.
یه جاهایی جمله با jwt شروع میشه و ویرگول نوشته چپ به راست میکنه، مجبورم.

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

  • روش اول Cookie-Based Authentication که پرکاربردترین روش بوده و در این حالت به ازای هر درخواست، یک کوکی جهت اعتبارسنجی کاربر به سمت سرور ارسال می‌شود (و برعکس).
  • روش دوم Token-Based Authentication که بر مبنای ارسال یک توکن امضا شده به سرور، به ازای هر درخواست است.


مزیت‌های استفاده‌ی از روش مبتنی بر توکن چیست؟

  • دامنه‌های متفابل Cross-domain / CORS: کوکی‌ها و CORS آنچنان با هم سازگاری ندارند، چون صدور یک کوکی وابسته‌ است به دومین مرتبط به آن و استفاده‌ از آن در سایر دومین‌ها عموما پذیرفته شده نیست، اما روش مبتنی بر توکن، وابستگی به دومین صدور آن‌ را ندارد و اصالت آن بر اساس روش‌های رمزنگاری تصدیق می‌شود.
  • بدون حالت بودن و مقیاس پذیری سمت سرور: در حین کار با توکن‌ها، نیازی به ذخیره‌ اطلاعات داخل سشن سمت سرور نیست و توکن موجودیتی است خود شمول (self-contained). به این معنا که حاوی تمام اطلاعات مرتبط با کاربر بوده و محل ذخیره‌ی آن در local storage و یا کوکی سمت کاربر می‌باشد. (البته اگر نیاز به مدیریت توکن‌ها داشته باشید نباید این مورد درنظر بگیرید)
  • توزیع برنامه با CDN: حین استفاده از روش مبتنی بر توکن، امکان توزیع تمام فایل‌های برنامه (جاوا اسکریپت، تصاویر و غیره) توسط CDN وجود دارد و در این حالت کدهای سمت سرور، تنها یک API ساده خواهد بود.
  • عدم در هم تنیدگی کدهای سمت سرور و کلاینت: در حالت استفاده‌ از توکن، این توکن می‌تواند از هرجایی و هر برنامه‌ای صادر شود و در این حالت نیازی نیست تا وابستگی ویژه‌ای بین کدهای سمت کلاینت و سرور وجود داشته باشد.
  • سازگاری بهتر با سیستم‌های موبایل: در حین توسعه‌ برنامه‌های بومی پلتفرم‌های مختلف موبایل، کوکی‌ها روش مطلوبی جهت کار با APIهای سمت سرور نیستند. تطابق یافتن با روش‌های مبتنی بر توکن در این حالت ساده‌تر است.
  • باگ CSRF: از آنجایی که از کوکی استفاده نمی‌شود، نیازی به نگرانی در مورد حملات CSRF نیست. چون دیگر برای مثال امکان سو استفاده‌ از کوکی فعلی اعتبارسنجی شده، جهت صدور درخواست‌هایی با سطح دسترسی شخص لاگین شده وجود ندارد، چون این روش کوکی را به سمت سرور ارسال نمی‌کند.
  • کارآیی بهتر: حین استفاده‌ از توکن‌ها، به علت ماهیت خود شمول آنها، رفت و برگشت کمتری به بانک اطلاعاتی صورت گرفته و سرعت بالاتری را شاهد خواهیم بود. (البته اگر نیاز به مدیریت توکن‌ها داشته باشید نباید این مورد درنظر بگیرید)
  • امکان نوشتن آزمون‌های یکپارچگی ساده‌تر: در حالت استفاده‌ از توکن‌ها آزمودن یکپارچگی برنامه نیازی به رد شدن از صفحه‌ی لاگین را ندارد و پیاده سازی این نوع آزمون‌ها ساده‌تر از قبل است.
  • استاندارد بودن: امروزه همینقدر که استاندارد JSON Web Token را پیاده سازی کرده باشید، امکان کار با انواع و اقسام پلتفرم‌ها و کتابخانه‌ها را خواهید یافت.


اما JWT یا JSON Web Token چیست؟

توضیح: JSON Web Token یا JWT یک استاندارد وب است (RFC 7519) که روشی فشرده و خود شمول (self-contained) را جهت انتقال امن اطلاعات بین مقاصد مختلف را توسط یک شی JSON تعریف می‌کند. این اطلاعات قابل تصدیق و اطمینان هستند، از این‌ رو که به صورت دیجیتال امضا می‌شوند. JWTها توسط یک کلید خصوصی (با استفاده از الگوریتم HMAC) و یا یک جفت کلید خصوصی و عمومی (توسط الگوریتم RSA) قابل امضا شدن هستند.
در این تعریف واژه‌هایی مانند «فشرده» و «خود شمول» بکار رفته‌اند:
 - «فشرده بودن»: اندازه‌ی شی JSON یک توکن در این حالت کوچک بوده و به سادگی از طریق یک URL و یا پارامترهای POST و یا داخل یک HTTP Header قابل ارسال است و به دلیل کوچک بودن این اندازه انتقال آن نیز سریع است.
 - «خود شمول»: بار مفید (payload) این توکن شامل تمام اطلاعات مورد نیاز جهت اعتبارسنجی یک کاربر است تا دیگر نیازی به کوئری گرفتن هر باره‌ از بانک اطلاعاتی نباشد (در این روش مرسوم است که فقط یکبار از بانک اطلاعاتی کوئری گرفته شده و اطلاعات مرتبط با کاربر را امضای دیجیتال کرده و به سمت کاربر ارسال می‌کنند).

جوت‌ها دارای سه قسمت است و این قسمت‌ها با نقطه از هم جا شدند، مانند: xxxxx.yyyyy.zzzzz که شامل سه بخش: header.payload.signature می‌باشند و هر بخش با الگوریتم Base64 اینکد می‌شود. نمونه‌ای از یک توکن جوت:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o

بخش Header:

بخش Header عموما دارای دو قسمت است که نوع توکن و الگوریتم مورد استفاده‌ توسط آن را مشخص می‌کند. نوع توکن در اینجا JWT است و الگوریتم‌های مورد استفاده، عموما  HMAC SHA256 و یا RSA هستند. نمونه اطلاعات دیکد شده بخش هدر:

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


بخش payload:

بخش payload یا «بار مفید» توکن، شامل claims است، منظور از claims اطلاعاتی است در مورد موجودیت مدنظر (عموما کاربر) و یک سری متادیتای اضافی. سه نوع claim وجود دارند:

نوع اول Reserved claims: یک سری اطلاعات مفید و از پیش تعیین شده‌ غیراجباری هستند مانند:iss یا صادر کنند، exp یا تاریخ انقضا، sub یا عنوان (subject) و aud یا مخاطب (audience)

نوع دوم Public claims: می‌تواند شامل اطلاعاتی باشد که توسط IANA JSON Web Token Registry پیشتر ثبت شده‌است و فضاهای نام آنها تداخلی نداشته باشند.

نوع سوم Private claims: ادعای سفارشی هستند که جهت انتقال داده‌ها بین مقاصد مختلف مورد استفاده قرار می‌گیرند.

نمونه اینکد شده بخش payload توکن:

{
   "exp": 1520507268,
   "sub": 582946,
   "name": "ErFUN KH",
   "admin": true
}

در نمونه بالا دو دیتای اول از نوع Reserved claims می‌باشد و دو دیتای آخر از نوع Private claims.


به طور استاندارد بخش Reserved claims می‌تونه شامل این موارد باشه:

  • فیلد iss یا issuer: صادر کننده توکن در این قسمت مشخص میشه، برای مثال مقدارش می‌تونه "hesabfun.com" باشه.
  • فیلد sub یا subject: در اینجا موضوع اصلی توکن مطرح میشه، مثلا موضوع توکن ما شناسایی کاربرانه، پس یوزر آیدی کاربر توش قرار میدیم تا متوجه بشیم کسی که توکن برامون ارسال کرده کیه.
  • فیلد aud یا audience: در این قسمت مشخص می‌کنیم این توکن باید کجا مورد استفاده قرار بگیره، این مواقعی کاربرد داره که شما چندتا سرور داشته باشید و همه از یک کلید خصوصی برای امضا استفاده می‌کنند. برای مثال مقدارش می‌تونه "https://blog.hesabfun.com" باشه.
  • فیلد exp یا expiration: در این قسمت مشخص میکنیم توکن تا چه زمانی اعتبار داره، این تاریخ بصورت Unix time مشخص می‌کنیم.
  • فیلد nbf یا not before: در این قسمت مشخص می‌کنیم توکن از چه تاریخی به بعد باید مورد مورد پردازش قرار بگیره، یعنی ممکنه توکن زودتر ایجاد کنیم ولی فعلا اجازه استفاده از اون نداشته باشند.
  • فیلد iat یا issuedAt: تاریخ ایجاد توکن به صورت Unix time اینجا قرار میدیم، بیشتر برای اینکه متوجه بشیم توکن جدیده یا نه استفاده میشه.
  • فیلد jti یا jwt id: آیدی منحصر به فرد برای هر توکن، اگه سیستم مدیریت توکن تلگرام دیده باشید متوجه میشید میتونه همچین استفاده‌ای داشته باشه، بهتره برای بلاک کردن، کل توکن تو دیتابیس قرار ندید و به هرکدوم یه آیدی بدید اینجوری دیتابیس سبک‌تری دارید.

این مقادیر همینجا تموم نمیشه، میتونید نگاهی به لیست مایکروسافت کنید، ولی بهتره خیلی شلوغش نکنید، مهم همین‌ها هستند که خیلی وقت‌ها استفاده نمیشن نهایتا sub و exp استفاده می‌کنند.


بخش signature:

تا اینا دیدیم همه دیتاهای با الگوریتم بیس۶۴ اینکد شد که به راحتی تو هرسیستمی دیکد میشه (اگه با این شیوه آشنا نیستید باید بگم فقط کاراکترهای غیر مجاز به حروف تبدیل میکنه، مقاله ویکی‌پدیا بخونید) و هرکسی می‌تونه همچین توکنی بسازه و برای ما ارسال کنه ولی نه با وجود بخش سوم (امضا).

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

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


چگونه از JWT در برنامه‌ها استفاده می‌شود؟

زمانیکه کاربر، لاگین موفقی را به سیستم انجام می‌دهد، یک توکن امن توسط سرور صادر شده و با فرمت JWT به سمت کلاینت ارسال می‌شود. این توکن باید به صورت محلی در سمت کاربر ذخیره شود. عموما از local storage برای ذخیره‌ی این توکن استفاده می‌شود اما استفاده‌ی از کوکی‌ها نیز منعی ندارد (ولی از لحاظ امنیتی خیلی خوب نیست، پایین‌تر توضیح میدم چرا). بنابراین دیگر در اینجا سشنی در سمت سرور به ازای هر کاربر ایجاد نمی‌شود و کوکی سمت سروری به سمت کلاینت ارسال نمی‌گردد.
سپس هر زمانیکه کاربری قصد داشت به یک صفحه یا محتوای محافظت شده دسترسی پیدا کند، باید توکن خود را به سمت سرور ارسال نماید. عموما اینکار توسط یک header سفارشی Authorization به همراه Bearer schema صورت می‌گیرد و یک چنین شکلی را دارد:

Authorization: Bearer <token>

این روش اعتبارسنجی، بدون حالت (stateless) است، از این جهت که وضعیت کاربر هیچگاه در سمت سرور ذخیره نمی‌گردد. API سمت سرور ابتدا به دنبال هدر Authorization فوق در درخواست دریافتی می‌گردد، اگر یافت شد و اصالت آن تایید شد کاربر امکان دسترسی به منبع محافظت شده را پیدا می‌کند. نکته‌ی مهم اینجا است که چون این توکن‌ها «خود شمول» هستند و تمام اطلاعات لازم جهت اعطای دسترسی‌های کاربر به او در آن وجود دارند، دیگر نیازی به رفت و برگشت به بانک اطلاعاتی جهت تایید این اطلاعات تصدیق شده نیست. به همین جهت کارآیی و سرعت بالاتری را نیز به همراه خواهند داشت.


نگاهی به محل ذخیره سازی JWT و نکات مرتبط با آن

محل متداول ذخیره‌ JWT ها در local storage مرورگرها است و در اغلب سناریوها نیز به خوبی کار می‌کند. فقط باید دقت داشت که local storage یک sandbox است و محدود به دومین جاری برنامه و از طریق برای مثال زیر دامنه‌های آن قابل دسترسی نیست. در این حالت می‌توان JWT را در کوکی‌های ایجاد شده‌ در سمت کاربر نیز ذخیره کرد که چنین محدودیتی را ندارند. اما باید دقت داشت که حداکثر اندازه‌ی حجم کوکی‌ها 4 کیلوبایت است و با افزایش claims ذخیره شده‌ی در یک JWT و انکد شدن آن، این حجم ممکن است از 4 کیلوبایت بیشتر شود. بنابراین باید به این نکات دقت داشت.
امکان ذخیره سازی توکن‌ها در session storage مرورگرها نیز وجود دارد. session storage بسیار شبیه است به local storage اما به محض بسته شدن مرورگر پاک می‌شود.
اگر از local storage استفاده می‌کنید حملات Cross Site Request Forgery در اینجا دیگر موثر نخواهند بود، اما اگر به حالت استفاده‌ی از کوکی‌ها برای ذخیره‌ی توکن‌ها سوئیچ کنید این مساله همانند قبل خواهد بود و مسیر است، در این حالت بهتر است طول عمر توکن‌ها را تاحد ممکن کوتاه تعریف کنید تا اگر اطلاعات آن‌ها فاش شد به زودی بی‌مصرف شوند.


انقضا و صدور مجدد توکن‌ها به چه صورتی است؟

توکن‌های بدون حالت صرفا بر اساس بررسی امضای پیام رسیده کار می‌کنند. به این معنا که یک توکن می‌تواند تا ابد معتبر باقی بماند. برای رفع این مشکل باید exp یا تاریخ انقضای متناسبی را به توکن اضافه کرد. برای برنامه‌های حساس این عدد می‌تواند 15 دقیقه باشد و برای برنامه‌های کمتر حساس، چندین ماه.
اما اگر در این بین قرار به ابطال سریع توکنی بود چه باید کرد؟ (مثلا کاربری را در همین لحظه غیرفعال کردید)
یک راه حل آن، ثبت رکورد‌های تمام توکن‌های صادر شده در بانک اطلاعاتی است. برای این منظور می‌توان یک از فیلد jti کرد. این idها در بانک اطلاعاتی ذخیره می‌کنیم. به این ترتیب می‌توان بین توکن‌های صادر شده و کاربران و اطلاعات به روز آن‌ها ارتباط برقرار کرد. در این حالت برنامه علاوه بر بررسی امضای توکن می‌تواند به لیست idهای صادر شده و ذخیره شده‌ی در دیتابیس نیز مراجعه کرده و اعتبارسنجی اضافه‌تری را جهت باطل کردن سریع توکن‌ها انجام دهد. هرچند این روش دیگر آنچنان stateless نیست، اما با دنیای واقعی سازگاری بیشتری دارد.


حداکثر امنیت JWTها را چگونه می‌توان تامین کرد؟

- تمام توکن‌های خود را با یک کلید قوی امضا کنید و این کلید تنها باید بر روی سرور ذخیره شده باشد. هر زمانیکه سرور توکنی را از کاربر دریافت می‌کند، این سرور است که باید کار بررسی اعتبار امضای پیام رسیده را بر اساس کلید قوی خود انجام دهد.
- اگر اطلاعات حساسی را در توکن‌ها قرار می‌دهید، باید از JWE یا JSON Web Encryption استفاده کنید، زیرا JWTها صرفا دارای امضای دیجیتال هستند و نه اینکه رمزنگاری شده باشند.
- بهتر است توکن‌ها را از طریق ارتباطات غیر HTTPS، ارسال نکرد.
- اگر از کوکی‌ها برای ذخیره سازی آن‌ها استفاده می‌کنید، از Secure استفاده کنید تا از Cross-Site Scripting XSS attacks در امان باشید.
- مدت اعتبار توکن‌های صادر شده را منطقی انتخاب کنید.


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