Kết nối tiện ích bổ sung của Google Workspace với một dịch vụ bên thứ ba

Thẻ uỷ quyền tuỳ chỉnh trong bản xem trước đường liên kết, bao gồm biểu trưng, nội dung mô tả và nút đăng nhập của công ty.
Giao diện thẻ đăng nhập cho một tiện ích bổ sung xem trước các đường liên kết từ một dịch vụ bên thứ ba.

Nếu tiện ích bổ sung Google Workspace của bạn kết nối với một dịch vụ hoặc API bên thứ ba yêu cầu uỷ quyền, thì tiện ích bổ sung đó có thể nhắc người dùng đăng nhập và uỷ quyền truy cập.

Trang này giải thích cách xác thực người dùng bằng quy trình uỷ quyền (chẳng hạn như OAuth), bao gồm các bước sau:

  1. Phát hiện trường hợp cần có sự uỷ quyền.
  2. Trả về một giao diện thẻ nhắc người dùng đăng nhập vào dịch vụ.
  3. Làm mới tiện ích bổ sung để người dùng có thể truy cập vào dịch vụ hoặc tài nguyên được bảo vệ.

Nếu tiện ích bổ sung của bạn chỉ yêu cầu danh tính người dùng, thì bạn có thể xác thực trực tiếp người dùng bằng cách sử dụng mã nhận dạng hoặc địa chỉ email Google Workspace của họ. Để sử dụng địa chỉ email cho mục đích xác thực, hãy xem phần xác thực các yêu cầu JSON. Nếu đã tạo tiện ích bổ sung bằng Google Apps Script, bạn có thể đơn giản hoá quy trình này bằng cách sử dụng thư viện OAuth2 cho Google Apps Script (cũng có phiên bản OAuth1).

Phát hiện thấy cần phải uỷ quyền

Khi sử dụng tiện ích bổ sung của bạn, người dùng có thể không được phép truy cập vào một tài nguyên được bảo vệ vì nhiều lý do, chẳng hạn như:

  • Mã thông báo truy cập để kết nối với dịch vụ bên thứ ba chưa được tạo hoặc đã hết hạn.
  • Mã truy cập không bao gồm tài nguyên được yêu cầu.
  • Mã truy cập không bao gồm các phạm vi bắt buộc của yêu cầu.

Tiện ích bổ sung của bạn phải phát hiện những trường hợp này để người dùng có thể đăng nhập và truy cập vào dịch vụ của bạn.

Nếu bạn đang tạo trong Apps Script, hàm hasAccess() của thư viện OAuth có thể cho bạn biết liệu bạn có quyền truy cập vào một dịch vụ hay không. Ngoài ra, khi sử dụng các yêu cầu UrlFetchApp fetch(), bạn có thể đặt tham số muteHttpExceptions thành true. Điều này ngăn yêu cầu đưa ra một ngoại lệ khi yêu cầu không thành công và cho phép bạn kiểm tra mã phản hồi yêu cầu cũng như nội dung trong đối tượng HttpResponse được trả về.

Nhắc người dùng đăng nhập vào dịch vụ của bạn

Khi phát hiện thấy cần có uỷ quyền, tiện ích bổ sung của bạn phải trả về một giao diện thẻ để nhắc người dùng đăng nhập vào dịch vụ. Thẻ đăng nhập phải chuyển hướng người dùng để hoàn tất quy trình xác thực và uỷ quyền của bên thứ ba trên cơ sở hạ tầng của bạn.

Khi tạo tiện ích bổ sung bằng các điểm cuối HTTP, bạn nên bảo vệ ứng dụng đích bằng tính năng Đăng nhập bằng Google và lấy mã nhận dạng người dùng bằng cách sử dụng mã thông báo nhận dạng được phát hành trong quá trình đăng nhập. Phần phụ này chứa mã nhận dạng duy nhất của người dùng và có thể tương quan với mã nhận dạng của tiện ích bổ sung.

Tạo và trả về thẻ đăng nhập

Đối với thẻ đăng nhập của dịch vụ, bạn có thể sử dụng thẻ uỷ quyền cơ bản của Google hoặc tuỳ chỉnh thẻ để hiển thị thông tin bổ sung, chẳng hạn như biểu trưng của tổ chức. Nếu xuất bản tiện ích bổ sung công khai, bạn phải sử dụng thẻ tuỳ chỉnh.

Thẻ uỷ quyền cơ bản

Hình ảnh sau đây minh hoạ ví dụ về thẻ uỷ quyền cơ bản của Google:

