Google Workspace 부가기능이 승인이 필요한 서드 파티 서비스 또는 API에 연결되는 경우 부가기능에서 사용자에게 로그인하고 액세스 권한을 승인하라는 메시지를 표시할 수 있습니다.
이 페이지에서는 승인 흐름 (예: OAuth)을 사용하여 사용자를 인증하는 방법을 설명합니다. 여기에는 다음 단계가 포함됩니다.
- 승인이 필요한 경우 감지합니다.
- 사용자에게 서비스에 로그인하라는 메시지를 표시하는 카드 인터페이스를 반환합니다.
- 사용자가 서비스 또는 보호된 리소스에 액세스할 수 있도록 부가기능을 새로고침합니다.
부가기능에 사용자 ID만 필요한 경우 Google Workspace ID 또는 이메일 주소를 사용하여 사용자를 직접 인증할 수 있습니다. 인증에 이메일 주소를 사용하려면 JSON 요청 유효성 검사를 참고하세요. Google Apps Script를 사용하여 부가기능을 빌드한 경우 Google Apps Script용 OAuth2 라이브러리를 사용하면 이 프로세스를 더 쉽게 처리할 수 있습니다 (OAuth1 버전도 있음).
승인이 필요한지 감지
부가기능을 사용할 때 사용자는 다음과 같은 다양한 이유로 보호된 리소스에 액세스할 권한이 없을 수 있습니다.
- 서드 파티 서비스에 연결할 액세스 토큰이 아직 생성되지 않았거나 만료되었습니다.
- 액세스 토큰이 요청된 리소스를 포함하지 않습니다.
- 액세스 토큰이 요청의 필수 범위를 포함하지 않습니다.
사용자가 로그인하고 서비스에 액세스할 수 있도록 하려면 부가기능에서 이러한 사례를 감지해야 합니다.
Apps Script에서 빌드하는 경우 OAuth 라이브러리 hasAccess()
함수를 사용하여 서비스에 액세스할 수 있는지 확인할 수 있습니다.
또는 UrlFetchApp fetch()
요청을 사용할 때 muteHttpExceptions
매개변수를 true
로 설정할 수 있습니다. 이렇게 하면 요청 실패 시 요청이 예외를 발생시키지 않으며 반환된 HttpResponse
객체에서 요청 응답 코드와 콘텐츠를 검사할 수 있습니다.
사용자에게 서비스에 로그인하라는 메시지 표시
부가기능에서 승인이 필요하다고 감지하면 부가기능은 사용자에게 서비스에 로그인하라는 메시지를 표시하는 카드 인터페이스를 반환해야 합니다. 로그인 카드는 사용자를 리디렉션하여 인프라에서 서드 파티 인증 및 승인 프로세스를 완료하도록 해야 합니다.
HTTP 엔드포인트를 사용하여 부가기능을 빌드할 때는 Google 로그인으로 대상 앱을 보호하고 로그인 중에 발급된 ID 토큰을 사용하여 사용자 ID를 가져오는 것이 좋습니다. 하위 클레임에는 사용자의 고유 ID가 포함되며 부가기능의 ID와 연결될 수 있습니다.
로그인 카드 빌드 및 반환
서비스의 로그인 카드에는 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로 연결하는 버튼 또는 유사한 위젯을 포함합니다. 이 위젯의 기능이 사용자에게 명확하게 표시되어야 합니다.
- 위의 위젯은 승인을 받은 후 부가기능이 새로고침되도록
OpenLink
객체의OnClose.RELOAD
설정을 사용해야 합니다. - 승인 메시지에서 열리는 모든 링크는 HTTPS를 사용해야 합니다.
다음 이미지는 부가기능 홈페이지의 맞춤 승인 카드 예시를 보여줍니다. 카드에는 로고, 설명, 로그인 버튼이 포함됩니다.
다음 코드는 이 맞춤 카드 예시를 사용하는 방법을 보여줍니다.
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입니다.- 선택사항: 버튼 색상을 변경하려면
color
필드의 RGBA 부동 소수점 값을 업데이트합니다. Apps Script의 경우 16진수 값을 사용하여setBackgroundColor()
메서드를 업데이트합니다.
TEXT_SIGN_UP
: 계정이 없는 사용자에게 계정을 만들라는 메시지를 표시하는 텍스트입니다. 예를 들면New to Cymbal Labs? <a href=\"https://www.example.com/signup\">Sign up</a> here
입니다.
Google Workspace 앱 전반에서 서드 파티 로그인 관리하기
Google Workspace 부가기능의 일반적인 한 가지 응용 프로그램은 Google Workspace 호스트 애플리케이션 내에서 서드 파티 시스템과 상호작용하는 인터페이스를 제공하는 것입니다.
서드 파티 시스템에서는 사용자가 사용자 ID, 비밀번호 또는 기타 사용자 인증 정보를 사용하여 로그인해야 하는 경우가 많습니다. 사용자가 하나의 Google Workspace 호스트를 사용하는 동안 서드 파티 서비스에 로그인하는 경우 다른 Google Workspace 호스트로 전환할 때 다시 로그인하지 않아도 되도록 해야 합니다.
Apps Script에서 빌드하는 경우 사용자 속성 또는 ID 토큰을 사용하여 로그인 요청이 반복되는 것을 방지할 수 있습니다. 이에 대해서는 다음 섹션에서 설명합니다.
사용자 속성
Apps Script의 사용자 속성에 사용자의 로그인 데이터를 저장할 수 있습니다. 예를 들어 로그인 서비스에서 자체 JSON 웹 토큰 (JWT)을 만들어 사용자 속성에 기록하거나 서비스의 사용자 이름과 비밀번호를 기록할 수 있습니다.
사용자 속성은 부가기능 스크립트 내에서 해당 사용자만 액세스할 수 있도록 범위가 지정됩니다. 다른 사용자와 다른 스크립트는 이러한 속성에 액세스할 수 없습니다. 자세한 내용은 PropertiesService
를 참고하세요.
ID 토큰
Google ID 토큰을 서비스의 로그인 사용자 인증 정보로 사용할 수 있습니다. 이는 싱글 사인온을 달성하는 방법입니다. 사용자가 Google 호스트 앱에 있으므로 이미 Google에 로그인되어 있습니다.
Google 이외의 OAuth 구성 예
다음 Apps Script 코드 샘플은 OAuth가 필요한 Google 이외의 API를 사용하도록 부가기능을 구성하는 방법을 보여줍니다. 이 샘플은 Apps Script용 OAuth2 라이브러리를 사용하여 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();
}