เชื่อมต่อส่วนเสริมของ Google Workspace กับบริการของบุคคลที่สาม

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

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

หน้านี้อธิบายวิธีตรวจสอบสิทธิ์ผู้ใช้โดยใช้ขั้นตอนการให้สิทธิ์ (เช่น OAuth) ซึ่งรวมถึงขั้นตอนต่อไปนี้

  1. ตรวจหาเมื่อต้องมีการให้สิทธิ์
  2. แสดงอินเทอร์เฟซการ์ดที่แจ้งให้ผู้ใช้ลงชื่อเข้าใช้บริการ
  3. รีเฟรชส่วนเสริมเพื่อให้ผู้ใช้เข้าถึงบริการหรือทรัพยากรที่ได้รับการปกป้องได้

หากส่วนเสริมต้องการเพียงข้อมูลระบุตัวตนของผู้ใช้ คุณสามารถตรวจสอบสิทธิ์ผู้ใช้ได้โดยตรงโดยใช้รหัสหรืออีเมล Google Workspace ของผู้ใช้ หากต้องการใช้อีเมล ในการตรวจสอบสิทธิ์ โปรดดูการตรวจสอบคำขอ JSON หากคุณสร้างส่วนเสริมโดยใช้ Google Apps Script, คุณสามารถทำให้กระบวนการนี้ง่ายขึ้นได้โดยใช้ไลบรารี OAuth2 for Google Apps Script (นอกจากนี้ยังมี เวอร์ชัน OAuth1)

ตรวจหาว่าต้องมีการให้สิทธิ์

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

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

ส่วนเสริมควรตรวจหาเคสเหล่านี้เพื่อให้ผู้ใช้ลงชื่อเข้าใช้และเข้าถึงบริการของคุณได้

หากคุณสร้างใน Apps Script ฟังก์ชัน hasAccess ของไลบรารี OAuth จะบอกคุณว่าคุณมีสิทธิ์เข้าถึงบริการหรือไม่ หรือเมื่อใช้ UrlFetchApp.fetch คำขอ คุณสามารถตั้งค่าพารามิเตอร์ muteHttpExceptions เป็น true ได้ ซึ่งจะป้องกันไม่ให้คำขอส่งข้อยกเว้นเมื่อคำขอไม่สำเร็จ และช่วยให้คุณตรวจสอบโค้ดตอบกลับและเนื้อหาของคำขอในออบเจ็กต์ HttpResponse ที่แสดงผลได้

แจ้งให้ผู้ใช้ลงชื่อเข้าใช้บริการ

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

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

สร้างและแสดงการ์ดลงชื่อเข้าใช้

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

การ์ดการให้สิทธิ์พื้นฐาน

รูปภาพต่อไปนี้แสดงตัวอย่างการ์ดการให้สิทธิ์พื้นฐานของ Google

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

หากต้องการแจ้งให้ผู้ใช้ทราบด้วยการ์ดการให้สิทธิ์พื้นฐาน คุณต้องแสดงออบเจ็กต์ AuthorizationError โค้ดต่อไปนี้แสดงตัวอย่างออบเจ็กต์ AuthorizationError

Apps Script

CardService.newAuthorizationException()
    .setAuthorizationUrl('AUTHORIZATION_URL')
    .setResourceDisplayName('RESOURCE_DISPLAY_NAME')
    .throwException();

JSON

แสดงการตอบกลับ JSON ต่อไปนี้

{
  "basic_authorization_prompt": {
    "authorization_url": "AUTHORIZATION_URL",
    "resource": "RESOURCE_DISPLAY_NAME"
  }
}

แทนที่ค่าต่อไปนี้

  • AUTHORIZATION_URL: URL สำหรับเว็บแอปที่จัดการการให้สิทธิ์
  • RESOURCE_DISPLAY_NAME: ชื่อที่แสดงสำหรับทรัพยากรหรือบริการที่ได้รับการปกป้อง ระบบจะแสดงชื่อนี้ต่อผู้ใช้ในข้อความแจ้งการให้สิทธิ์ ตัวอย่างเช่น หาก RESOURCE_DISPLAY_NAME คือ Example Account ข้อความแจ้งจะระบุว่า "ส่วนเสริมนี้ ต้องการแสดงข้อมูลเพิ่มเติม แต่ต้องได้รับอนุมัติให้เข้าถึง บัญชีตัวอย่างของคุณ"

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

แสดงการ์ดการให้สิทธิ์ใน Google Chat

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

