Google Chat 앱의 세부적인 OAuth 권한 관리하기

사용자 인증을 사용하는 채팅 앱은 사용자가 요청된 범위의 하위 집합을 부여할 수 있도록 세분화된 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 부가기능

Chat 앱을 Apps Script를 사용하여 Google Chat을 확장하는 Google Workspace 부가기능으로 빌드하는 경우 Apps Script에서 세분화된 OAuth 권한 처리의 안내를 따르세요.

독립형 Apps Script Chat 앱

Apps Script 및 상호작용 이벤트를 사용하여 Chat 앱을 빌드하는 경우 Apps Script에서 세부적인 OAuth 권한 처리의 안내는 다음 사항을 고려하여 적용됩니다.

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 엔드포인트를 사용하여 채팅 앱을 빌드하는 경우 채팅 앱은 세부적인 OAuth 권한을 지원해야 합니다.

Chat을 확장하는 Google Workspace 부가기능

Chat 앱을 Google Workspace 부가기능으로 빌드하는 경우 (예: Google Drive 또는 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']
            }
        })
    

    자바

    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 }
    })
    

    자바

    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'
)

자바

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 권한을 참고하세요.