API Auth & Securityللموبايل والتكاملات.
مين بيقدر يوصل لإيه، وإزاي تتأكد. أنواع التوكنات والفرق بينها، الـ ACL وحماية الـ endpoints، والأخطاء الأمنية اللي بتتكرّر في تطبيقات الموبايل تحديداً.
أمان الـ API في Magento 2 تلات طبقات: المصادقة بالتوكنات، والتفويض بالـ ACL في webapi.xml، والملكية اللي Magento مابيفحصهاش في كودك ولازم تعملها بـ UserContextInterface. Customer Token لتطبيقات العملاء، Integration Token للأنظمة الخارجية، والـ Admin Token مايتحطش في أي تطبيق عميل. استخدم /me و/mine، وارجّع 404 مش 403، واحسب أي قيمة مالية على السيرفر.
المفاهيم الأساسية
تلات مفاهيم بيتخلطوا كتير، والخلط بينهم بيسبب ثغرات.
أنواع التوكنات
أربع أنواع، وكل واحد لغرض مختلف.
GET /rest/V1/customers/me HTTP/1.1 Host: store.com Authorization: Bearer eyJraWQiOiIxIiwiYWxn... Content-Type: application/json
توكن العميل
الأساس في أي تطبيق موبايل.
curl -X POST https://store.com/rest/V1/integration/customer/token \ -H "Content-Type: application/json" \ -d '{"username":"customer@mail.com","password":"..."}' # الرد: نص التوكن # "eyJraWQiOiIxIiwiYWxnIjoiSFMyNTYifQ..."
# بيانات العميل الحالي curl https://store.com/rest/V1/customers/me \ -H "Authorization: Bearer TOKEN" # سلة العميل الحالي curl https://store.com/rest/V1/carts/mine \ -H "Authorization: Bearer TOKEN"
لاحظ إن الـ endpoints دي مابتاخدش رقم العميل. Magento بيستنتجه من التوكن نفسه. ده بيمنع تغيير الرقم للوصول لبيانات غيرك — لأن مفيش رقم أصلاً تغيّره.
التوكنات ليها مدة انتهاء بتتحدد من: Stores → Configuration → Services → OAuth → Access Token Expiration. الافتراضي ساعة للأدمن وساعة للعميل (بالساعات).
غلط
تخزين عادي (SharedPreferences / UserDefaults) — بيتقرا على جهاز مكسور الحماية.
صح
التخزين الآمن للنظام — Keychain في iOS وEncryptedSharedPreferences في Android.
الـ Integration Tokens
للأنظمة الخارجية — سيرفر لسيرفر.
توكن دائم (مالوش انتهاء) بصلاحيات محددة مسبقاً، بيتعمل من الأدمن لنظام خارجي معيّن — ERP، نظام شحن، أداة تسويق.
- System → Extensions → Integrations ثم Add New Integration.
- اكتب اسم واضح — اسم النظام المستخدم مش "integration 1".
- من تبويب API، اختار الصلاحيات المطلوبة بس.
- احفظ، وبعدين اضغط Activate عشان تاخد التوكن.
<?xml version="1.0"?> <config> <integration name="ERP Sync"> <email>erp@company.com</email> </integration> </config>
<?xml version="1.0"?> <config> <integration name="ERP Sync"> <resources> <!-- الصلاحيات المطلوبة بس --> <resource name="Magento_Sales::sales"/> <resource name="Magento_Sales::actions_view"/> </resources> </integration> </config>
الصلاحيات بتبقى موثّقة في الـ git وبتتطبّق على كل بيئة. ولو حد وسّع الصلاحيات من الأدمن بالغلط، هتلاحظ الفرق عن الملف.
الـ ACL — الصلاحيات
إزاي تعرّف صلاحيات لموديولك.
شجرة صلاحيات. كل عملية في الأدمن أو الـ API مربوطة بـ resource، وأدوار المستخدمين بتاخد صلاحية على resources محددة.
<?xml version="1.0"?> <config> <acl> <resources> <resource id="Magento_Backend::admin"> <resource id="Vendor_Module::tickets" title="Tickets" sortOrder="50"> <!-- صلاحية القراءة --> <resource id="Vendor_Module::tickets_view" title="View Tickets" sortOrder="10"/> <!-- صلاحية التعديل — منفصلة --> <resource id="Vendor_Module::tickets_manage" title="Manage Tickets" sortOrder="20"/> </resource> </resource> </resources> </acl> </config>
عشان تقدر تدّي موظف صلاحية يشوف من غير ما يعدّل. لو resource واحد، الاختيار بيبقى "كل حاجة أو لا حاجة" — وده بيخلّي الفرق يدّوا صلاحيات أوسع من اللازم.
<route url="/V1/tickets/:id" method="GET"> <service class="Vendor\Module\Api\TicketRepositoryInterface" method="getById"/> <resources> <!-- لازم صلاحية القراءة --> <resource ref="Vendor_Module::tickets_view"/> </resources> </route>
class Edit extends Action { // Magento بيشيّك عليها تلقائياً قبل execute() public const ADMIN_RESOURCE = 'Vendor_Module::tickets_manage'; public function execute() { // لو وصلنا هنا، الصلاحية اتأكدت } }
حماية الـ endpoints
القواعد اللي تمشي عليها في كل endpoint تعمله.
- مصادقة — مين الطالب؟ (التوكن)
- تفويض — مسموحله بالعملية دي؟ (الـ ACL)
- ملكية — المورد ده بتاعه؟ (كودك إنت)
- تحقق من المدخلات — البيانات سليمة؟
public function getTicket(int $ticketId): TicketInterface { // الـ ACL أكّد إنه عميل مسجّل // بس مين قال إن التذكرة دي بتاعته؟ return $this->ticketRepository->getById($ticketId); } // أي عميل يغيّر الرقم ويقرا تذاكر غيره // GET /V1/tickets/1, /V1/tickets/2, /V1/tickets/3 ...
use Magento\Authorization\Model\UserContextInterface; use Magento\Framework\Exception\NoSuchEntityException; class TicketService { public function __construct( private readonly TicketRepositoryInterface $repository, // بيديك هوية الطالب من التوكن private readonly UserContextInterface $userContext ) {} public function getTicket(int $ticketId): TicketInterface { $ticket = $this->repository->getById($ticketId); $callerId = (int) $this->userContext->getUserId(); $callerType = $this->userContext->getUserType(); // الأدمن يشوف كل حاجة if ($callerType === UserContextInterface::USER_TYPE_ADMIN) { return $ticket; } // العميل يشوف بتاعه بس if ((int) $ticket->getCustomerId() !== $callerId) { // مهم: نفس رسالة "مش موجود" throw new NoSuchEntityException( __('No such entity.') ); } return $ticket; } }
لو رجّعت 403 ممنوع، بتأكّد للمهاجم إن التذكرة دي موجودة فعلاً وبس مش بتاعته. لو رجّعت 404 مش موجود، مابيعرفش يفرّق بين رقم مش موجود ورقم بتاع غيره. ده اسمه منع تعداد الموارد.
مشكلة الـ anonymous
امتى مقبول وامتى كارثة.
<route url="/V1/products/featured" method="GET"> <service class="..." method="getFeatured"/> <resources> <resource ref="anonymous"/> </resources> </route>
مقبول
بيانات الكتالوج العامة — منتجات، أقسام، صفحات محتوى. حاجة أي زائر يشوفها من الموقع أصلاً.
خطر
أي حاجة تخص عميل، أو أي عملية بتكتب بيانات، أو بتكشف معلومات داخلية.
اسأل نفسك: "لو نشرت رابط الـ endpoint ده على تويتر، فيه مشكلة؟" لو الإجابة أيوة، يبقى مينفعش يكون anonymous.
# كل الـ routes المفتوحة في موديولاتك grep -rn -B5 'ref="anonymous"' app/code/ \ --include="webapi.xml" # وفي الموديولات الخارجية كمان grep -rln 'ref="anonymous"' vendor/ \ --include="webapi.xml"
أخطاء تطبيقات الموبايل
الأخطاء اللي بتتكرّر في السياق ده تحديداً.
public function addItem(int $productId, float $price) { $item->setCustomPrice($price); // من التطبيق! // العميل يقدر يبعت price = 0.01 }
public function addItem(int $productId, int $qty) { // السعر بيتقرا من المنتج نفسه $product = $this->productRepository->getById($productId); // Magento بيحسب السعر والخصومات $quote->addProduct($product, new DataObject([ 'qty' => $qty, ])); $quote->collectTotals(); }
أمان GraphQL
مخاطر إضافية مش موجودة في REST.
في REST، كل endpoint بيرجّع شكل ثابت. في GraphQL، العميل بيحدد شكل الرد — وده بيفتح مخاطر جديدة.
public function resolve( Field $field, $context, ResolveInfo $info, array $value = null, array $args = null ) { // التحقق إن العميل مسجّل if (!$context->getExtensionAttributes()->getIsCustomer()) { throw new GraphQlAuthorizationException( __('Authorization required.') ); } // الرقم من السياق مش من الـ args $customerId = $context->getUserId(); return $this->loadDataFor($customerId); }
التقوية والإعدادات
إعدادات بتقلّل المخاطر فوراً.
# هل HTTPS مفروض؟ bin/magento config:show web/secure/use_in_frontend bin/magento config:show web/secure/use_in_adminhtml # مدة التوكنات bin/magento config:show \ oauth/access_token_lifetime/customer # التكاملات النشطة — راجعها # System → Extensions → Integrations
قائمة المراجعة
امشي عليها قبل أي إطلاق.
- محدّد له ACL resource مناسب، مش anonymous بالغلط؟
- فيه فحص ملكية لو بيرجّع بيانات مرتبطة بعميل؟
- بيستخدم /me أو /mine بدل تمرير أرقام؟
- بيرجّع 404 مش 403 للموارد اللي مش بتاعة الطالب؟
- بيتحقق من المدخلات ومابيثقش في أي قيمة مالية جاية من العميل؟
- بيرجّع الحقول المطلوبة بس من غير بيانات داخلية؟
- راجعت كل الـ anonymous في app/code و vendor؟
- التكاملات النشطة كلها مستخدمة فعلاً وبأقل صلاحيات؟
- HTTPS مفروض في كل مكان؟
- الـ 2FA للأدمن مفعّل؟
- مفيش admin token في أي تطبيق عميل؟
- التطبيق بيخزّن التوكن في التخزين الآمن وبيعالج الـ 401؟
أسئلة الإنترفيو
الأمان بيتسأل فيه كتير في المستوى السنيور.
API Auth & Security ✓
دلوقتي فاهم الفرق بين المصادقة والتفويض والملكية، أنواع التوكنات وامتى تستخدم كل واحد، إزاي تحمي الـ endpoints، والأخطاء اللي بتتكرّر في تطبيقات الموبايل.
الخلاصة: الملكية مسؤوليتك مش مسؤولية Magento، وأي قيمة مالية تتحسب على السيرفر، و/me أأمن من تمرير الأرقام.
خلّصت الدرس؟علّمه عشان تتابع تقدّمك في الكورس.
أسئلة شائعة
إيه أنواع التوكنات في Magento 2 وامتى أستخدم كل واحد؟
أربعة: Customer Token لتطبيقات العملاء وصلاحياته محدودة ببيانات العميل، Admin Token للعمليات الإدارية ومينفعش يتحط في أي تطبيق عميل، Integration Token دائم لنظام خارجي زي ERP بصلاحيات محددة من الأدمن، و anonymous من غير توكن للبيانات العامة بس. التوكن بيتبعت في header Authorization: Bearer.
إزاي أجيب Customer Token في Magento 2 REST API؟
POST على /rest/V1/integration/customer/token بالإيميل والباسورد، والرد نص التوكن. بعدها استخدمه في header Authorization: Bearer مع endpoints زي /V1/customers/me و /V1/carts/mine اللي بتستنتج العميل من التوكن من غير ما تمرّر رقم. مدة صلاحيته الافتراضية ساعة من Stores › Configuration › Services › OAuth، والتطبيق لازم يمسك 401 ويعيد المصادقة.
إزاي أمنع عميل من قراءة بيانات عميل تاني في الـ API؟
الـ ACL بيتأكد إن الطالب عميل مسجّل بس، مش إن المورد بتاعه. فحص الملكية مسؤوليتك: احقن UserContextInterface وقارن رقم المالك بـ getUserId()، والأدمن من getUserType() يشوف كل حاجة. لو المورد مش بتاعه ارمي NoSuchEntityException عشان يرجع 404 مش 403 ومايتأكدش إن المورد موجود. والأفضل صيغ /me و/mine اللي مابتاخدش رقم أصلًا.
امتى ينفع الـ route يكون anonymous في webapi.xml؟
لما البيانات عامة فعلًا زي المنتجات والأقسام وصفحات المحتوى، يعني حاجة أي زائر بيشوفها من الموقع أصلًا. أي حاجة تخص عميل أو بتكتب بيانات أو بتكشف حقول داخلية زي التكلفة مينفعش. وحتى للعام حدّد الحقول الراجعة وحط حد للمعدّل. راجع الـ endpoints المفتوحة عندك بـ grep -rn 'ref="anonymous"' app/code vendor --include=webapi.xml.
ليه مينفعش أستخدم Admin Token في تطبيق الموبايل؟
لأن التطبيق بيتنزّل على أجهزة المستخدمين، وأي توكن مدفون جوّاه ممكن يتستخرج بأدوات بسيطة. اللي هياخده هيبقى عنده صلاحيات أدمن كاملة على المتجر، وده اختراق كامل مش ثغرة. الصح Customer Token لكل مستخدم بيتخزّن في Keychain أو EncryptedSharedPreferences، والتطبيق يبعت نوايا زي المنتج والكمية مش حقائق زي السعر.
إزاي أعرّف صلاحيات ACL لموديول في Magento 2؟
في etc/acl.xml تحت Magento_Backend::admin عرّف resources بمعرّف زي Vendor_Module::tickets_view و tickets_manage منفصلين عشان تدّي موظف قراءة من غير تعديل. في webapi.xml اربط كل route بـ <resource ref="..."/>، وفي controllers الأدمن حدّد ADMIN_RESOURCE دايمًا، لأن من غيره Magento بيستخدم قيمة عامة وأي مستخدم أدمن يقدر يفتح الصفحة.
إيه المخاطر الأمنية الخاصة بـ GraphQL في Magento 2؟
الاستعلامات المتداخلة بعمق كبير بتستنزف السيرفر فحدّد العمق والتعقيد، والـ introspection بيكشف الـ schema كامل فعطّله في production، والـ N+1 في الـ resolvers بيتحوّل لباب استنزاف، وتسريب حقول داخلية. في الـ resolver خد رقم العميل من $context->getUserId() مش من $args، واتأكد من getIsCustomer() وإلا ارمي GraphQlAuthorizationException.