אם התוסף של Google Workspace מתחבר לשירות או ל-API של צד שלישי שדורשים הרשאה, התוסף יכול לבקש מהמשתמשים להיכנס לחשבון ולהעניק הרשאת גישה.
בדף הזה נסביר איך לאמת משתמשים באמצעות תהליך הרשאה (כמו OAuth), שכולל את השלבים הבאים:
- זיהוי המצבים שבהם נדרשת הרשאה.
- להציג ממשק כרטיס שמבקש מהמשתמשים להיכנס לשירות.
- מרעננים את התוסף כדי שהמשתמשים יוכלו לגשת לשירות או למשאב המוגן.
אם התוסף דורש רק את זהות המשתמש, תוכלו לאמת את המשתמשים ישירות באמצעות מזהה Google Workspace או כתובת האימייל שלהם. במאמר אימות בקשות JSON מוסבר איך משתמשים בכתובת האימייל לצורך אימות. אם פיתחתם את התוסף באמצעות Google Apps Script, תוכלו להשתמש בספריית OAuth2 ל-Google Apps Script כדי לפשט את התהליך (יש גם גרסה של OAuth1).
זיהוי הצורך בהרשאה
כשמשתמשים בתוסף, יכול להיות שלמשתמשים לא תהיה הרשאה לגשת למשאב מוגן מסיבות שונות, למשל:
- אסימון הגישה לחיבור לשירות של הצד השלישי עדיין לא נוצר או שתוקפו פג.
- אסימון הגישה לא מכסה את המשאב המבוקש.
- טוקן הגישה לא מכסה את ההיקפים הנדרשים של הבקשה.
התוסף צריך לזהות את המקרים האלה כדי שהמשתמשים יוכלו להיכנס לחשבון ולגשת לשירות שלכם.
אם אתם מפתחים ב-Apps Script, הפונקציה hasAccess()
בספריית OAuth יכולה לבדוק אם יש לכם גישה לשירות.
לחלופין, כשמשתמשים בבקשות UrlFetchApp fetch()
, אפשר להגדיר את הפרמטר muteHttpExceptions
ל-true
. כך הבקשה לא תגרום להשלכת חריגה במקרה של כשל בבקשה, ותוכלו לבדוק את קוד התגובה והתוכן של הבקשה באובייקט HttpResponse
המוחזר.
בקשה מהמשתמשים להיכנס לשירות
כשתוסף מזהה שנדרש אישור, הוא צריך להציג ממשק כרטיס כדי לבקש מהמשתמשים להיכנס לחשבון בשירות. כרטיס הכניסה צריך להפנות את המשתמשים להשלים את תהליך האימות וההרשאה של הצד השלישי בתשתית שלכם.
כשאתם יוצרים את התוסף באמצעות נקודות קצה מסוג HTTP, מומלץ להגן על אפליקציית היעד באמצעות כניסה באמצעות חשבון Google, ולקבל את מזהה המשתמש באמצעות אסימון הזהות שהונפקה במהלך הכניסה. הצהרת המשנה מכילה את המזהה הייחודי של המשתמש, וניתן לשייך אותה למזהה מהתוסף.
יצירת כרטיס כניסה והחזרתו
בכרטיס הכניסה לשירות, אפשר להשתמש בכרטיס ההרשאה הבסיסי של 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
, ההודעה שתופיע תהיה "התוסף הזה רוצה להציג מידע נוסף, אבל צריך לאשר לו גישה לחשבון Example".
אחרי השלמת ההרשאה, המשתמש מתבקש לרענן את התוסף כדי לגשת למשאב המוגן.
כרטיס הרשאה מותאם אישית
כדי לשנות את הבקשה לאישור, אפשר ליצור כרטיס מותאם אישית לחוויית הכניסה לשירות.
אם אתם מפרסמים את התוסף באופן ציבורי, עליכם להשתמש בכרטיס הרשאה בהתאמה אישית. למידע נוסף על דרישות הפרסום ב-Google Workspace Marketplace, אפשר לעיין במאמר מידע על בדיקת אפליקציות.
הכרטיס שהוחזר צריך לעמוד בדרישות הבאות:
- חשוב להבהיר למשתמש שהתוסף מבקש הרשאה לגשת בשמם לשירות שאינו של Google.
- חשוב להבהיר מה התוסף יכול לעשות אם יתקבל אישור.
- מכילים לחצן או ווידג'ט דומה שמעבירים את המשתמש לכתובת ה-URL של השירות לצורך הרשאה. חשוב לוודא שהפונקציה של הווידג'ט הזה ברורה למשתמש.
- הווידג'ט שלמעלה צריך להשתמש בהגדרה
OnClose.RELOAD
באובייקטOpenLink
שלו כדי לוודא שהתוסף יטען מחדש אחרי קבלת ההרשאה. - כל הקישורים שנפתחים מההנחיה לאישור חייבים להשתמש ב-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 של אפליקציית האינטרנט שמטפלת באישור.- אופציונלי: כדי לשנות את צבע הלחצן, מעדכנים את ערכי ה-float של RGBA בשדה
color
. ב-Apps Script, מעדכנים את השיטה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.
מערכות של צדדים שלישיים מחייבות בדרך כלל את המשתמש להיכנס באמצעות מזהה משתמש, סיסמה או פרטי כניסה אחרים. כשמשתמש נכנס לשירות שלכם של צד שלישי בזמן שהוא משתמש במארח אחד של Google Workspace, עליכם לוודא שהוא לא יצטרך להיכנס שוב כשיעביר את החשבון למארח אחר של Google Workspace.
אם אתם מפתחים ב-Apps Script, תוכלו למנוע בקשות כניסה חוזרות באמצעות מאפייני משתמשים או אסימוני מזהה. הם מוסברים בקטעים הבאים.
מאפייני משתמש
אפשר לאחסן את נתוני הכניסה של משתמש במאפייני המשתמש של Apps Script. לדוגמה, תוכלו ליצור אסימון אינטרנט מסוג JSON (JWT) משלכם משירות ההתחברות שלהם ולתעד אותו במאפיין משתמש, או לתעד את שם המשתמש והסיסמה של השירות שלהם.
מאפייני המשתמשים מוגדרים כך שרק המשתמש הזה יוכל לגשת אליהם בסקריפט של התוסף. משתמשים אחרים וסקריפטים אחרים לא יכולים לגשת למאפיינים האלה. לפרטים נוספים, ראו PropertiesService
.
אסימונים מזהים
אפשר להשתמש באסימון מזהה של Google כפרטי הכניסה לשירות. זוהי דרך להשיג כניסה יחידה (SSO). המשתמשים כבר מחוברים ל-Google כי הם נמצאים באפליקציית מארח של Google.
דוגמה להגדרה של OAuth שאינו של Google
בדוגמת הקוד הבאה ב-Apps Script מוסבר איך להגדיר תוסף לשימוש ב-API שאינו של Google שמחייב OAuth. בדוגמה הזו נעשה שימוש בספריית OAuth2 ל-Apps Script כדי ליצור שירות לגישה ל-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();
}