استخدام نموذج الرمز المميز

تساعدك مكتبة JavaScript google.accounts.oauth2 في طلب موافقة المستخدم والحصول على رمز دخول للعمل على بيانات المستخدمين. وهو يستند إلى تدفق المنح الضمني في OAuth 2.0 وتم تصميمه للسماح لك إما بطلب واجهات برمجة تطبيقات Google مباشرة باستخدام REST وCORS، أو استخدام مكتبة برامج Google APIs لـ JavaScript (المعروفة أيضًا باسم gapi.client) للوصول البسيط والمرن إلى واجهات برمجة التطبيقات الأكثر تعقيدًا.

وقبل الوصول إلى بيانات المستخدمين المحمية من أحد المتصفحات، يشغّل المستخدمون على موقعك الإلكتروني عمليات اختيار الحساب وتسجيل الدخول والموافقة التي تستند إليها Google، وأخيرًا، مشكلة خوادم OAuth من Google وعرض رمز الدخول إلى تطبيق الويب الخاص بك.

في نموذج التفويض المستند إلى الرمز المميز، لا يلزم تخزين الرموز المميزة للتحديث لكل مستخدم على الخادم الخلفي.

يُنصح باتّباع النهج الموضّح هنا بدلاً من الأساليب التي يشملها دليل OAuth 2.0 لتطبيقات الويب من جهة العميل القديم.

الإعداد

يمكنك البحث عن معرِّف عميل أو إنشاؤه باتّباع الخطوات الموضّحة في دليل الحصول على معرِّف العميل لواجهة Google API. بعد ذلك، أضِف مكتبة العملاء إلى صفحات موقعك الإلكتروني التي ستتصل بـ Google APIs. وأخيرًا، قم بتهيئة عميل الرمز المميز. ويتم ذلك عادةً ضِمن معالج onload في مكتبة العميل.

إعداد برنامج رمز مميّز

يمكنك الاتصال بـ initTokenClient() لتهيئة برنامج رمز مميز جديد باستخدام معرّف العميل لتطبيق الويب، ويمكنك اختياريًا تضمين قائمة بواحد أو أكثر من النطاقات التي يحتاج المستخدم إلى الوصول إليها:

const client = google.accounts.oauth2.initTokenClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly',
  callback: (response) => {
    ...
  },
});

تشغيل مسار الرمز المميّز لبروتوكول OAuth 2.0

استخدِم طريقة requestAccessToken() لتشغيل مسار تجربة المستخدم للرمز المميّز والحصول على رمز دخول. تطلب Google من المستخدم ما يلي:

  • اختَر حسابه.
  • تسجيل الدخول إلى حساب Google إذا لم تكن قد سجّلت الدخول إليه
  • منح الموافقة لتطبيق الويب للوصول إلى كل نطاق مطلوب.

تؤدي إيماءة المستخدم إلى تفعيل مسار الرمز المميز: <button onclick="client.requestAccessToken();">Authorize me</button>

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

يمكن للمستخدمين إغلاق محدد الحساب أو نوافذ تسجيل الدخول، وفي هذه الحالة لن يتم استدعاء وظيفة معاودة الاتصال.

يجب عدم تنفيذ تصميم تطبيقك وتجربة المستخدم إلا بعد مراجعة شاملة لسياسات OAuth 2.0 في Google. تشمل هذه السياسات العمل على نطاقات متعددة، ومتى يتم التعامل مع موافقة المستخدم وكيفية التعامل معها، وغير ذلك.

التفويض التدريجي هو منهجية لتصميم التطبيقات والسياسة التي تُستخدم لطلب الوصول إلى الموارد، باستخدام النطاقات، فقط حسب الحاجة بدلاً من المقدمة وكل ذلك في آنٍ واحد. ويمكن للمستخدمين الموافقة على مشاركة الموارد الفردية التي يطلبها تطبيقك أو رفضها، وهو ما يُعرف باسم الأذونات الدقيقة.

أثناء هذه العملية، يتم طلب موافقة المستخدمين من Google مع إدراج كل نطاق مطلوب بشكل فردي، ويختار المستخدمون الموارد التي ستتم مشاركتها مع تطبيقك، وأخيرًا، تستدعي Google وظيفة معاودة الاتصال لعرض رمز الدخول والنطاقات التي وافق عليها المستخدم. يتعامل تطبيقك بعد ذلك بأمان مع مختلف النتائج المختلفة المحتملة من خلال أذونات دقيقة.

التفويض التزايدي

