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

تساعدك مكتبة JavaScript google.accounts.oauth2 في طلب موافقة المستخدم والحصول على رمز دخول للتعامل مع بيانات المستخدمين. تستند هذه الطريقة إلى مسار المنح الضمني في OAuth 2.0 وقد صُممت للسماح لك إما بطلب واجهات برمجة تطبيقات Google مباشرةً باستخدام REST وCORS، أو استخدام مكتبة برامج Google APIs (المعروفة أيضًا باسم 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 وظيفة معاودة الاتصال لعرض رمز الدخول والنطاقات التي وافق عليها المستخدم. يتعامل تطبيقك بعد ذلك بأمان مع النتائج المختلفة المحتملة من خلال أذونات دقيقة.

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

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

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

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

أياكس

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

تطبيق 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 API

يعمل برنامج الرمز المميز مع مكتبة برامج 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);
  });