Lời nhắc uỷ quyền cơ bản cho Tài khoản ví dụ. Lời nhắc này cho biết tiện ích bổ sung muốn hiển thị thêm thông tin, nhưng cần có sự chấp thuận của người dùng để truy cập vào tài khoản.

Để nhắc người dùng bằng thẻ uỷ quyền cơ bản, bạn phải trả về đối tượng AuthorizationError. Đoạn mã sau đây cho thấy một ví dụ về đối tượng AuthorizationError:

Apps Script

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

JSON

Trả về phản hồi JSON sau:

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

Thay thế nội dung sau:

  • AUTHORIZATION_URL: URL cho ứng dụng web xử lý việc uỷ quyền.
  • RESOURCE_DISPLAY_NAME: Tên hiển thị của tài nguyên hoặc dịch vụ được bảo vệ. Tên này sẽ xuất hiện cho người dùng trên lời nhắc uỷ quyền. Ví dụ: nếu RESOURCE_DISPLAY_NAME của bạn là Example Account, thì lời nhắc sẽ có nội dung "Tiện ích bổ sung này muốn hiển thị thêm thông tin, nhưng cần được chấp thuận để truy cập vào Tài khoản ví dụ của bạn".

Sau khi hoàn tất việc uỷ quyền, người dùng sẽ được nhắc làm mới tiện ích bổ sung để truy cập vào tài nguyên được bảo vệ.

Thẻ uỷ quyền trả lại hàng trong Google Chat

Nếu tiện ích bổ sung của bạn mở rộng Google Chat và người dùng thực thi tiện ích bổ sung đó trong Google Chat, thì người dùng có thể hoàn tất quy trình uỷ quyền mà không cần làm mới theo cách thủ công. Google Chat hỗ trợ tự động thử lại lần thực thi trước nếu trình kích hoạtTin nhắn, Được thêm vào không gian hoặc Lệnh của ứng dụng. Đối với những trình kích hoạt này, tiện ích bổ sung của bạn sẽ nhận được completeRedirectUri trong tải trọng sự kiện. Bạn phải mã hoá completeRedirectUri trong URL cấu hình để kích hoạt tính năng tự động thử lại. Việc chuyển hướng đến URL này báo hiệu cho Google Chat rằng yêu cầu định cấu hình đã được thực hiện và cho phép Google Chat thử lại lần thực thi trước đó.

Khi người dùng được chuyển hướng thành công đến configCompleteRedirectUrl có trong tin nhắn ban đầu, Google Chat sẽ thực hiện các bước sau:

  1. Xoá lời nhắc hiển thị cho người dùng bắt đầu.
  2. Gửi đối tượng sự kiện ban đầu đến cùng một tiện ích bổ sung lần thứ hai.

Nếu bạn không mã hoá completeRedirectUri trong URL cấu hình, người dùng vẫn có thể hoàn tất quy trình uỷ quyền. Tuy nhiên, Google Chat không thử lại lần thực thi trước đó và người dùng phải gọi lại tiện ích bổ sung của bạn theo cách thủ công.

Mẫu mã sau đây cho thấy cách một ứng dụng Chat có thể yêu cầu thông tin đăng nhập OAuth2 ngoại tuyến, lưu trữ thông tin đó trong cơ sở dữ liệu và sử dụng thông tin đó để thực hiện các lệnh gọi API bằng xác thực người dùng.

Thẻ uỷ quyền tuỳ chỉnh

Để sửa đổi lời nhắc uỷ quyền, bạn có thể tạo một thẻ tuỳ chỉnh cho trải nghiệm đăng nhập của dịch vụ.

Nếu xuất bản tiện ích bổ sung công khai, bạn phải sử dụng thẻ uỷ quyền tuỳ chỉnh cho tất cả các ứng dụng lưu trữ Google Workspace, ngoại trừ Chat. Để tìm hiểu thêm về các yêu cầu khi xuất bản trên Google Workspace Marketplace, hãy xem bài viết Giới thiệu về quy trình đánh giá ứng dụng.

Thẻ được trả về phải thực hiện những việc sau:

  • Cho người dùng biết rõ rằng tiện ích bổ sung đang yêu cầu cấp quyền truy cập vào một dịch vụ không phải của Google thay cho họ.
  • Nêu rõ những việc mà tiện ích bổ sung có thể làm nếu được uỷ quyền.
  • Chứa một nút hoặc tiện ích tương tự đưa người dùng đến URL uỷ quyền của dịch vụ. Đảm bảo người dùng hiểu rõ chức năng của tiện ích này.
  • Tiện ích nêu trên phải sử dụng chế độ cài đặt OnClose.RELOAD trong đối tượng OpenLink để đảm bảo rằng tiện ích bổ sung tải lại sau khi nhận được uỷ quyền.
  • Tất cả các đường liên kết được mở từ lời nhắc uỷ quyền đều phải sử dụng HTTPS.

