رهام رفیعی تهرانی
رهام رفیعی تهرانی
خواندن ۶ دقیقه·۱ سال پیش

تجربه اتصال Angular به identityServer

یک ماه پیش لازم شد اپ انگولار شرکت رو به identityServer وصل کنم تا کاربرها بتونن شماره موبایلشون رو بزنن، بعد یک رمز otp وارد کنند و وارد پروفایل یا قسمت های شخصی خودشون بشن. صفحه لاگین و ورود با otp به صورت custom شده سمت سرور پیاده سازی شده بود. من باید این صفحه customize شده رو داخل یک iframe نمایش میدادم و دخالتی در لاگین کاربر نداشتم تا زمانی که خبردار بشم کاربر لاگین شده.

شروع کردم به جستجو و سرچ . سه کتابخانه مختلف پیشنهادی رو تست کردم و در نهایت کتابخونه oids-client-ts رو انتخاب کردم. به نظر من عملکرد پایدارتر و در عین حال راحت تری نسبت به بقیه کتابخانه ها داره. بریم مرحله به مرحله کار رو ببریم جلو

آشنایی مختصر با کتابخانه oidc-client-ts

توجه: اگر فقط میخواید پیاده سازی کنین و براتون خیلی جزییات مهم نیست، از این بخش مقاله میتونید بگذرید و به بخش بعدی برید.

هدف این بخش، توضیح سه تا مطلب اصلی در کتابخانه oidc-client-ts برای درک بهتر مطالب بعدیه.

  • کلاس User
  • کلاس UserManager
  • تنظیمات مورد نیاز


کلاس User

این کلاس شامل همه اطلاعاتیه که از سرویس دهنده IdentityService در مورد کاربر گرفته میشه:

  • متغیر id_token: یک JSON Web Token هست و تنها زمانی ارائه میشه که اسکوپ openid درخواست شده باشه. اطلاعات کاربر به صورت decode شده در متغیر دیگری بنام profile ذخیره شده.
  • متغیر session_state که میتونه null باشه یا یک عبارت string
  • متغیر access_token که از طرف سرویس دهنده برمیگرده.
  • متغیر refresh_token که از نوع رفرش توکن OAuth2.0 هست و توسط سرویس دهنده برمیگرده.
  • متغیر token_type که معمولا مقدارش Bearer هست.
  • متغیر scope شامل اسکوپ هایی که برای کاربر ولید هستند.
  • متغیر profile شامل اطلاعات کاربر هست.
  • متغیر expired_at که توسط سرویس دهنده ارائه شده و حاوی زمان انقضای ولید بودن کاربر هست.
  • تابع getter بنام expires_in : ثانیه های باقی مانده تا انقضای ولید بودن کاربر رو برمیگردونه
  • تابع getter بنام expired : مشخص میکنه کاربر منقضی شده یا هنوز ولیده
  • تابع getter بنام scopes که آرایه ای از اسکوپ های ولید کاربر رو برمیگردونه


کلاس UserManager

این کلاس یک API سطح بالا برای ارتباط با سرویس دهنده (مدلی از IdentityService) رو پیاده سازی کرده.

کلاس UserManager امکانات زیر رو در اختیار ما قرار میده:

  • امکان Sign in و Sing Out کاربر رو به ما میده
  • دریافت و مدیریت claim های کاربر رو انجام میده
  • مدیریت access token دریافتی از سرویس دهنده رو انجام میده


برای ساخت یک instance از UserManager لازمه که تنظیمات مربوط به سرویس دهنده IdentityService و سایر تنظیمات رو به constructor این کلاس پاس بدیم. اما ... این تنظیمات چی هستند؟


تنظیمات کتابخانه

این کتابخانه یک interface تعریف کرده بنام OidcClientSettings که شامل تنظیمات اصلی ارتباط با سرویس دهنده IdentityService است. همینطور یک interface بنام UserManagerSettings از روی interface اول تعریف کرده و تنظیمات مربوط به امکانات خودش رو اضافه کرده به تنظیمات اصلی.

مهمترین آیتم های موجود در این تنظیمات عبارتند از:

  • آیتم authority : لینک سرویس دهنده IdentitServer
  • آیتم client_id : شناسه تعریف شده کاربر در سرویس دهنده IdentityServer
  • آیتم redirect_uri : لینک عمومی برگشت از سرور که آدرسی از اپ تحت وب هست
  • آیتم response_type : نوع پاسخ سرویس دهنده که بصورت پیش فرض code هست.
  • آیتم scope : شامل scope هایی که کاربر اجازه دسترسی داره و در سرویس دهنده تعریف شده

همینطور من یک آیتم دیگه رو هم استفاده کردم بنام popup_post_logout_redirect_uri که از نوع آیتم هاییست که مربوط به Oidc نیست بلکه از امکانات خود کتابخانه هست و در UserManagerSettings تعریف شده.

بریم سراغ پیاده سازی کتابخانه در برنامه:


مرحله اول: نصب کتابخانه