بالنسبة إلى تطبيقات الويب، يوضّح السيناريوهان ذوو المستوى الأعلى التاليان التفويض التزايدي باستخدام:

  • هو تطبيق Ajax من صفحة واحدة، ويستخدم غالبًا XMLHttpRequest مع إمكانية الوصول الديناميكي إلى الموارد.
  • صفحات ويب متعددة وموارد منفصلة وإدارتها على أساس كل صفحة.

يتم تقديم هذين السيناريوهين لتوضيح اعتبارات التصميم والمنهجيات، ولكن لا يُقصد تقديم توصيات شاملة حول كيفية الحصول على موافقة المستخدمين في تطبيقك. قد تستخدم التطبيقات الواقعية أحد أشكال هذه الأساليب أو مزيجًا منها.

أياكس

يمكنك إضافة الدعم للتفويض المتزايد لتطبيقك من خلال إجراء استدعاءات متعددة لـ requestAccessToken() واستخدام معلَمة OverridableTokenClientConfig scope في العنصر لطلب نطاقات فردية في الوقت الذي تكون هناك حاجة إليها وعند الضرورة فقط. في هذا المثال، سيتم طلب الموارد ولن تظهر إلا بعد توسيع إيماءة المستخدم لقسم المحتوى المصغّر.

تطبيق Ajax
إعداد برنامج الرمز المميّز عند تحميل الصفحة:
        const client = google.accounts.oauth2.initTokenClient({
          client_id: 'YOUR_GOOGLE_CLIENT_ID',
          callback: "onTokenResponse",
        });
      
يمكنك طلب الموافقة والحصول على رموز الدخول من خلال إيماءات المستخدم، والنقر على "+" لفتح:

المستندات المطلوب قراءتها

عرض المستندات الأخيرة

          client.requestAccessToken(
            overrideConfig = ({
               scope = 'https://www.googleapis.com/auth/documents.readonly'
             })
           );
        

الفعاليات القادمة

عرض معلومات التقويم

          client.requestAccessToken(
            overrideConfig = ({
               scope = 'https://www.googleapis.com/auth/calendar.readonly'
             })
           );
        

عرض الصور

          client.requestAccessToken(
            overrideConfig = ({
               scope = 'https://www.googleapis.com/auth/photoslibrary.readonly'
             })
           );
        

تؤدي كل استدعاء للرمز requestAccessToken إلى لحظة موافقة المستخدم، وسيُتاح لتطبيقك الوصول فقط إلى تلك الموارد التي يتطلبها القسم الذي يختار المستخدم توسيعه، ما يؤدي إلى الحدّ من مشاركة الموارد من خلال اختيار المستخدم.

صفحات ويب متعددة

عند التصميم للحصول على إذن متزايد، يتم استخدام صفحات متعددة لطلب النطاق(النطاقات) المطلوبة فقط لتحميل الصفحة، ما يقلل من التعقيد والحاجة إلى إجراء استدعاءات متعددة للحصول على موافقة المستخدم واسترداد رمز الدخول.

تطبيق متعدد الصفحات
صفحة ويب الرمز
الصفحة 1. المستندات المطلوب قراءتها
  const client = google.accounts.oauth2.initTokenClient({
    client_id: 'YOUR_GOOGLE_CLIENT_ID',
    callback: "onTokenResponse",
    scope: 'https://www.googleapis.com/auth/documents.readonly',
  });
  client.requestAccessToken();
          
الصفحة 2: الفعاليات القادمة
  const client = google.accounts.oauth2.initTokenClient({
    client_id: 'YOUR_GOOGLE_CLIENT_ID',
    callback: "onTokenResponse",
    scope: 'https://www.googleapis.com/auth/calendar.readonly',
  });
  client.requestAccessToken();
          
الصفحة 3: لوحة عرض دوّارة للصور
  const client = google.accounts.oauth2.initTokenClient({
    client_id: 'YOUR_GOOGLE_CLIENT_ID',
    callback: "onTokenResponse",
    scope: 'https://www.googleapis.com/auth/photoslibrary.readonly',
  });
  client.requestAccessToken();
          

تطلب كل صفحة النطاق اللازم وتحصل على رمز دخول من خلال طلب الرمز البرمجي initTokenClient() وrequestAccessToken() في وقت التحميل. في هذا السيناريو، يتم استخدام صفحات الويب الفردية لفصل وظائف المستخدم وموارده بوضوح حسب النطاق. في موقف واقعي، قد تطلب الصفحات الفردية نطاقات متعددة ذات صلة.

الأذونات الدقيقة

يتم التعامل مع الأذونات الدقيقة بالطريقة نفسها في جميع السيناريوهات. وبعد استدعاء requestAccessToken() لوظيفة معاودة الاتصال وعرض رمز دخول، تأكَّد من أنّ المستخدم قد وافق على النطاقات المطلوبة باستخدام hasGrantedAllScopes() أو hasGrantedAnyScope(). مثال:

