التفويض
OAuth 2.1 مع PKCE، والرموز، ومنحة التحديث، والإلغاء، وجدول الأخطاء.
في هذه الصفحة
هذه الصفحة لمن ينفّذ تدفق تثبيت التطبيق. في نهايتها تعرف كل معامل في رابط الموافقة، وشكل الرموز وأعمارها، وكيف تجدّدها وتتعامل مع إلغائها، وماذا يعني كل خطأ.
النموذج#
- تطبيقك عميل سرّي: يقدّم
client_idوclient_secretعند تبديل الرمز ويستخدم PKCE (S256) في الوقت نفسه. الشرطان معاً، لا أحدهما. - كل تثبيت يربط تطبيقاً بمتجر واحد ويصدر رموزه الخاصة. لا يوجد رمز يغطي أكثر من متجر.
- النطاقات الفعلية لأي طلب هي: النطاقات الممنوحة عند الموافقة ∩ صلاحيات العضو المانح الحالية. إن فقد العضو صلاحية، فقدها تطبيقك في الطلب التالي مباشرة.
رابط الموافقة#
GET https://dukkan.one/apps/oauth/authorize| المعامل | مطلوب | القيمة |
|---|---|---|
response_type |
نعم | code |
client_id |
نعم | معرّف تطبيقك |
redirect_uri |
نعم | رابط مسجّل في التطبيق بالحرف |
scope |
نعم | نطاقات مفصولة بمسافة، ضمن ما يطلبه الإصدار النشط من تطبيقك؛ ما عداها يُهمل، وقائمة فارغة خطأ |
state |
موصى به | قيمة عشوائية حتى 512 حرفاً، تعود كما هي |
code_challenge |
نعم | BASE64URL(SHA256(code_verifier)) |
code_challenge_method |
لا | S256 فقط (الافتراضي) |
install_token |
لا | رمز توزيع خاص، عند تثبيت تطبيق غير منشور في السوق. راجع دورة حياة التطبيق |
يعيد الخادم خطأ HTTP مباشراً — لا إعادة توجيه — عندما لا يمكنه الوثوق برابط الإعادة: 400 لعميل مجهول أو redirect_uri غير مسجّل، و403 لتطبيق غير متاح للتثبيت. بعد ذلك تُعاد كل الأخطاء إلى redirect_uri بمعامل error (انظر الأخطاء).
شاشة الموافقة#
يرى التاجر كل نطاق جملة عربية واضحة (مثل «قراءة الطلبات وبنودها وحالة التجهيز»). تُميَّز النطاقات المالية بلون تحذيري، ويحمل orders:create العبارة «تظهر هذه الطلبات في لوحة التحكم موسومة باسم التطبيق، ويُخصم المخزون كأي طلب آخر.»، ويظهر clients:read وclients:write في قسم منفصل غير محدد افتراضياً لأنهما يكشفان بيانات العملاء. يمكن للتاجر قبول جزء من النطاقات، فاقرأ scope من استجابة الرمز ولا تفترض ما طلبته.
تبديل الكود برموز#
POST https://dukkan.one/apps/oauth/tokenأرسل بيانات العميل في ترويسة Authorization: Basic (أو في الجسم كـ client_id وclient_secret) والجسم بصيغة application/x-www-form-urlencoded:
import { exchangeCode } from "@dukkan.one/app-sdk/oauth";
const tokens = await exchangeCode({
credentials: { clientId: CLIENT_ID, clientSecret: CLIENT_SECRET },
code: AUTH_CODE,
redirectUri: "https://app.example.com/callback",
codeVerifier: PKCE_VERIFIER,
});curl -X POST https://dukkan.one/apps/oauth/token \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code=AUTH_CODE \
-d redirect_uri=https://app.example.com/callback \
-d code_verifier=PKCE_VERIFIER{
"access_token": "dk_app_at_REDACTED_EXAMPLE_TOKEN",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dk_app_rt_REDACTED_EXAMPLE_TOKEN",
"scope": "orders:read orders:write",
"install_id": "8b2c1d4e-5f60-4a7b-8c9d-0e1f2a3b4c5d",
"store_id": "3065a1f2-0c4d-4e8b-b7a9-5d2f8c1e9a44",
"store_slug": "sham-perfumes"
}| الحقل | المعنى |
|---|---|
access_token |
dk_app_at_…، حامل صالح لساعة |
expires_in |
3600 ثانية دائماً |
refresh_token |
dk_app_rt_…، صالح 30 يوماً، يتدوّر مع كل استخدام |
scope |
النطاقات التي منحها التاجر فعلاً، مفصولة بمسافة |
install_id |
التثبيت الذي يعود إليه هذا الرمز. اجعله مفتاح تخزينك؛ وهو ما يحمله كل غلاف Webhook |
store_id |
المتجر المرتبط بالتثبيت؛ القيمة نفسها التي يعيدها GET /installation وكل غلاف |
store_slug |
المعرّف العام للمتجر، للعرض فقط. قد يتغيّر؛ لا تربط بياناتك به أبداً |
الكود صالح للاستخدام مرة واحدة. أي محاولة لإعادة استخدام كود مستهلَك تلغي كل رموز ذلك التثبيت (حماية من السرقة، وفق RFC 6749).
يعيد GET /installation الهوية نفسها لاحقاً، مع النطاقات الفعلية، والأس العشري لعملة المتجر ولغته ومنطقته الزمنية، واشتراك Webhooks الحالي؛ راجع مرجع التثبيت.
منحة التحديث#
curl -X POST https://dukkan.one/apps/oauth/token \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-d grant_type=refresh_token \
-d refresh_token=YOUR_REFRESH_TOKEN{
"access_token": "dk_app_at_REDACTED_NEW_TOKEN",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dk_app_rt_REDACTED_NEW_TOKEN",
"scope": "orders:read orders:write",
"install_id": "8b2c1d4e-5f60-4a7b-8c9d-0e1f2a3b4c5d",
"store_id": "3065a1f2-0c4d-4e8b-b7a9-5d2f8c1e9a44",
"store_slug": "sham-perfumes"
}- تعيد كل عملية تحديث زوجاً جديداً؛ احفظ الرمزين معاً ذرياً وتخلّص من القديمين. تحمل استجابة التحديث أيضاً
scopeوinstall_idوstore_idوstore_slug. - إعادة استخدام رمز تحديث سبق تدويره تُفسَّر سرقةً: تُلغى كامل سلسلة الرموز للتثبيت، ويعود الطلب
invalid_grant. يجب حينها إعادة التاجر إلى رابط الموافقة. - استثناء واحد يحمي التطبيقات متعددة النسخ: تقديم رمز دُوِّر قبل أقل من 30 ثانية بينما خلفه ما زال صالحاً يُعامل كسباق بين نسختين من تطبيقك. يعود الطلب
invalid_grantدون إلغاء أي شيء؛ أعد قراءة مخزن الرموز واستخدم الزوج الذي حفظته النسخة الأخرى. ومع ذلك، سلسِل عمليات التحديث لكل تثبيت. - لا تحدّث قبل انتهاء رمز الوصول بكثير؛ حدّث عند
401أو قبل الانتهاء بدقائق.
بادئات الرموز#
| البادئة | الرمز | العمر |
|---|---|---|
dk_app_at_ |
رمز وصول التطبيق | ساعة |
dk_app_rt_ |
رمز تحديث التطبيق | 30 يوماً، يتدوّر |
dk_whsec_ |
مفتاح توقيع Webhooks | حتى تدوّره عبر PUT /webhooks |
dk_cli_at_ / dk_cli_rt_ |
رموز أداة الثيمات | ساعة / 30 يوماً |
كل الرموز تُخزَّن على الخادم كتجزئة SHA-256 فقط ولا يمكن استرجاعها.
الإلغاء#
- عندما يلغي التاجر التثبيت تُلغى رموزه فوراً، ويصلك حدث
app.uninstalled— بلا نطاق مطلوب — حتى بعد الإلغاء. عالِجه بحذف بيانات ذلك المتجر. - عند إيقاف تطبيقك (بقرارك أو بقرار دكان) تُلغى الرموز وتُعطَّل الاشتراكات؛ راجع دورة حياة التطبيق.
- استدعاء واجهة المنصّة برمز ملغى أو منتهٍ يعيد
401بجسمerrorورمزunauthorized.
الأخطاء#
خطأ رابط الموافقة يعود إلى redirect_uri كمعامل error (مع state)؛ خطأ نقطة الرمز يعود جسم JSON { "error": "…" }.
| الخطأ | أين | السبب |
|---|---|---|
unsupported_response_type |
authorize | response_type ليس code |
invalid_request |
authorize | code_challenge غير صالح أو الطريقة ليست S256 |
invalid_scope |
authorize | لم يبقَ أي نطاق صالح بعد الترشيح على نطاقات الإصدار النشط |
invalid_client (401) |
token | client_id أو client_secret خاطئ، أو التطبيق موقوف |
invalid_grant (400) |
token | كود منتهٍ أو مستهلَك أو غير مطابق (redirect_uri، code_verifier، العميل)، أو رمز تحديث منتهٍ/ملغى/مُعاد استخدامه |
unsupported_grant_type (400) |
token | grant_type ليس authorization_code ولا refresh_token |
الحدود: 60 طلباً كل 10 دقائق لكل عنوان IP على النقطتين، و600 طلب كل 10 دقائق لكل عميل على نقطة الرمز. التجاوز يعيد 429.
قائمة الأمان#
- ولّد
stateعشوائياً لكل جلسة وتحقّق منه عند العودة. - خزّن
code_verifierفي جلسة الخادم، لا في المتصفح. - خزّن المفتاح السري ورموز التحديث مشفّرة على الخادم فقط؛ لا تُرسل أي رمز إلى العميل.
- جدول تدوير المفتاح السري بنافذة تداخل (24 ساعة افتراضياً) موصوف في دورة حياة التطبيق.
- عامل
invalid_grantعند التحديث كإشارة لإعادة التفويض، لا لإعادة المحاولة.