Google Chat 用アプリのきめ細かい OAuth 権限を管理する

ユーザー認証を使用する Chat 用アプリは、粒度の細かい OAuth 権限をサポートして、ユーザーがリクエストされたスコープのサブセットを付与できるようにする必要があります。たとえば、ユーザーが名前へのアクセスは許可しても、カレンダーへのアクセスは拒否する場合があります。

きめ細かな OAuth 権限の処理は、Chat 用アプリのビルド方法によって異なります。

Apps Script

Apps Script を使用して Chat 用アプリをビルドする場合、Apps Script はきめ細かい OAuth 権限を自動的に処理します。ただし、ユーザーがリクエストされたすべてのスコープを付与しない場合をコードで処理するようにしてください。この方法は、Apps Script が Apps Script を使用して Google Chat を拡張する Google Workspace アドオンであるか、Apps Script とインタラクション イベントで構築されたスタンドアロンの Chat 用アプリであるかによって異なります。

Chat を拡張する Google Workspace アドオン

Apps Script を使用して Google Chat を拡張する Google Workspace アドオンとして Chat 用アプリをビルドする場合は、Apps Script で粒度の細かい OAuth 権限を処理するの手順に沿って操作します。

スタンドアロンの Apps Script Chat 用アプリ

Apps Script とインタラクション イベントを使用して Chat 用アプリを構築する場合、Apps Script できめ細かい OAuth 権限を処理するの手順は、次の 1 つの考慮事項を除いて機能します。

ScriptApp.requireScopes は、指定されたスコープが付与されていない場合、スクリプトの実行を停止しますが、ユーザーには OAuth 同意画面ではなく、Chat の設定カードが表示されます。構成カードでは、未付与のスコープだけでなく、リクエストされたすべてのスコープを付与するようユーザーに常に求められます。

個別の認可スコープ レベルのチェックを行うには、ScriptApp.getAuthorizationInfo を使用して認可をチェックし、必要に応じてプライベート メッセージを使用して認可をリクエストします。

次の例は、特定の権限(カレンダーへのアクセスなど)を確認し、権限がない場合は、必要な認証 URL を含むプライベート メッセージを返す方法を示しています。

Apps Script

/**
* Responds to a MESSAGE event in Google Chat.
* Checks for required permissions and if missing asks for them.
*
* @param {Object} event the event object from Chat
* @return {Object} JSON response
*/
function onMessage(event) {
  // Check if the script has the necessary permissions.
  // In this example, the script checks for the "calendar.events" scope.
  var requiredScopes = ['https://www.googleapis.com/auth/calendar.events'];
  var authInfo = ScriptApp.getAuthorizationInfo(ScriptApp.AuthMode.FULL, requiredScopes);

  // If permissions are missing, return a message with the authorization URL.
  if (authInfo.getAuthorizationStatus() === ScriptApp.AuthorizationStatus.REQUIRED) {
    var authUrl = authInfo.getAuthorizationUrl();
    return {
      "text": "This action requires authorization. Please <" + authUrl + "|click here to authorize>.",
      "privateMessageViewer": {
        "name": event.user.name
      }
    };
  }

  // Permission granted; proceed with the application logic.
  // ...
}

HTTP エンドポイント

HTTP エンドポイントを使用して Chat 用アプリを構築する場合、Chat 用アプリはきめ細かい OAuth 権限をサポートする必要があります。

Chat を拡張する Google Workspace アドオン

Chat 用アプリを Google Workspace アドオンとして構築する場合(たとえば、Google ドライブや Gmail などの他の Google Workspace アプリを拡張する場合)、マニフェスト ファイルとコードを構成して、きめ細かい OAuth 権限を処理します。

  1. アドオンのマニフェスト ファイルで、granularOauthPermissionSupport フィールドを OPT_IN に設定します。granularOauthPermissionSupport フィールドの詳細については、きめ細かい OAuth 権限フローに移行するをご覧ください。

    JSON

    {
      "oauthScopes": [
        "https://www.googleapis.com/auth/chat.messages",
        "https://www.googleapis.com/auth/calendar.events"
      ],
      "addOns": {
        "common": {
          "name": "My Chat App",
          "logoUrl": "https://lh3.googleusercontent.com/..."
        },
        "chat": {},
        "httpOptions": {
          "granularOauthPermissionSupport": "OPT_IN"
        }
      }
    }
    
  2. ユーザーが承認したスコープを確認するには、コードで authorizationEventObject.authorizedScopes フィールドを確認します。必要なスコープがない場合は、requesting_google_scopes アクションを返して、不足しているスコープをユーザーに求めるようにします。

    Node.js

    // Check for authorized scopes.
    const authorizedScopes = req.body.authorizationEventObject.authorizedScopes || [];
    if (!authorizedScopes.includes('https://www.googleapis.com/auth/chat.messages')) {
      // Respond with a request for the missing scope.
      res.send({
        'requesting_google_scopes': {
          'scopes': ['https://www.googleapis.com/auth/chat.messages']
        }
      });
      return;
    }
    

    Python

    from flask import jsonify, request
    
    # Check for authorized scopes.
    event_data = request.get_json()
    authorized_scopes = event_data.get('authorizationEventObject', {}).get('authorizedScopes', [])
    if 'https://www.googleapis.com/auth/chat.messages' not in authorized_scopes:
        # Respond with a request for the missing scope.
        return jsonify({
            'requesting_google_scopes': {
                'scopes': ['https://www.googleapis.com/auth/chat.messages']
            }
        })
    

    Java

    import com.google.gson.JsonArray;
    import com.google.gson.JsonObject;
    import java.util.List;
    
    // Check for authorized scopes.
    List<String> authorizedScopes = event.getAuthorizationEventObject().getAuthorizedScopes();
    if (!authorizedScopes.contains("https://www.googleapis.com/auth/chat.messages")) {
      // Respond with a request for the missing scope.
      JsonObject requestingGoogleScopes = new JsonObject();
      JsonArray scopes = new JsonArray();
      scopes.add("https://www.googleapis.com/auth/chat.messages");
      requestingGoogleScopes.add("scopes", scopes);
    
      JsonObject response = new JsonObject();
      response.add("requesting_google_scopes", requestingGoogleScopes);
      return response.toString();
    }
    

    アドオンに関連付けられているすべてのスコープをリクエストするには、all_scopestrue に設定します。

    Node.js

    res.send({
      'requesting_google_scopes': { 'all_scopes': true }
    });
    

    Python

    from flask import jsonify
    
    return jsonify({
        'requesting_google_scopes': { 'all_scopes': True }
    })
    

    Java

    import com.google.gson.JsonObject;
    
    JsonObject requestingGoogleScopes = new JsonObject();
    requestingGoogleScopes.addProperty("all_scopes", true);
    
    JsonObject response = new JsonObject();
    response.add("requesting_google_scopes", requestingGoogleScopes);
    return response.toString();
    