Hình ảnh sau đây cho thấy một ví dụ về thẻ uỷ quyền tuỳ chỉnh cho trang chủ của tiện ích bổ sung. Thẻ này bao gồm biểu trưng, nội dung mô tả và nút đăng nhập:

Thẻ uỷ quyền tuỳ chỉnh cho Cymbal Labs, bao gồm biểu trưng, nội dung mô tả và nút đăng nhập của công ty.

Đoạn mã sau đây cho biết cách sử dụng ví dụ về thẻ tuỳ chỉnh này:

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

Trả về phản hồi JSON sau:

{
  "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"
                    }
                  }
                ]
              }
            ]
          }
        }
      ]
    }
  }
}

Thay thế nội dung sau:

  • LOGO_URL: URL của biểu trưng hoặc hình ảnh. Phải là một URL công khai.
  • LOGO_ALT_TEXT: Văn bản thay thế cho biểu trưng hoặc hình ảnh, chẳng hạn như Cymbal Labs Logo.
  • DESCRIPTION: Lời kêu gọi hành động để người dùng đăng nhập, chẳng hạn như Sign in to get started.
  • Cách cập nhật nút đăng nhập:
    • AUTHORIZATION_URL: URL cho ứng dụng web xử lý việc uỷ quyền.
    • Không bắt buộc: Để thay đổi màu nút, hãy cập nhật các giá trị RGBA float của trường color. Đối với Apps Script, hãy cập nhật phương thức setBackgroundColor() bằng cách sử dụng các giá trị thập lục phân.
  • TEXT_SIGN_UP: Văn bản nhắc người dùng tạo tài khoản nếu họ chưa có. Ví dụ: New to Cymbal Labs? <a href=\"https://www.example.com/signup\">Sign up</a> here.

Quản lý hoạt động đăng nhập qua bên thứ ba trên các ứng dụng Google Workspace

Một ứng dụng phổ biến cho tiện ích bổ sung của Google Workspace là cung cấp giao diện để tương tác với hệ thống của bên thứ ba trong ứng dụng lưu trữ Google Workspace.

Các hệ thống bên thứ ba thường yêu cầu người dùng đăng nhập bằng mã nhận dạng người dùng, mật khẩu hoặc thông tin đăng nhập khác. Khi người dùng đăng nhập vào dịch vụ bên thứ ba của bạn trong khi đang sử dụng một máy chủ lưu trữ Google Workspace, bạn phải đảm bảo rằng họ không phải đăng nhập lại khi chuyển sang một máy chủ lưu trữ Google Workspace khác.

Nếu đang tạo trong Apps Script, bạn có thể ngăn chặn các yêu cầu đăng nhập lặp lại bằng thuộc tính người dùng hoặc mã thông báo nhận dạng. Những điều này được giải thích trong các phần sau.

Thuộc tính người dùng

Bạn có thể lưu trữ dữ liệu đăng nhập của người dùng trong các thuộc tính người dùng của Apps Script. Ví dụ: bạn có thể tạo Mã thông báo web JSON (JWT) của riêng mình từ dịch vụ đăng nhập của họ và ghi lại mã thông báo đó trong một thuộc tính người dùng, hoặc ghi lại tên người dùng và mật khẩu cho dịch vụ của họ.

Thuộc tính người dùng được giới hạn phạm vi để chỉ người dùng đó mới có thể truy cập vào trong tập lệnh của tiện ích bổ sung. Những người dùng và tập lệnh khác không thể truy cập vào các thuộc tính này. Hãy xem PropertiesService để biết thêm thông tin chi tiết.

Mã nhận dạng

Bạn có thể sử dụng mã thông báo giá trị nhận dạng của Google làm thông tin đăng nhập cho dịch vụ của mình. Đây là một cách để đạt được tính năng đăng nhập một lần. Người dùng đã đăng nhập vào Google vì họ đang dùng một ứng dụng lưu trữ của Google.

Ví dụ về cấu hình OAuth không phải của Google

Mã mẫu Apps Script sau đây cho biết cách định cấu hình một tiện ích bổ sung để sử dụng một API không phải của Google yêu cầu OAuth. Mẫu này sử dụng Thư viện OAuth2 cho Apps Script để tạo một dịch vụ truy cập vào 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();
}