گزینههای احراز هویت
پیش از شروع نوشتن کد، بیایید درباره نحوه احراز هویت درخواستهای API خود و شناسایی کاربر درخواستکننده صحبت کنیم.
انتخاب یک رویکرد سطح بالا برای احراز هویت API میتواند دشوار باشد — گزینههای مختلف زیادی وجود دارد و همیشه مشخص نیست کدام یک برای پروژه شما مناسب است. بنابراین در این فصل برخی از رایجترین رویکردها را در سطح بالا بررسی میکنیم، مزایا و معایب نسبی آنها را بحث میکنیم و با برخی دستورالعملهای کلی درباره زمان استفاده مناسب از آنها به پایان میرسیم.
به طور خاص، پنج رویکردی که مقایسه خواهیم کرد عبارتند از:
- Basic authentication
- Stateful token authentication
- Stateless token authentication
- API key authentication
- OAuth 2.0 / OpenID Connect
HTTP basic authentication
شاید سادهترین روش برای تشخیص چه کسی درخواستی به API شما ارسال میکند، استفاده از HTTP basic authentication باشد.
در این روش، کلاینت در هر درخواست یک هدر Authorization شامل اعتبارنامههای خود ارسال میکند. اعتبارنامهها باید در فرمت username:password و به صورت base64 رمزگذاری شده باشند. بنابراین، به عنوان مثال، برای احراز هویت با alice@example.com:pa55word کلاینت هدر زیر را ارسال میکند:
Authorization: Basic YWxpY2VAZXhhbXBsZS5jb206cGE1NXdvcmQ=
در API خود، میتوانید اعتبارنامهها را از این هدر با استفاده از متد Request.BasicAuth() Go استخراج کنید و قبل از ادامه پردازش درخواست، صحت آنها را تأیید کنید.
یک مزیت بزرگ HTTP basic authentication سادگی آن برای کلاینتهاست. آنها میتوانند همان هدر را در هر درخواست ارسال کنند — و HTTP basic authentication به صورت پیشفرض توسط اکثر زبانهای برنامهنویسی، مرورگرهای وب و ابزارهایی مانند curl و wget پشتیبانی میشود.
این روش اغلب در سناریوهایی مفید است که API شما حسابهای کاربری «واقعی» ندارد، اما میخواهید راهی سریع و آسان برای محدود کردن دسترسی به آن یا محافظت از آن در برابر دیدهای ناخواسته داشته باشید.
برای APIهایی با حسابهای کاربری «واقعی» و به ویژه رمزهای عبور هششده، این روش چندان مناسب نیست. مقایسه رمز عبور ارائه شده توسط کلاینت با یک رمز عبور هششده (کند) یک عملیات عمداً پرهزینه است و هنگام استفاده از HTTP basic authentication باید این بررسی را برای هر درخواست انجام دهید. این کار کار اضافی زیادی برای سرور API شما ایجاد میکند و تأخیر قابل توجهی به پاسخها اضافه میکند.
اما حتی در این صورت، basic authentication میتواند انتخاب خوبی باشد اگر ترافیک API شما بسیار کم است و سرعت پاسخ برای شما مهم نیست.
Token authentication
ایده اصلی پشت token authentication (که گاهی bearer token authentication نیز نامیده میشود) به این صورت است:
کلاینت درخواستی به API شما ارسال میکند که حاوی اعتبارنامههای آن (معمولاً نام کاربری یا آدرس ایمیل و رمز عبور) است.
API اعتبارنامهها را تأیید میکند، یک bearer token که نمایانگر کاربر است تولید میکند و آن را به کاربر بازمیگرداند. توکن پس از یک بازه زمانی مشخص منقضی میشود و پس از آن کاربر باید مجدداً اعتبارنامههای خود را ارسال کند تا توکن جدیدی دریافت کند.
برای درخواستهای بعدی به API، کلاینت توکن را در هدر
Authorizationبه این صورت شامل میکند:Authorization: Bearer <token>
وقتی API شما این درخواست را دریافت میکند، بررسی میکند که توکن منقضی نشده باشد و مقدار توکن را بررسی میکند تا هویت کاربر را تعیین کند.
برای APIهایی که رمزهای عبور کاربران هششده هستند (مانند ما)، این رویکرد بهتر از basic authentication است زیرا به این معنی است که بررسی کند رمز عبور فقط باید به صورت دورهای انجام شود — یا هنگام ایجاد توکن برای اولین بار یا پس از انقضای توکن.
نکته منفی این است که مدیریت توکنها میتواند برای کلاینتها پیچیده باشد — آنها باید منطق لازم برای کش کردن توکنها، نظارت و مدیریت انقضای توکن و تولید دورهای توکنهای جدید را پیادهسازی کنند.
میتوانیم token authentication را بیشتر به دو زیرمجموعه تقسیم کنیم: stateful و stateless token authentication. این دو از نظر مزایا و معایب کاملاً متفاوت هستند، بنابراین بیایید هر کدام را جداگانه بررسی کنیم.
Stateful token authentication
در رویکرد stateful token، مقدار توکن یک رشته تصادفی با آنتروپی بالا و امنیت رمزنگاری است. این توکن — یا یک هش سریع از آن — در سمت سرور در یک دیتابیس همراه با شناسه کاربر و زمان انقضای توکن ذخیره میشود.
وقتی کلاینت توکن را در درخواستهای بعدی ارسال میکند، برنامه شما میتواند توکن را در دیتابیس جستجو کند، بررسی کند که منقضی نشده باشد و شناسه کاربر مربوطه را برای شناسایی فرستنده درخواست بازیابی کند.
مزیت بزرگ این است که API شما کنترل توکنها را حفظ میکند — لغو توکنها به صورت توکن-به-توکن یا کاربر-به-کاربر با حذف آنها از دیتابیس یا علامتگذاری آنها به عنوان منقضی شده، ساده است.
از نظر مفهومی نیز ساده و قوی است — امنیت توسط «غیرقابل حدس بودن» توکن فراهم میشود، به همین دلیل استفاده از مقدار تصادفی با آنتروپی بالا و امنیت رمزنگاری برای توکن مهم است.
خوب، معایب چیست؟
به جز ناراحتی کلاینتها در مدیریت توکنها، به سختی میتوان انتقاد زیادی به این رویکرد وارد کرد. شاید نیاز به جستجو در دیتابیس یک نکته منفی باشد — اما در اکثر موارد شما در هر صورت باید برای بررسی وضعیت فعالسازی کاربر یا بازیابی اطلاعات بیشتر درباره آنها به دیتابیس مراجعه کنید.
Stateless token authentication
در مقابل، stateless tokenها شناسه کاربر و زمان انقضا را در خود توکن رمزگذاری میکنند. توکن به صورت رمزنگاری امضا شده تا از دستکاری جلوگیری شود و (در برخی موارد) رمزگذاری شده تا از خوانده شدن محتوا جلوگیری شود.
چندین فناوری مختلف وجود دارد که میتوانید برای ایجاد stateless token از آنها استفاده کنید. رمزگذاری اطلاعات در یک JWT (JSON Web Token) احتمالاً شناختهشدهترین رویکرد است، اما PASETO، Branca و nacl/secretbox نیز جایگزینهای مناسبی هستند. اگرچه جزئیات پیادهسازی این فناوریها متفاوت است، مزایا و معایب کلی از نظر احراز هویت مشابه هستند.
مزیت اصلی استفاده از stateless tokenها برای احراز هویت این است که رمزگذاری و رمزگشایی توکن میتواند در حافظه انجام شود و تمام اطلاعات لازم برای شناسایی کاربر در خود توکن موجود است. نیازی به جستجو در دیتابیس برای شناسایی فرستنده درخواست نیست.
نکته منفی اصلی stateless tokenها این است که پس از صدور نمیتوان آنها را به راحتی لغو کرد.
در شرایط اضطراری، میتوانید با تغییر secret مورد استفاده برای امضای توکنها، تمام توکنها را به طور مؤثر لغو کنید (و همه کاربران را مجبور به احراز هویت مجدد کنید)، یا راه حل دیگر نگهداری یک لیست مسدود از توکنهای لغو شده در دیتابیس است (اگرچه این کار جنبه «stateless» بودن stateless tokenها را از بین میبرد).
در نهایت، به ویژه با JWTها، این واقعیت که بسیار قابل پیکربندی هستند به این معنی است که چیزهای زیادی وجود دارد که میتوانید اشتباه کنید. مقالات Critical vulnerabilities in JSON Web Token libraries و JWT Security Best Practices معرفی خوبی از نوع مسائلی هستند که باید در اینجا مراقب آنها باشید.
به دلیل این معایب، stateless tokenها و به ویژه JWTها، به طور کلی بهترین انتخاب برای مدیریت احراز هویت در اکثر برنامههای API نیستند.
اما آنها میتوانند در سناریویی بسیار مفید باشند که به احراز هویت تفویض شده نیاز دارید — جایی که برنامه ایجاد کننده توکن احراز هویت با برنامه مصرف کننده آن متفاوت است و آن برنامهها هیچ state مشترکی ندارند (که به این معنی است استفاده از stateful tokenها گزینه نیست). به عنوان مثال، اگر در حال ساخت سیستمی هستید که در پشت صحنه معماری microservice دارد، توکن stateless ایجاد شده توسط یک سرویس «احراز هویت» میتواند در ادامه به سرویسهای دیگر منتقل شود تا کاربر را شناسایی کند.
API-key authentication
ایده پشت API-key authentication این است که کاربر یک secret «کلید» بدون انقضا مرتبط با حساب خود دارد. این کلید باید یک رشته تصادفی با آنتروپی بالا و امنیت رمزنگاری باشد و یک هش سریع از کلید (SHA256 یا SHA512) باید همراه با شناسه کاربر مربوطه در دیتابیس شما ذخیره شود.
سپس کاربر کلید خود را با هر درخواست به API شما در هدری مانند زیر ارسال میکند:
Authorization: Key <key>
پس از دریافت آن، API شما میتواند هش سریع کلید را بازتولید کند و از آن برای جستجوی شناسه کاربر مربوطه در دیتابیس استفاده کند.
از نظر مفهومی، این تفاوت زیادی با رویکرد stateful token ندارد — تفاوت اصلی این است که کلیدها کلیدهای دائمی هستند، نه توکنهای موقت.
از یک طرف، این برای کلاینت خوب است زیرا میتوانند از همان کلید برای هر درخواست استفاده کنند و نیازی به نوشتن کد برای مدیریت توکنها یا انقضا ندارند. از طرف دیگر، کاربر اکنون دو secret بلندمدت برای مدیریت دارد که میتواند حساب او را به خطر بیندازد: رمز عبور و API key او.
پشتیبانی از API keyها همچنین پیچیدگی اضافی به برنامه API شما اضافه میکند — باید راهی برای بازتولید API key کاربران در صورت گم شدن یا به خطر افتادن کلید داشته باشید و ممکن است بخواهید از API keyهای متعدد برای یک کاربر پشتیبانی کنید تا بتوانند از کلیدهای مختلف برای اهداف مختلف استفاده کنند.
همچنین مهم است توجه داشته باشید که API keyها خود باید فقط از طریق یک کانال امن به کاربران ارتباط داده شوند و باید با همان سطح مراقبتی که رمز عبور کاربر را مدیریت میکنید، آنها را مدیریت کنید.
OAuth 2.0 / OpenID Connect
گزینه دیگر استفاده از OAuth 2.0 برای احراز هویت است. در این رویکرد، اطلاعات کاربران (و رمزهای عبور آنها) توسط یک identity provider شخص ثالث مانند Google یا Facebook ذخیره میشود، نه توسط شما.
اولین نکتهای که باید ذکر شود این است که OAuth 2.0 یک پروتکل احراز هویت نیست و نباید واقعاً از آن برای احراز هویت کاربران استفاده کنید. وبسایت oauth.net مقاله عالی توضیح این موضوع را دارد و من شدیداً توصیه میکنم آن را بخوانید.
اگر میخواهید بررسیهای احراز هویت را در برابر یک identity provider شخص ثالث پیادهسازی کنید، باید از OpenID Connect (که مستقیماً بر روی OAuth 2.0 ساخته شده) استفاده کنید.
مرور کاملی از OpenID Connect اینجا وجود دارد، اما در سطح بسیار بالا به این صورت کار میکند:
- وقتی میخواهید درخواستی را احراز هویت کنید، کاربر را به فرم «احراز هویت و رضایت» میزبانی شده توسط identity provider هدایت میکنید.
- اگر کاربر رضایت دهد، identity provider یک authorization code به API شما ارسال میکند.
- سپس API شما authorization code را به endpoint دیگری ارائه شده توسط identity provider ارسال میکند. آنها authorization code را تأیید میکنند و اگر معتبر باشد، پاسخ JSON حاوی ID token برای شما ارسال میکنند.
- این ID token خود یک JWT است. باید این JWT را اعتبارسنجی و رمزگشایی کنید تا اطلاعات واقعی کاربر را دریافت کنید، که شامل مواردی مانند آدرس ایمیل، نام، تاریخ تولد، منطقه زمانی و غیره است.
- اکنون که میدانید کاربر کیست، میتوانید الگوی توکن احراز هویت stateful یا stateless را پیادهسازی کنید تا مجبور نباشید کل فرآیند را برای هر درخواست بعدی تکرار کنید.
مانند تمام گزینههای دیگری که بررسی کردیم، استفاده از OpenID Connect مزایا و معایبی دارد. مزیت بزرگ این است که نیازی به ذخیره دائمی اطلاعات کاربر یا رمزهای عبور ندارید. نکته منفی بزرگ این است که بسیار پیچیده است — اگرچه پکیجهای کمکی مانند coreos/go-oidc وجود دارند که کار خوبی در پنهان کردن این پیچیدگی و ارائه رابط ساده برای گردش کار OpenID Connect انجام میدهند.
همچنین مهم است اشاره شود که استفاده از OpenID Connect نیاز دارد همه کاربران شما حسابی با identity provider داشته باشند و مرحله «احراز هویت و رضایت» نیاز به تعامل انسانی از طریق مرورگر وب دارد — که احتمالاً اگر API شما backend یک وبسایت است مشکلی ندارد، اما ایدهآل نیست اگر API «مستقل» با کلاینتهایی از نوع برنامههای کامپیوتری باشد.
از کدام رویکرد احراز هویت باید استفاده کنم؟
ارائه راهنمایی قاطع درباره اینکه کدام رویکرد احراز هویت برای API شما بهترین است، دشوار است. مانند اکثر موارد در برنامهنویسی، ابزارهای مختلف برای کارهای مختلف مناسب هستند.
اما به عنوان قواعد کلی ساده و تقریبی:
- اگر API شما حسابهای کاربری «واقعی» با هشهای کند رمز عبور ندارد، HTTP basic authentication میتواند انتخاب خوب — و اغلب نادیده گرفته شده —ای باشد.
- اگر نمیخواهید رمزهای عبور کاربران را خودتان ذخیره کنید، همه کاربران شما حساب با یک identity provider شخص ثالث که از OpenID Connect پشتیبانی میکنند دارند، و API شما backend یک وبسایت است… از OpenID Connect استفاده کنید.
- اگر به احراز هویت تفویض شده نیاز دارید، مانند زمانی که API شما معماری microservice با سرویسهای مختلف برای انجام احراز هویت و انجام سایر وظایف دارد، از stateless authentication tokenها استفاده کنید.
- در غیر این صورت از API keyها یا stateful authentication tokenها استفاده کنید. به طور کلی:
- Stateful authentication tokenها انتخاب خوبی برای APIهایی هستند که به عنوان backend یک وبسایت یا application تک صفحهای عمل میکنند، زیرا لحظه طبیعی وجود دارد که کاربر وارد میشود و میتوانند با اعتبارنامههای کاربر مبادله شوند.
- در مقابل، API keyها میتوانند برای APIهای «通用تر» بهتر باشند زیرا دائمی هستند و برای توسعهدهندگان سادهتر است از آنها در برنامهها و اسکریپتهای خود استفاده کنند.
در بقیه این کتاب، احراز هویت را با استفاده از الگوی stateful authentication token پیادهسازی خواهیم کرد. در مورد ما، بخش زیادی از منطق لازم را به عنوان بخشی از کار activation tokenهای خود قبلاً پیادهسازی کردهایم.