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

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

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

Apps Script

Apps Script を使用して Chat 用アプリを構築する場合、Apps Script はきめ細かい OAuth 権限を自動的に処理します。ただし、ユーザーがリクエストされたすべてのスコープを付与しないケースをコードで処理するようにしてください。Apps Script でのきめ細かい OAuth 権限の処理の手順に沿って操作します。

HTTP エンドポイント

HTTP エンドポイントを使用して Chat 用アプリを構築する場合は、きめ細かい OAuth 権限を処理するようにコードを構成します。ユーザーが付与した認証スコープを確認し、必要に応じて、不足しているスコープまたはすべてのスコープの認証をリクエストします。

  1. Chat 用アプリのマニフェスト ファイルで、oauthScopes フィールドに必要な認証スコープを指定します。このフィールドは projects.deployments リソースの一部です。

    次の例では、chat.messages と calendar.events の認可スコープが必要です。

    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": {},
        "calendar": {},
        "httpOptions": {}
      }
    }
    
  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() != null
        ? event.getAuthorizationEventObject().getAuthorizedScopes()
        : null;
    if (authorizedScopes == null || !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();
    }
    

    Chat 用アプリに関連付けられているすべてのスコープをリクエストするには、all_scopes を true に設定します。

    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 アドオンの詳細な権限を管理するをご覧ください。

アドオンではない Chat 用アプリ: Google Chat 用アプリの詳細な OAuth 権限を管理する

Google Workspace アドオンではない Chat 用アプリを管理している場合は、次の手順に沿ってきめ細かい 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 Chat 用アプリ

アドオンではない Chat 用アプリが HTTP サービスの場合、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();