詳細な手順については、HTTP Google Workspace アドオンのきめ細かい権限を管理するをご覧ください。

スタンドアロン HTTP Chat 用アプリ

Chat 用アプリがスタンドアロンの HTTP サービス(Google Workspace アドオンではない)の場合は、OAuth 2.0 フローを自分で管理します。

保存されたトークンを取得したり、認証コードを交換したりするときは、どのスコープが付与されたかを確認します。必要なスコープがない場合は、ユーザーに承認を求めるプロンプトを表示します。

Node.js

// 1. List authorized scopes.
const fs = require('fs');
const tokens = JSON.parse(fs.readFileSync('token.json'));
const grantedScopes = tokens.scope.split(' ');

// 2. Detect missing scopes.
const requiredScopes = ['https://www.googleapis.com/auth/chat.messages'];
const missingScopes = requiredScopes.filter(scope => !grantedScopes.includes(scope));

if (missingScopes.length > 0) {
  // 3. Request missing scopes.
  const authUrl = oauth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: missingScopes,
    include_granted_scopes: true
  });
  res.redirect(authUrl);
}

// To request all scopes instead of just the missing ones:
const allScopesAuthUrl = oauth2Client.generateAuthUrl({
  access_type: 'offline',
  scope: requiredScopes,
  include_granted_scopes: true
});

Python

from flask import redirect
from google.oauth2.credentials import Credentials

# 1. List authorized scopes.
credentials = Credentials.from_authorized_user_file('token.json')
granted_scopes = set(credentials.scopes)

# 2. Detect missing scopes.
required_scopes = {'https://www.googleapis.com/auth/chat.messages'}
missing_scopes = required_scopes - granted_scopes

if missing_scopes:
    # 3. Request missing scopes.
    flow.scope = list(missing_scopes)
    auth_url, _ = flow.authorization_url(
        access_type='offline',
        include_granted_scopes=True
    )
    return redirect(auth_url)

# To request all scopes instead of just the missing ones:
flow.scope = list(required_scopes)
all_scopes_auth_url, _ = flow.authorization_url(
    access_type='offline',
    include_granted_scopes='true'
)

Java

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeRequestUrl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

// 1. List authorized scopes.
// The "user" string is the user ID for which to load credentials.
Credential credential = flow.loadCredential("user");
Collection<String> grantedScopes = credential.getScopes();

// 2. Detect missing scopes.
// The `requiredScopes` variable contains a list of the OAuth scopes
// that your app requires to function. Define this variable with the
// scopes needed by your application.
List<String> requiredScopes = Arrays.asList("https://www.googleapis.com/auth/chat.messages");
List<String> missingScopes = new ArrayList<>();
for (String scope : requiredScopes) {
  if (!grantedScopes.contains(scope)) {
    missingScopes.add(scope);
  }
}

if (!missingScopes.isEmpty()) {
  // 3. Request missing scopes.
  GoogleAuthorizationCodeRequestUrl urlBuilder = new GoogleAuthorizationCodeRequestUrl(
      clientId, redirectUri, missingScopes)
      .setAccessType("offline")
      .set("include_granted_scopes", "true");
  String authUrl = urlBuilder.build();
  response.sendRedirect(authUrl);
}

// To request all scopes instead of just the missing ones:
GoogleAuthorizationCodeRequestUrl allScopesUrlBuilder = new GoogleAuthorizationCodeRequestUrl(
    clientId, redirectUri, requiredScopes)
    .setAccessType("offline")
    .set("include_granted_scopes", "true");
String allScopesAuthUrl = allScopesUrlBuilder.build();

詳細については、きめ細かい OAuth 権限をご覧ください。