เมื่อระบบเปลี่ยนเส้นทางผู้ใช้ไปยัง configCompleteRedirectUrl ที่ระบุไว้ในข้อความเดิมได้สำเร็จ Google Chat จะดำเนินการตามขั้นตอนต่อไปนี้

  1. ลบข้อความแจ้งที่แสดงต่อผู้ใช้ที่เริ่มการดำเนินการ
  2. ส่งออบเจ็กต์เหตุการณ์เดิมไปยังส่วนเสริมเดียวกันเป็นครั้งที่ 2

หากคุณไม่เข้ารหัส completeRedirectUri ใน URL การกำหนดค่า ผู้ใช้จะยังคงดำเนินการให้สิทธิ์ให้เสร็จสมบูรณ์ได้ อย่างไรก็ตาม Google Chat จะไม่ลองเรียกใช้การดำเนินการก่อนหน้าอีกครั้ง และผู้ใช้ต้องเรียกใช้ส่วนเสริมอีกครั้งด้วยตนเอง

ตัวอย่างโค้ดต่อไปนี้แสดงวิธีที่แอป Chat สามารถ ขอข้อมูลเข้าสู่ระบบ OAuth2 แบบออฟไลน์ จัดเก็บข้อมูลดังกล่าวในฐานข้อมูล และใช้ข้อมูลดังกล่าวเพื่อ เรียก API ด้วย การตรวจสอบสิทธิ์ผู้ใช้

การ์ดการให้สิทธิ์ที่กำหนดเอง

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

หากคุณเผยแพร่ส่วนเสริมแบบสาธารณะ คุณต้องใช้การ์ดการให้สิทธิ์ที่กำหนดเองสำหรับแอปพลิเคชันโฮสต์ Google Workspace ทั้งหมดยกเว้น Chat ดูข้อมูลเพิ่มเติมเกี่ยวกับข้อกำหนดในการเผยแพร่สำหรับ Google Workspace Marketplace ได้ที่ หัวข้อ เกี่ยวกับ การตรวจสอบแอป

การ์ดที่แสดงผลต้องมีลักษณะดังนี้

  • แจ้งให้ผู้ใช้ทราบอย่างชัดเจนว่าส่วนเสริมกำลังขอสิทธิ์เข้าถึงบริการที่ไม่ใช่ของ Google ในนามของผู้ใช้
  • แจ้งให้ทราบอย่างชัดเจนว่าส่วนเสริมจะทำอะไรได้บ้างหากได้รับอนุญาต
  • มีปุ่มหรือวิดเจ็ตที่คล้ายกันซึ่งจะนำผู้ใช้ไปยัง URL การให้สิทธิ์ของบริการ ตรวจสอบว่าฟังก์ชันของวิดเจ็ตนี้ชัดเจนสำหรับผู้ใช้
  • วิดเจ็ตก่อนหน้าต้องใช้การตั้งค่า OnClose.RELOAD ในออบเจ็กต์ OpenLink เพื่อให้แน่ใจว่าส่วนเสริมจะโหลดซ้ำหลังจากได้รับสิทธิ์
  • ลิงก์ทั้งหมดที่เปิดจากข้อความแจ้งการให้สิทธิ์ต้อง ใช้ HTTPS

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

การ์ดการให้สิทธิ์ที่กำหนดเองสำหรับ Cymbal Labs ซึ่งมีโลโก้ คำอธิบาย และปุ่มลงชื่อเข้าใช้ของบริษัท

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

Apps Script

function customAuthorizationCard() {
    let cardSection1Image1 = CardService.newImage()
        .setImageUrl('LOGO_URL')
        .setAltText('LOGO_ALT_TEXT');

    let cardSection1Divider1 = CardService.newDivider();

    let cardSection1TextParagraph1 = CardService.newTextParagraph()
        .setText('DESCRIPTION');

    let cardSection1ButtonList1Button1 = CardService.newTextButton()
        .setText('Sign in')
        .setBackgroundColor('#0055ff')
        .setTextButtonStyle(CardService.TextButtonStyle.FILLED)
        .setAuthorizationAction(CardService.newAuthorizationAction()
            .setAuthorizationUrl('AUTHORIZATION_URL'));

    let cardSection1ButtonList1 = CardService.newButtonSet()
        .addButton(cardSection1ButtonList1Button1);

    let cardSection1TextParagraph2 = CardService.newTextParagraph()
        .setText('TEXT_SIGN_UP');

    let cardSection1 = CardService.newCardSection()
        .addWidget(cardSection1Image1)
        .addWidget(cardSection1Divider1)
        .addWidget(cardSection1TextParagraph1)
        .addWidget(cardSection1ButtonList1)
        .addWidget(cardSection1TextParagraph2);

    let card = CardService.newCardBuilder()
        .addSection(cardSection1)
        .build();
    return [card];
}