برای نصب کتابخونه کافیه دستور زیر رو اجرا کنیم و بشینیم فرآیند نصبش رو نگاه کنیم:

$ npm install oidc-client-ts

مرحله دوم: ساخت سرویس برای مدیریت ارتباط با IdentityService

یک فایل auth.service.ts درست میکنیم به همراه کلاس AuthService تا کتابخانه اضافه شدن رو مستقیم داخل اپ نبریم و هر موقع لازم بود بتونیم تغییرش بدیم یا جابجاش کنیم:

$ ng g s services/auth


مرحله سوم: پیاده سازی سرویس AuthService

من برای سرویس AuthService رفتارهای زیر رو در نظر گرفتم:

  • login
  • logout
  • renewToken


برای پیاده سازی سرویس به کلاس های User و UserManager از کتابخانه نیاز داریم:

ابتدا یک متغیر بنام userManager به سرویس اضافه میکنیم که قرار یک instance از کلاس UserManager رو نگه داره.

سپس در تابع constructor سرویس، تنظیمات IdentityServer رو به constructor کلاس UserManager پاس میدیم و یک instance میسازیم و نگهش میداریم:

در ادامه constructor ، تابع UserManager.getUser رو صدا کردم تا مشخص بشه زمانی که access token دریافت شد، چه کارهایی با access token انجام بدم. این کد رو هر جایی از برنامه تون که فکر میکنید لازمه اضافه کنید:

فرایند login:

با فراخوانی تابع signinPopup میشه لاگین مدل popup انجام داد که من در پروژه به همین روش لاگین رو پیاده سازی کردم. درواقع از صفحه ای استفاده کردم که در سرویس دهنده پیاده سازی شده بود.برای لاگین مدل popup میتوان یک div popup ساخت و داخل آن یک iframe به همراه یک id (مثلا login-iframe) تعبیه کرد. سپس به کتابخانه oidc-client-ts، شناسه iframe را ارسال کرد تا صفحه لاگین سرویس دهنده در iframe باز شود:

همچنین میتوان پارامترهای اضافه ای از سمت کلاینت برای سرویس دهنده ارسال کرد تا سرویس دهنده به کمک آنها صفحه ای custom پیاده سازی کند. این بستگی به نحوه پیاده سازی سرویس دهنده دارد.


اما فرایند اینجا تموم نشده. وقتی کار login تموم میشه ، از صفحه لاگین ریدایرکت میکنه به صفحه دیگه ای که اینجا مثلا بهش میگیم redirected-from-login . من این صفحه رو یک کامپوننت خالی در نظر گرفتم. ولی بعد از اینکه ریدایرکت انجام شد، در این صفحه باید تابع signinCallback رو صدا بزنیم وگرنه اطلاعات کاربر درست دست ما نمیرسه.

این مشکل نزدیک به چهار ساعت از وقت من رو گرفت تا متوجه ش شدم:

میتونیم متغیر userManager در AuthService رو private کنیم و یک تابع signinCallback برای AuthService درنظر بگیریم که همین کار رو خودش انجام بده. این روش درست تره ولی من این کار رو نکردم! :)


فرآیند renewToken:

برای افزودن فرایند renewToken به AuthService کافیه این کد رو اضافه کنیم:


فرآیند logout:

برای فرآند logout میتونیم یک iframe کاملا پنهان و دور از نظر کاربر در صفحه تعریف کنیم و نام و شناسه براش تعریف کنیم (مثلا logout-iframe):

سپس فرآیند logout مدل popup رو با فراخوانی کد زیر انجام بدیم:

و فرآیند logout اتفاق می افتد.


مشکل بزرگ در آینده

کتابخانه iodc-client-ts یک مشکل بزرگ دارد که تا این لحظه که من این مقاله رو مینویسم حل نشده. این کتابخانه اطلاعات کاربر رو در localStorage ذخیره میکنه و نام اطلاعاتی که ذخیره میکنه با "oidc." شروع میشه و در ادامه یک hash بهش اضافه میشه. زمانی که logout میکنیم این اطلاعات پاک نمیشه. هر بار که دوباره کاربر میخواهد به سرور وصل شود و لاگین کند اطلاعات جدیدی ذخیره میشود با پیشوند oidc. و hash جدید.

این فرآیند ممکن است به مرور حافظه localStorage رو پر کنه و برنامه از کار بیفته. البته این ایراد گزارش شده ولی هنوز حل نشده. برای همین باید به فکر خالی کردن این اطلاعات بود. تیکه کد زیر رو من از خودم نوشتم و بعد از صدا زدن singoutPopup گذاشتم. این راه حل کار میکنه ولی شاید راه بهتری هم باشه.



نتیجه:

در مجموع اتصال به سرویس IdentityServer برای احزار هویت کاربر بسیار ساده، استاندارد و راحته. حتی وصل شدن با ارسال Username و Password هم از این خیلی خیلی ساده تره. امیدوارم این مقاله براتون مفید باشه‌ :)




oauth2angularjavascriptانگولار
برنامه نویسی یک شغل نیست، یک هنره.
شاید از این پست‌ها خوشتان بیاید