مقدمة
يستند هذا المقال إلى مستودعي على GitHub ويصف القالب الذي أنشأته لـ .NET، والدافع وراءه، وما يأتي بعده.
حاولت قبل هذا المقال أن أكتب سلسلة مقالات وصفت فيها تنفيذ IdentityServer، لكنها لم تسر بشكل جيد، خاصة أنني لم يكن لدي الوقت الكافي لكتابتها فجاءت ناقصة، ولم أنجز سوى مقال واحد من تلك السلسلة.
هذا ما دفعني لكتابة هذا المقال، لكنني لم أرغب في مقال تقليدي.
أردت مقالًا يصف المستودع، وفي الوقت نفسه يتيح لي فرصة الحصول على ملاحظات حول خيارات التنفيذ التي اتخذتها، لأنه لنكن صادقين لا أحد كاملًا والجميع يرتكب أخطاء، وأودّ الحصول على بعض المشكلات (issues) على المستودع حتى نتمكن جميعًا من العمل عليها وتحسينها.
لذا سيصف هذا المقال ما تم تنفيذه، وما هي الميزات والتحسينات التي سنحاول تقديمها مستقبلًا.
الأدوات
طُوّر هذا المشروع باستخدام VS Code فقط، لذا تم تكييف كل الأدوات لتناسب احتياجات VS Code بحيث تعمل بسلاسة مع الأدوات الأخرى.
التصحيح (Debugging)
كان أحد أهدافي الرئيسية إعداد إعادة التحميل الفوري (hot reload) مع Docker Compose وواجهة سطر أوامر .NET، لأن أحد أهم الأمور بالنسبة لي هو التصحيح داخل بيئة معزولة بالحاويات (containerized)، بحيث تُجرَّد اعتماديات النظام الموجودة في كل جهاز، وتُمنح تجربة تطوير موحدة للجميع.
لذلك أنشأت سكريبت Compose وDockerfile للتصحيح، وفي كل مرة يحدث فيها تغيير يُعاد تحميل التطبيق فوريًا، فلا تحتاج لإعادة تشغيله يدويًا في كل مرة، إذ يستخدم هذا الإعداد dotnet watch داخل الحاوية.
تنسيق الكود
عند العمل ضمن فرق أكبر، يصبح تنسيق الكود أمرًا مهمًا جدًا، ولم أجد حلولًا جيدة لـ .NET في VS Code، مثل Prettier.
وجدت حلًا بديلًا باستخدام مكتبة Csharper، وفي الـ README ستجد تفاصيل التنفيذ. باختصار، نستخدم csharper الذي ينسّق الكود بطريقة مشابهة لـ Prettier، لكن يجب تشغيل أمر يدويًا لذلك، فثبّتنا Run on Save، وهو امتداد لـ VS Code يشغّل csharper لتنسيق الكود لدينا.
إعداد الحل والمشروع
بُني الحل بالكامل مع مراعاة بنية CLEAN، ويحتوي الحل على 4 مشاريع:
- Template.Domain
- Template.Application
- Template.Infrastructure
دون الخوض في التفاصيل، وكما تعرف بالفعل من بنية CLEAN، لكل مشروع مسؤولياته الخاصة.
أدوات إضافية
إذا لم تكن بحاجة إلى الحل بالكامل معدًّا مسبقًا بـ Identity وIdentityServer، يمكنك استخدام سكريبت الإعداد الخاص بنا الذي يهيّئ حلًا نظيفًا دون أي تنفيذ، ويمكنك قراءة المزيد عنه في الـ README.
سكريبتات الترحيل (Migration)
في بيئة التطوير، تريد أن تُطبَّق عمليات الترحيل بسهولة عبر الكود، وقد تم تنفيذ ذلك ولكن فقط في بيئة التطوير؛ أما في أي بيئة أخرى فتريد تطبيق عمليات الترحيل يدويًا، ولهذا أضفت أيضًا سكريبتات لإضافة عمليات الترحيل وتطبيقها، وتفاصيل التنفيذ متوفرة هنا.
شهادات SSL
بما أننا لا نستخدم Visual Studio ولدينا Nginx كبوابة إلى واجهة برمجة التطبيقات، كنا بحاجة إلى شهادات SSL حتى يعمل كل شيء بشكل صحيح، فأنشأت تلك الشهادات وتُولَّد باستخدام سكريبت certificates.sh عند تشغيل docker compose، ويمكنك الاطلاع على تفاصيل التنفيذ هنا.
Identity على ASP.NET Core وDuende IdentityServer
نفّذ المستودع Identity لإدارة المستخدمين، لكن عملية المصادقة الفعلية مُفوَّضة إلى IdentityServer، بحيث يمكنك استخدام هذا التنفيذ كخادم تخويل (Authorization Server) فعلي لديك؛ وهو حاليًا غير جاهز للإنتاج لكنه سيكون كذلك مستقبلًا.
لن أخوض هنا في تفاصيل تنفيذ الكود بالكامل، ولذلك يمكنك الاطلاع على المقال الذي كتبته سابقًا حيث أصف التفاصيل وأقدّم مراجع للموارد.
إدارة المستخدمين باستخدام ASP.NET Core Identity
نفّذت نقاط النهاية (endpoints) لـ:
- إنشاء مستخدم (وهذا يرسل بريدًا إلكترونيًا للتأكيد)
- التحقق من عنوان البريد الإلكتروني
- إعادة إرسال بريد التأكيد
- نسيان كلمة المرور (يرسل بريدًا إلكترونيًا للمستخدم)
- إعادة تعيين كلمة المرور (يستخدم الرمز من البريد الإلكتروني المُرسَل لإعادة تعيين كلمة المرور للمستخدم)
- تغيير كلمة المرور (فقط للمستخدمين المسجَّلين للدخول)
- حذف المستخدم
يمكنك تعديل سلوك ASP.NET Core Identity في IdentityConfiguration.cs، حيث يمكنك الوصول إلى:
- عمليات الترحيل التي تُشغَّل لإنشاء مستخدم Identity
- تحديد السياسات (Policies) التي ستُستخدَم على معدِّلات التخويل (Authorization Decorators) في المتحكمات (Controllers) لديك
- ضبط مدة رمز التحقق من البريد الإلكتروني
- سلوكيات أخرى لـ Identity
التخويل باستخدام Duende IdentityServer
نفّذت oAuth 2 مع IdentityServer، لذا إن احتجت لذلك، يمكنك استخدام هذا المستودع كخادم التخويل الرئيسي لديك، وستُضاف ميزات إضافية مستقبلًا، مثل التكامل مع مزوّدي مصادقة خارجيين.
حاليًا يمكنك عرض التوثيق عند الانتقال إلى:
https://localhost/.well-known/openid-configurationلإعادة تهيئة الموارد (Resources) والعملاء (Clients) والنطاقات (Scopes)، انتقل إلى IdentityServerResourceConfiguration.cs حيث يمكنك إضافة عملائك المخصَّصين وتهيئة الموارد حسب احتياجاتك.
لتخصيص سلوك IdentityServer، انتقل إلى IdentityServerConfiguration.cs، حيث يمكنك تهيئة DbContext الخاصة بـ PersistedGrant وConfiguration، ونوع مستخدم Identity، وخدمة الملف الشخصي (Profile Service)، وأشياء أخرى كثيرة.
تهيئة المُصدِر (Issuer) والسلطة (Authority)
أنجزنا كل شيء حتى هذه اللحظة، لكن تبقى مشكلة واحدة لن تكتشفها حتى تحاول التخويل باستخدام الرمز الصادر لك.
المشكلة تكمن في مُصدِر الرمز (Token Issuer). عندما تصل إلى مستند الاكتشاف (Discovery Document) عبر المتصفح، سترى أن المُصدِر هو localhost، وهذا صحيح، وسيصدر الرمز الذي ستحصل عليه بهذا المُصدِر.
لكن عندما تحاول الوصول إلى نقطة نهاية مخوَّلة، ستحصل على خطأ 401 Unauthorized لأن المُصدِر غير صالح:
تحدث هذه المشكلة لأنه عند الوصول إلى مستند الاكتشاف عبر المتصفح، يرث IdentityServer المُصدِر تلقائيًا من الرابط.
لكن عندما تحتاج طبقة وسيطة التخويل (Authorization Middleware) إلى التحقق من الرمز، فإنها تطلب مستند الاكتشاف من داخل الحاوية. وكما عرّفنا في ملف compose، خدمتنا تُسمى api وتُعرَض على المنفذ 5000.
لذا للحصول على مستند الاكتشاف نحتاج إلى استدعاء http://api:5000/.well-known/openid-configuration، وهذا يعني أيضًا أن المُصدِر سيُورَث من هذا الرابط ولن يكون مطابقًا لرمزنا.
الآن لإصلاح هذا المُصدِر، ستحتاج إلى ضبط خاصيتين. الأولى هي المُصدِر، وستضبطها في IdentityServerConfiguration.cs. اضبط IssuerUri على الرابط الفعلي لديك (مثال http://localhost):
options.IssuerUri = settings.IdentityServerConfig.IssuerUri;بمجرد تهيئة هاتين الخاصيتين، حتى في بيئة الحاويات، سيكون المُصدِر لديك localhost وسيكون الرمز صالحًا. الآن عند إجراء طلب curl من داخل الحاوية ستحصل على نفس المُصدِر كما لو وصلت إليه من المتصفح:
الخلاصة
هذه مجرد البداية لهذا المستودع القالب، لأنني أخطط مستقبلًا للتوسع بتحسينات وإصلاحات وتعديلات، وهناك بالفعل مشكلات (issues) تم إنشاؤها، فإذا أردت المساهمة يمكنك ذلك.
إذا كانت لديك اقتراحات أو أفكار أو ترغب ببساطة في المساهمة في المستودع، فلا تتردد في ذلك.