function startNonGoogleAuth() {
    CardService.newAuthorizationException()
        .setAuthorizationUrl('AUTHORIZATION_URL')
        .setResourceDisplayName('RESOURCE_DISPLAY_NAME')
        .setCustomUiCallback('customAuthorizationCard')
        .throwException();
  }

JSON

แสดงการตอบกลับ JSON ต่อไปนี้

{
  "custom_authorization_prompt": {
    "action": {
      "navigations": [
        {
          "pushCard": {
            "sections": [
              {
                "widgets": [
                  {
                    "image": {
                      "imageUrl": "LOGO_URL",
                      "altText": "LOGO_ALT_TEXT"
                    }
                  },
                  {
                    "divider": {}
                  },
                  {
                    "textParagraph": {
                      "text": "DESCRIPTION"
                    }
                  },
                  {
                    "buttonList": {
                      "buttons": [
                        {
                          "text": "Sign in",
                          "onClick": {
                            "openLink": {
                              "url": "AUTHORIZATION_URL",
                              "onClose": "RELOAD",
                              "openAs": "OVERLAY"
                            }
                          },
                          "color": {
                            "red": 0,
                            "green": 0,
                            "blue": 1,
                            "alpha": 1,
                          }
                        }
                      ]
                    }
                  },
                  {
                    "textParagraph": {
                      "text": "TEXT_SIGN_UP"
                    }
                  }
                ]
              }
            ]
          }
        }
      ]
    }
  }
}

แทนที่ค่าต่อไปนี้

  • LOGO_URL: URL สำหรับโลโก้หรือรูปภาพ ต้องเป็น URL สาธารณะ
  • LOGO_ALT_TEXT: ข้อความแสดงแทนสำหรับโลโก้หรือรูปภาพ เช่น Cymbal Labs Logo
  • DESCRIPTION: คำกระตุ้นให้ผู้ใช้ลงชื่อเข้าใช้, เช่น Sign in to get started
  • วิธีอัปเดตปุ่มลงชื่อเข้าใช้
    • AUTHORIZATION_URL: URL สำหรับเว็บแอปที่จัดการการให้สิทธิ์
    • ไม่บังคับ: หากต้องการเปลี่ยนสีปุ่ม ให้อัปเดตค่าทศนิยม RGBA ของcolor ช่อง สำหรับ Apps Script ให้อัปเดต setBackgroundColor เมธอดโดยใช้ค่าฐานสิบหก
  • TEXT_SIGN_UP: ข้อความที่แจ้งให้ผู้ใช้สร้างบัญชีหากยังไม่มี เช่น New to Cymbal Labs? <a href=\"https://www.example.com/signup\">Sign up</a> here

จัดการการลงชื่อเข้าใช้ของบุคคลที่สามในแอป Google Workspace

แอปพลิเคชันทั่วไปอย่างหนึ่งสำหรับส่วนเสริม Google Workspace คือการจัดเตรียมอินเทอร์เฟซสำหรับการโต้ตอบกับระบบของบุคคลที่สามจากภายใน Google Workspace แอปพลิเคชันโฮสต์

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

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

พร็อพเพอร์ตี้ผู้ใช้

คุณสามารถจัดเก็บข้อมูลการลงชื่อเข้าใช้ของผู้ใช้ในพร็อพเพอร์ตี้ผู้ใช้ของ Apps Script ตัวอย่างเช่น คุณสามารถสร้าง JSON Web Token (JWT) ของคุณเองจากบริการลงชื่อเข้าใช้ของผู้ใช้และบันทึกโทเค็นดังกล่าวในพร็อพเพอร์ตี้ผู้ใช้ หรือบันทึกชื่อผู้ใช้และรหัสผ่านสำหรับบริการของผู้ใช้

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

โทเค็นรหัส

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

ตัวอย่างการกำหนดค่า OAuth ที่ไม่ใช่ของ Google

ตัวอย่างโค้ด Apps Script ต่อไปนี้แสดงวิธีกำหนดค่าส่วนเสริมให้ใช้ API ที่ไม่ใช่ของ Google ซึ่งต้องใช้ OAuth ตัวอย่างนี้ใช้ไลบรารี OAuth2 for Apps Script เพื่อสร้างบริการสำหรับการเข้าถึง API