const client = google.accounts.oauth2.initTokenClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly \
          https://www.googleapis.com/auth/documents.readonly \
          https://www.googleapis.com/auth/photoslibrary.readonly',
  callback: (tokenResponse) => {
    if (tokenResponse && tokenResponse.access_token) {
      if (google.accounts.oauth2.hasGrantedAnyScope(tokenResponse,
          'https://www.googleapis.com/auth/photoslibrary.readonly')) {
        // Look at pictures
        ...
      }
      if (google.accounts.oauth2.hasGrantedAllScopes(tokenResponse,
          'https://www.googleapis.com/auth/calendar.readonly',
          'https://www.googleapis.com/auth/documents.readonly')) {
        // Meeting planning and review documents
        ...
      }
    }
  },
});

سيتضمن الرد أيضًا أي المِنح التي تم قبولها سابقًا من الجلسات أو الطلبات السابقة. يتم الاحتفاظ بسجلّ للحصول على موافقة المستخدم لكل مستخدم ومعرّف العميل، كما يتم الاحتفاظ بسجلّ الحصول على موافقة المستخدمين لكل من initTokenClient() أو requestAccessToken(). بشكلٍ تلقائي، لا يلزم موافقة المستخدم إلا في المرة الأولى التي يزور فيها المستخدم موقعك الإلكتروني ويطلب نطاقًا جديدًا، ولكن قد يتم طلبها في كل مرة يتم فيها تحميل صفحة باستخدام prompt=consent في كائنات ضبط Token Client.

التعامل مع الرموز المميّزة

في نموذج الرمز المميّز، لا يخزّن نظام التشغيل أو المتصفّح رمز الدخول، بل يتم الحصول على رمز مميّز جديد لأول مرة عند تحميل الصفحة، أو بعد ذلك عن طريق استدعاء رمز الدخول requestAccessToken() من خلال إيماءة المستخدم، مثل الضغط على زر.

استخدام REST وCORS مع Google APIs

يمكن استخدام رمز الدخول لإجراء طلبات تمت المصادقة عليها إلى واجهات Google APIs باستخدام REST وCORS. يتيح ذلك للمستخدمين تسجيل الدخول ومنح الموافقة وإصدار Google رمز الدخول وموقعك الإلكتروني للعمل على بيانات المستخدم.

في هذا المثال، يمكنك عرض أحداث التقويم القادمة للمستخدمين الذين سجّلوا الدخول باستخدام رمز الدخول الذي يعرضه tokenRequest():

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.googleapis.com/calendar/v3/calendars/primary/events');
xhr.setRequestHeader('Authorization', 'Bearer ' + tokenResponse.access_token);
xhr.send();

راجِع كيفية استخدام CORS للوصول إلى Google APIs لمزيد من المعلومات.

يتناول القسم التالي كيفية الدمج بسهولة مع واجهات برمجة التطبيقات الأكثر تعقيدًا.

العمل باستخدام مكتبة JavaScript لواجهات Google APIs

يعمل برنامج الرمز المميز مع مكتبة برامج Google API للغة JavaScript. يمكنك الاطّلاع على مقتطف الرمز أدناه.

const client = google.accounts.oauth2.initTokenClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly',
  callback: (tokenResponse) => {
    if (tokenResponse && tokenResponse.access_token) {
      gapi.client.setApiKey('YOUR_API_KEY');
      gapi.client.load('calendar', 'v3', listUpcomingEvents);
    }
  },
});

function listUpcomingEvents() {
  gapi.client.calendar.events.list(...);
}

انتهاء صلاحية الرمز المميّز

وحسب التصميم، تتسم رموز الدخول بعمر قصير. إذا انتهت صلاحية رمز الدخول قبل نهاية جلسة المستخدم، احصل على رمز مميّز جديد عن طريق استدعاء requestAccessToken() من حدث يستند إلى المستخدم، مثل الضغط على زر.

عليك استدعاء طريقة google.accounts.oauth2.revoke لإزالة موافقة المستخدم وإمكانية الوصول إلى الموارد لجميع النطاقات التي تم منحها لتطبيقك. ويجب توفّر رمز دخول صالح لإبطال هذا الإذن:

google.accounts.oauth2.revoke('414a76cb127a7ece7ee4bf287602ca2b56f8fcbf7fcecc2cd4e0509268120bd7', done => {
    console.log(done);
    console.log(done.successful);
    console.log(done.error);
    console.log(done.error_description);
  });