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

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

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

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

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

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

ตรวจพบว่าต้องให้สิทธิ์

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

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

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

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

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

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

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

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

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

บัตรการให้สิทธิ์พื้นฐาน

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

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

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

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 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() โดยใช้ค่าฐาน 16
  • 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 ID เป็นข้อมูลเข้าสู่ระบบสำหรับบริการได้ วิธีนี้เป็นวิธีลงชื่อเพียงครั้งเดียว ผู้ใช้เข้าสู่ระบบ Google อยู่แล้วเนื่องจากอยู่ในแอปโฮสต์ของ Google

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

ตัวอย่างโค้ด Apps Script ต่อไปนี้แสดงวิธีกำหนดค่าส่วนเสริมให้ใช้ API ที่ไม่ใช่ของ Google ซึ่งต้องใช้ OAuth ตัวอย่างนี้ใช้ไลบรารี OAuth2 สำหรับ 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. Also sets a timer to close the window
*          automatically.
*/
function authCallback(callbackRequest) {
  var authorized = getOAuthService().handleCallback(callbackRequest);
  if (authorized) {
    return HtmlService.createHtmlOutput(
      'Success! <script>setTimeout(function() { top.window.close() }, 1);</script>');
  } 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();
}