Apps Script

/**
* Attempts to access a non-Google API using a constructed service
* object.
*
* If your add-on needs access to non-Google APIs that require OAuth,
* you need to implement this method. You can use the OAuth1 and
* OAuth2 Apps Script libraries to help implement it.
*
* @param {String} url         The URL to access.
* @param {String} method_opt  The HTTP method. Defaults to GET.
* @param {Object} headers_opt The HTTP headers. Defaults to an empty
*                             object. The Authorization field is added
*                             to the headers in this method.
* @return {HttpResponse} the result from the UrlFetchApp.fetch() call.
*/
function accessProtectedResource(url, method_opt, headers_opt) {
  var service = getOAuthService();
  var maybeAuthorized = service.hasAccess();
  if (maybeAuthorized) {
    // A token is present, but it may be expired or invalid. Make a
    // request and check the response code to be sure.

    // Make the UrlFetch request and return the result.
    var accessToken = service.getAccessToken();
    var method = method_opt || 'get';
    var headers = headers_opt || {};
    headers['Authorization'] =
        Utilities.formatString('Bearer %s', accessToken);
    var resp = UrlFetchApp.fetch(url, {
      'headers': headers,
      'method' : method,
      'muteHttpExceptions': true, // Prevents thrown HTTP exceptions.
    });

    var code = resp.getResponseCode();
    if (code >= 200 && code < 300) {
      return resp.getContentText("utf-8"); // Success
    } else if (code == 401 || code == 403) {
      // Not fully authorized for this action.
      maybeAuthorized = false;
    } else {
      // Handle other response codes by logging them and throwing an
      // exception.
      console.error("Backend server error (%s): %s", code.toString(),
                    resp.getContentText("utf-8"));
      throw ("Backend server error: " + code);
    }
  }

  if (!maybeAuthorized) {
    // Invoke the authorization flow using the default authorization
    // prompt card.
    CardService.newAuthorizationException()
        .setAuthorizationUrl(service.getAuthorizationUrl())
        .setResourceDisplayName("Display name to show to the user")
        .throwException();
  }
}

/**
* Create a new OAuth service to facilitate accessing an API.
* This example assumes there is a single service that the add-on needs to
* access. Its name is used when persisting the authorized token, so ensure
* it is unique within the scope of the property store. You must set the
* client secret and client ID, which are obtained when registering your
* add-on with the API.
*
* See the Apps Script OAuth2 Library documentation for more
* information:
*   https://github.com/googlesamples/apps-script-oauth2#1-create-the-oauth2-service
*
*  @return A configured OAuth2 service object.
*/
function getOAuthService() {
  return OAuth2.createService('SERVICE_NAME')
      .setAuthorizationBaseUrl('SERVICE_AUTH_URL')
      .setTokenUrl('SERVICE_AUTH_TOKEN_URL')
      .setClientId('CLIENT_ID')
      .setClientSecret('CLIENT_SECRET')
      .setScope('SERVICE_SCOPE_REQUESTS')
      .setCallbackFunction('authCallback')
      .setCache(CacheService.getUserCache())
      .setPropertyStore(PropertiesService.getUserProperties());
}

/**
* Boilerplate code to determine if a request is authorized and returns
* a corresponding HTML message. When the user completes the OAuth2 flow
* on the service provider's website, this function is invoked from the
* service. In order for authorization to succeed you must make sure that
* the service knows how to call this function by setting the correct
* redirect URL.
*
* The redirect URL to enter is:
* https://script.google.com/macros/d/<Apps Script ID>/usercallback
*
* See the Apps Script OAuth2 Library documentation for more
* information:
*   https://github.com/googlesamples/apps-script-oauth2#1-create-the-oauth2-service
*
*  @param {Object} callbackRequest The request data received from the
*                  callback function. Pass it to the service's
*                  handleCallback() method to complete the
*                  authorization process.
*  @return {HtmlOutput} a success or denied HTML message to display to
*          the user.
*/
function authCallback(callbackRequest) {
  var authorized = getOAuthService().handleCallback(callbackRequest);
  if (authorized) {
    return HtmlService.createHtmlOutput(
      'Success!');
  } else {
    return HtmlService.createHtmlOutput('Denied');
  }
}

/**
* Unauthorizes the non-Google service. This is useful for OAuth
* development/testing.  Run this method (Run > resetOAuth in the script
* editor) to reset OAuth to re-prompt the user for OAuth.
*/
function resetOAuth() {
  getOAuthService().reset();
}