การใช้โมเดลโทเค็น

google.accounts.oauth2ไลบรารี JavaScript ช่วยให้คุณขอความยินยอมจากผู้ใช้ และรับโทเค็นการเข้าถึงเพื่อใช้กับข้อมูลผู้ใช้ได้ โดยอิงตามขั้นตอนการให้สิทธิ์โดยนัยของ OAuth 2.0 และออกแบบมาเพื่อให้คุณเรียกใช้ Google API ได้โดยตรงโดยใช้ REST และ CORS หรือใช้ไลบรารีไคลเอ็นต์ Google APIs สำหรับ JavaScript (หรือที่เรียกว่า gapi.client) เพื่อเข้าถึง API ที่ซับซ้อนกว่าของเราได้อย่างง่ายดายและยืดหยุ่น

ก่อนที่จะเข้าถึงข้อมูลผู้ใช้ที่ได้รับการปกป้องจากเบราว์เซอร์ ผู้ใช้ในเว็บไซต์ของคุณจะทริกเกอร์ ตัวเลือกบัญชีบนเว็บของ Google, การลงชื่อเข้าใช้ และกระบวนการขอความยินยอม และสุดท้าย เซิร์ฟเวอร์ OAuth ของ Google จะออกและส่งคืนโทเค็นเพื่อการเข้าถึงไปยังเว็บแอปของคุณ

ในรูปแบบการให้สิทธิ์ตามโทเค็น คุณไม่จำเป็นต้องจัดเก็บโทเค็นรีเฟรชต่อผู้ใช้ในเซิร์ฟเวอร์แบ็กเอนด์

เราขอแนะนำให้คุณทำตามแนวทางที่ระบุไว้ที่นี่แทนเทคนิคที่อธิบายไว้ในคำแนะนำOAuth 2.0 สำหรับเว็บแอปพลิเคชันฝั่งไคลเอ็นต์ ฉบับเก่า

ข้อกำหนดเบื้องต้น

ทำตามขั้นตอนที่อธิบายไว้ในการตั้งค่าเพื่อกำหนดค่าหน้าจอขอความยินยอม OAuth รับรหัสไคลเอ็นต์ และโหลดไลบรารีไคลเอ็นต์

เริ่มต้นไคลเอ็นต์โทเค็น

เรียกใช้ initTokenClient() เพื่อเริ่มต้นไคลเอ็นต์โทเค็นใหม่ด้วยรหัสไคลเอ็นต์ของเว็บแอป คุณต้องระบุรายการขอบเขตอย่างน้อย 1 รายการที่ผู้ใช้ ต้องใช้เพื่อเข้าถึง

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() เพื่อทริกเกอร์โฟลว์ UX ของโทเค็นและรับ โทเค็นการเข้าถึง Google จะแจ้งให้ผู้ใช้ดำเนินการต่อไปนี้

  • เลือกบัญชีของบุคคลนั้น
  • ลงชื่อเข้าใช้บัญชี Google หากยังไม่ได้ลงชื่อเข้าใช้
  • ให้ความยินยอมให้เว็บแอปเข้าถึงขอบเขตที่ขอแต่ละรายการ

ท่าทางของผู้ใช้จะทริกเกอร์โฟลว์โทเค็น <button onclick="client.requestAccessToken();">Authorize me</button>

จากนั้น Google จะส่งคืน TokenResponse ที่มีโทเค็นเพื่อการเข้าถึงและรายการขอบเขต ที่ผู้ใช้ให้สิทธิ์เข้าถึง หรือข้อผิดพลาด ไปยังตัวแฮนเดิลการเรียกกลับ

ผู้ใช้อาจปิดตัวเลือกบัญชีหรือหน้าต่างลงชื่อเข้าใช้ ในกรณีนี้ ระบบจะไม่เรียกใช้ฟังก์ชันเรียกกลับของคุณ

คุณควรออกแบบและมอบประสบการณ์ของผู้ใช้สำหรับแอปหลังจากที่ได้อ่านนโยบาย OAuth 2.0 ของ Google อย่างละเอียดแล้วเท่านั้น นโยบายเหล่านี้ครอบคลุมถึง การทำงานกับขอบเขตหลายรายการ เวลาและวิธีจัดการความยินยอมของผู้ใช้ และอื่นๆ

การให้สิทธิ์แบบเพิ่มทีละรายการคือนโยบายและวิธีการออกแบบแอปที่ใช้เพื่อ ขอสิทธิ์เข้าถึงทรัพยากรโดยใช้ขอบเขตตามที่จำเป็นเท่านั้น แทนที่จะขอสิทธิ์ล่วงหน้า และขอทั้งหมดในครั้งเดียว ผู้ใช้อาจอนุมัติหรือปฏิเสธการแชร์ทรัพยากรแต่ละรายการ ที่แอปของคุณขอ ซึ่งเรียกว่าสิทธิ์แบบละเอียด

