تخطَّ إلى المحتوى

التفويض

OAuth 2.1 مع PKCE، والرموز، ومنحة التحديث، والإلغاء، وجدول الأخطاء.

آخر تحديث 2 أيلول 2026قراءة 4 د
في هذه الصفحة

هذه الصفحة لمن ينفّذ تدفق تثبيت التطبيق. في نهايتها تعرف كل معامل في رابط الموافقة، وشكل الرموز وأعمارها، وكيف تجدّدها وتتعامل مع إلغائها، وماذا يعني كل خطأ.

النموذج#

  • تطبيقك عميل سرّي: يقدّم client_id وclient_secret عند تبديل الرمز ويستخدم PKCE (S256) في الوقت نفسه. الشرطان معاً، لا أحدهما.
  • كل تثبيت يربط تطبيقاً بمتجر واحد ويصدر رموزه الخاصة. لا يوجد رمز يغطي أكثر من متجر.
  • النطاقات الفعلية لأي طلب هي: النطاقات الممنوحة عند الموافقة ∩ صلاحيات العضو المانح الحالية. إن فقد العضو صلاحية، فقدها تطبيقك في الطلب التالي مباشرة.

رابط الموافقة#

HTTP
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 من استجابة الرمز ولا تفترض ما طلبته.

تبديل الكود برموز#

HTTP
POST https://dukkan.one/apps/oauth/token

أرسل بيانات العميل في ترويسة Authorization: Basic (أو في الجسم كـ client_id وclient_secret) والجسم بصيغة application/x-www-form-urlencoded:

TypeScript
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,
});
token-response.jsonJSON
{
  "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 الحالي؛ راجع مرجع التثبيت.

منحة التحديث#

Shell
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
refresh-response.jsonJSON
{
  "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 عند التحديث كإشارة لإعادة التفويض، لا لإعادة المحاولة.