ในกระบวนการนี้ Google จะแจ้งให้ผู้ใช้ให้ความยินยอม โดยแสดงขอบเขตที่ขอแต่ละรายการแยกกัน ผู้ใช้จะเลือกทรัพยากรที่จะแชร์กับแอปของคุณ และสุดท้าย Google จะเรียกใช้ฟังก์ชันการเรียกกลับเพื่อส่งคืนโทเค็นการเข้าถึงและขอบเขตที่ผู้ใช้ อนุมัติ จากนั้นแอปจะจัดการผลลัพธ์ต่างๆ ที่อาจเกิดขึ้นได้อย่างปลอดภัยด้วยสิทธิ์แบบละเอียด

แต่ก็มีข้อยกเว้น แอป Google Workspace Enterprise ที่มีการมอบสิทธิ์ระดับโดเมนหรือแอปที่ทำเครื่องหมายเป็นเชื่อถือได้ จะข้ามหน้าจอขอความยินยอมให้สิทธิ์แบบละเอียด สำหรับแอปเหล่านี้ ผู้ใช้จะไม่เห็นหน้าจอขอความยินยอมให้สิทธิ์แบบละเอียด แต่แอปจะได้รับขอบเขตที่ขอทั้งหมดหรือไม่ก็ไม่ได้รับเลย

ดูข้อมูลโดยละเอียดเพิ่มเติมได้ที่วิธีจัดการสิทธิ์แบบละเอียด

การให้สิทธิ์แบบเพิ่ม

สำหรับเว็บแอป สถานการณ์ระดับสูง 2 สถานการณ์ต่อไปนี้แสดงการให้สิทธิ์แบบค่อยเป็นค่อยไปโดยใช้

  • แอป Ajax หน้าเว็บเดียวซึ่งมักใช้ XMLHttpRequest ที่มีการเข้าถึงแบบไดนามิกไปยัง ทรัพยากร
  • หน้าเว็บหลายหน้า ทรัพยากรจะแยกกันและได้รับการจัดการตามหน้าเว็บ

สถานการณ์ทั้ง 2 นี้แสดงให้เห็นถึงข้อควรพิจารณาในการออกแบบและ วิธีการต่างๆ แต่ไม่ได้มีวัตถุประสงค์เพื่อเป็นคำแนะนำที่ครอบคลุมเกี่ยวกับวิธี สร้างความยินยอมในแอป แอปในโลกแห่งความเป็นจริงอาจใช้เทคนิคเหล่านี้ในรูปแบบต่างๆ หรือ ใช้ร่วมกัน

อาแจ๊กซ์

เพิ่มการรองรับการให้สิทธิ์แบบเพิ่มทีละรายการลงในแอปโดยการเรียกใช้หลายครั้ง ไปยัง 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
        ...
      }
    }
  },
});

การให้สิทธิ์ที่ยอมรับก่อนหน้านี้จากเซสชันหรือคำขอก่อนหน้าจะรวมอยู่ในการตอบกลับด้วย ระบบจะเก็บรักษาบันทึกความยินยอมของผู้ใช้ต่อผู้ใช้และ Client ID แต่ละราย และจะยังคงอยู่ในการเรียกใช้ initTokenClient() หรือ requestAccessToken() หลายครั้ง โดยค่าเริ่มต้น ความยินยอมของผู้ใช้จะจำเป็นเฉพาะครั้งแรกที่ผู้ใช้เข้าชมเว็บไซต์และขอขอบเขตใหม่ แต่ระบบอาจขอความยินยอมทุกครั้งที่โหลดหน้าเว็บโดยใช้ prompt=consent ในออบเจ็กต์การกำหนดค่าไคลเอ็นต์โทเค็น

การทำงานกับโทเค็น

ในโมเดลโทเค็น ระบบปฏิบัติการหรือเบราว์เซอร์จะไม่จัดเก็บโทเค็นเพื่อการเข้าถึง แต่จะรับโทเค็นใหม่เป็นครั้งแรกเมื่อโหลดหน้าเว็บ หรือรับในภายหลังโดยการเรียกใช้ 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

ส่วนถัดไปจะอธิบายวิธีผสานรวมกับ API ที่ซับซ้อนมากขึ้นได้อย่างง่ายดาย

การทำงานกับไลบรารี 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);
  });