OAuth2 for Apps Script
OAuth2 for Apps Script is a library for Google Apps Script that provides the
+ OAuth2 for Apps Script is a library for Google Apps Script that provides the
ability to create and authorize OAuth2 tokens as well as refresh them when they
-expire. This library uses Apps Script's new
+expire. This library uses Apps Script's
StateTokenBuilder
and This library is already published as an Apps Script, making it easy to include
+ If you are trying to connect to a Google API from Apps Script you might not need
+to use this library at all. Apps Script has a number of easy-to-use,
+built-in services, as well as a variety of
+advanced services that wrap existing Google REST APIs. Even if your API is not covered by either, you can still use Apps Script to
+obtain the OAuth2 token for you. Simply
+edit the script's manifest to
+include the additional scopes that your API requires.
+When the user authorizes your script they will also be asked to approve those
+additional scopes. Then use the method Visit the sample This library is already published as an Apps Script, making it easy to include
in your project. To add it to your script, do the following in the Apps Script
code editor: This library is already published as an Apps Script, making it
Before you can start authenticating against an OAuth2 provider, you usually need
-to register your application and retrieve the client ID and secret. Often
-these registration screens require you to enter a "Redirect URI", which is the
-URL that users will be redirected to after they've authorized the token. For
-this library (and the Apps Script functionality in general) the URL will always
-be in the following format: Where Before you can start authenticating against an OAuth2 provider, you usually need
+to register your application with that OAuth2 provider and obtain a client ID
+and secret. Often a provider's registration screen requires you to enter a
+"Redirect URI", which is the URL that the user's browser will be redirected to
+after they've authorized access to their account at that provider. For this library (and the Apps Script functionality in general) the URL will
+always be in the following format: Where Alternatively you can call the service's Using the library to generate an OAuth2 token has the following basic steps. The OAuth2Service class contains the configuration information for a given
+}
+
+ Using the library to generate an OAuth2 token has the following basic steps. The OAuth2Service class contains the configuration information for a given
OAuth2 provider, including its endpoints, client IDs and secrets, etc. This
information is not persisted to any data store, so you'll need to create this
object each time you want to use it. The example below shows how to create a
service for the Google Drive API. The OAuth2Service class contains the con
// Sets the login hint, which will prevent the account chooser screen
// from being shown to users logged in with multiple accounts.
- .setParam('login_hint', Session.getActiveUser().getEmail())
+ .setParam('login_hint', Session.getEffectiveUser().getEmail())
// Requests offline access.
.setParam('access_type', 'offline')
- // Forces the approval prompt every time. This is useful for testing,
- // but not desirable in a production application.
- .setParam('approval_prompt', 'force');
-} Apps Script UI's are not allowed to redirect the user's window to a new URL, so
+ // Consent prompt is required to ensure a refresh token is always
+ // returned when requesting offline access.
+ .setParam('prompt', 'consent');
+}
+ Apps Script UI's are not allowed to redirect the user's window to a new URL, so
you'll need to present the authorization URL as a link for the user to click.
The URL is generated by the service, using the function The OAuth2Service class contains the con
} else {
// ...
}
-} When the user completes the OAuth2 flow, the callback function you specified
+}
+
+ When the user completes the OAuth2 flow, the callback function you specified
for your service will be invoked. This callback function should pass its
request object to the service's If the authorization URL was opened by the Apps Script UI (via a link, button,
-etc) it's possible to automatically close the window/tab using
- If the authorization URL was opened by the Apps Script UI (via a link, button,
+etc) it's possible to automatically close the window/tab using
+ Now that the service is authorized you can use its access token to make
-reqests to the API. The access token can be passed along with a Now that the service is authorized you can use its access token to make
+requests to the API. The access token can be passed along with a This library was designed to work with any OAuth2 provider, but because of small
-differences in how they implement the standard it may be that some APIs
-aren't compatible. If you find an API that it doesn't work with, open an issue or
-fix the problem yourself and make a pull request against the source code. See below for some features of the library you may need to utilize depending on
-the specifics of the OAuth provider you are connecting to. See the generated
+}
+
+ To logout the user or disconnect the service, perhaps so the user can select a
+different account, use the In almost all cases you'll want to persist the OAuth tokens after you retrieve
+them. This prevents having to request access from the user every time you want
+to call the API. To do so, make sure you set a properties store when you define
+your service: Apps Script has property stores scoped to the user, script,
+or document. In most cases you'll want to choose user-scoped properties, as it
+is most common to have each user of your script authorize access to their own
+account. However there are uses cases where you'd want to authorize access to
+a shared resource and then have all users of the script (or on the same
+document) share that access. When using a service account or 2-legged OAuth flow, where users aren't prompted
+for authorization, storing tokens is still beneficial as there can be rate
+limits on generating new tokens. However there are edge cases where you need to
+generate lots of different tokens in a short amount of time, and persisting
+those tokens to properties can exceed your Scripts that use the library heavily should enable caching on the service, so as
+to not exhaust their Make sure to select a cache with the same scope (user, script, or document) as
+the property store you configured. A race condition can occur when two or more script executions are both trying to
+refresh an expired token at the same time. This is sometimes observed in
+Gmail Add-ons, where a user
+quickly paging through their email can trigger the same add-on multiple times. To prevent this, use locking to ensure that only one execution is refreshing
+the token at a time. To enable locking, simply add a Make sure to select a lock with the same scope (user, script, or document) as
+the property store and cache you configured. See below for some features of the library you may need to utilize depending on
+the specifics of the OAuth provider you are connecting to. See the generated
reference documentation
for a complete list of methods available. If you have an access token set and need to remove it from the property store
-you can remove it with the OAuth services can return a token in two ways: as JSON or an URL encoded
+ OAuth services can return a token in two ways: as JSON or an URL encoded
string. You can set which format the token is in with
Some services, such as the FitBit API, require you to set an Authorization
+ Some services, such as the FitBit API, require you to set an Authorization
header on access token requests. The See the FitBit sample for the complete code. Similar to Setting additional token headers, some services, such as the
-Smartsheet API, require you to
-add a hash to the access token request payloads.
-The See the FitBit sample for the complete code. Almost all services use the Some OAuth providers, such as the Smartsheet API, require you to
+add a hash to the access token request payloads.
+The See the Smartsheet sample for the complete code. This library supports the service account authorization flow, also known as the
+.setTokenPayloadHandler(myTokenHandler)
+
+ See the Smartsheet sample for the complete code. Some OAuth providers return IDs and other critical information in the callback
+URL along with the authorization code. While it's possible to capture and store
+these separately, they often have a lifecycle closely tied to that of the token
+and it makes sense to store them together. You can use For example, the Harvest API returns the account ID of the authorized account
+in the callback URL. In the following code the account ID is extracted from the
+request parameters and saved saved into storage. When making an authorized request the account ID is retrieved from storage and
+passed via a header. Note that calling There are occasionally cases where you need to preserve some data through the
+OAuth flow, so that it is available in your callback function. Although you
+could use the token storage mechanism discussed above for that purpose, writing
+to the PropertiesService is expensive and not neccessary in the case where the
+user doesn't start or fails to complete the OAuth flow. As an alternative you can store small amounts of data in the OAuth2 These values will be stored along-side Apps Script's internal information in the
+encypted This library supports the service account authorization flow, also known as the
JSON Web Token (JWT) Profile.
This is a two-legged OAuth flow that doesn't require a user to visit a URL and
authorize access. One common use for service accounts with Google APIs is
domain-wide delegation.
-This process allows a Google Apps for Work/EDU domain administrator to grant an
+This process allows a G Suite domain administrator to grant an
application access to all the users within the domain. When the application
wishes to access the resources of a particular user, it uses the service account
authorization flow to obtain an access token. See the sample
Although optimized for the authorization code (3-legged) and service account
+(JWT bearer) flows, this library supports arbitrary flows using the
+ The most common of these is the See the sample The service name passed in to the Occasionally you may need to make multiple connections to the same API, for
+example if your script is trying to copy data from one account to another. In
+those cases you'll need to devise your own method for creating unique service
+names: You can list all of the service names you've previously stored tokens for using
+ This library was designed to work with any OAuth2 provider, but because of small
+differences in how they implement the standard it may be that some APIs
+aren't compatible. If you find an API that it doesn't work with, open an issue
+or fix the problem yourself and make a pull request against the source code. This library is designed for server-side OAuth flows, and client-side flows with
+implicit grants ( You are setting explicit scopes
-in your manifest file but have forgotten to add the
+ You are setting explicit scopes
+in your manifest file but have forgotten to add the
OAuth2 for Apps Script
+/usercallback endpoint to handle the redirects.Setup
Connecting to a Google API
+ScriptApp.getOAuthToken()
+in your code to access the OAuth2 access token the script has acquired and pass
+it in the Authorization header of a UrlFetchApp.fetch() call.NoLibrary to see an example of how this
+can be done.Setup
+
@@ -67,30 +100,38 @@
Setup
-https://www.googleapis.com/auth/script.external_requestRedirect URI
https://script.google.com/macros/d/{SCRIPT ID}/usercallback{SCRIPT ID} is the ID of the script that is using this library. You
+Redirect URI
+
+https://script.google.com/macros/d/{SCRIPT ID}/usercallback
+{SCRIPT ID} is the ID of the script that is using this library. You
can find your script's ID in the Apps Script code editor by clicking on
the menu item "File > Project properties".getRedirectUri() method to view the
exact URL that the service will use when performing the OAuth flow:/**
- * Logs the redict URI to register.
+ * Logs the redirect URI to register.
*/
function logRedirectUri() {
- var service = getService();
+ var service = getService_();
Logger.log(service.getRedirectUri());
-}Usage
1. Create the OAuth2 service
Usage
+1. Create the OAuth2 service
+
+function getDriveService() {
+function getDriveService_() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
@@ -118,19 +159,22 @@ 1. Create the OAuth2 service
2. Direct the user to the authorization URL
2. Direct the user to the authorization URL
+getAuthorizationUrl().function showSidebar() {
- var driveService = getDriveService();
+ var driveService = getDriveService_();
if (!driveService.hasAccess()) {
var authorizationUrl = driveService.getAuthorizationUrl();
var template = HtmlService.createTemplate(
@@ -142,90 +186,291 @@ 1. Create the OAuth2 service
3. Handle the callback
3. Handle the callback
+handleCallback function, and show a message
to the user.function authCallback(request) {
- var driveService = getDriveService();
+ var driveService = getDriveService_();
var isAuthorized = driveService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
-}window.top.close(). You can see an example of this in the sample add-on's
+}
+
+window.top.close(). You can see an example of this in the sample add-on's
Callback.html.4. Get the access token
UrlFetchApp
+4. Get the access token
+UrlFetchApp
request in the "Authorization" header.function makeRequest() {
- var driveService = getDriveService();
+ var driveService = getDriveService_();
var response = UrlFetchApp.fetch('https://github.com/service/https://www.googleapis.com/drive/v2/files?maxResults=10', {
headers: {
Authorization: 'Bearer ' + driveService.getAccessToken()
}
});
// ...
-}Compatibility
Other features
Logout
+reset() method:
+function logout() {
+ var service = getDriveService_()
+ service.reset();
+}
+Best practices
+Token storage
+
+return OAuth2.createService('Foo')
+ .setPropertyStore(PropertiesService.getUserProperties())
+ // ...
+PropertiesService quota. In those
+cases you can omit any form of token storage and just retrieve new ones as
+needed.Caching
+PropertiesService quotas. To enable caching, simply add
+a CacheService cache when configuring the service:
+return OAuth2.createService('Foo')
+ .setPropertyStore(PropertiesService.getUserProperties())
+ .setCache(CacheService.getUserCache())
+ // ...
+Locking
+LockService lock when
+configuring the service:
+return OAuth2.createService('Foo')
+ .setPropertyStore(PropertiesService.getUserProperties())
+ .setCache(CacheService.getUserCache())
+ .setLock(LockService.getUserLock())
+ // ...
+Advanced configuration
+Resetting the access token
reset() function. Before you can call reset you
-need to set the property store.function clearService(){
- OAuth2.createService('drive')
- .setPropertyStore(PropertiesService.getUserProperties())
- .reset();
-}Setting the token format
Setting the token format
+setTokenFormat(tokenFormat). There are two ENUMS to set the mode:
TOKEN_FORMAT.FORM_URL_ENCODED and TOKEN_FORMAT.JSON. JSON is set as default
if no token format is chosen.Setting additional token headers
Setting additional token headers
+setTokenHeaders() method allows you
to pass in a JavaScript object of additional header key/value pairs to be used
in these requests..setTokenHeaders({
'Authorization': 'Basic ' + Utilities.base64Encode(CLIENT_ID + ':' + CLIENT_SECRET)
-});Modifying the access token payload
setTokenPayloadHandler method allows you to pass in a function to modify
-the payload of an access token request before the request is sent to the token
+});
+
+Setting the token HTTP method
+POST HTTP method when retrieving the access token,
+but a few services deviate from the spec and use the PUT method instead. To
+accomodate those cases you can use the setTokenMethod() method to specify the
+HTTP method to use when making the request.Modifying the access token payload
+setTokenPayloadHandler method allows you to pass in a function to modify
+the payload of an access token request before the request is sent to the token
endpoint:// Set the handler for modifying the access token request payload:
-.setTokenPayloadHandler(myTokenHandler)Service Accounts
Storing token-related data
+Service.getStorage() to
+retrieve the token storage system for the service and set custom key-value
+pairs.
+function authCallback(request) {
+ var service = getService_();
+ var authorized = service.handleCallback(request);
+ if (authorized) {
+ // Gets the authorized account ID from the scope string. Assumes the
+ // application is configured to work with single accounts. Has the format
+ // "harvest:{ACCOUNT_ID}".
+ var scope = request.parameter['scope'];
+ var accountId = scope.split(':')[1];
+ // Save the account ID in the service's storage.
+ service.getStorage().setValue('Harvest-Account-Id', accountId);
+ return HtmlService.createHtmlOutput('Success!');
+ } else {
+ return HtmlService.createHtmlOutput('Denied.');
+ }
+}
+
+if (service.hasAccess()) {
+ // Retrieve the account ID from storage.
+ var accountId = service.getStorage().getValue('Harvest-Account-Id');
+ var url = 'https://api.harvestapp.com/v2/users/me';
+ var response = UrlFetchApp.fetch(url, {
+ headers: {
+ 'Authorization': 'Bearer ' + service.getAccessToken(),
+ 'User-Agent': 'Apps Script Sample',
+ 'Harvest-Account-Id': accountId
+ }
+ });
+Service.reset() will remove all custom values from storage,
+in addition to the token.Passing additional parameters to the callback function
+state
+token, which is a standard mechanism for this purpose. To do so, pass an
+optional hash of parameter names and values to the getAuthorizationUrl()
+method:
+var authorizationUrl = getService_().getAuthorizationUrl({
+ // Pass the additional parameter "lang" with the value "fr".
+ lang: 'fr'
+});
+state token, which is passed in the authorization URL and passed back
+to the redirect URI. The state token is automatically decrypted in the
+callback function and you can access your parameters using the same
+request.parameter field used in web apps:
+function authCallback(request) {
+ var lang = request.parameter.lang;
+ // ...
+}
+Using service accounts
+GoogleServiceAccount.gs for more
information.Breaking changes
-
Using alternative grant types
+setGrantType() method. Use setParam() or setTokenPayloadHandler() to add
+fields to the token request payload, and setTokenHeaders() to add any required
+headers.client_credentials grant type, which often
+requires that the client ID and secret are passed in the Authorization header.
+When using this grant type, if you set a client ID and secret using
+setClientId() and setClientSecret() respectively then an
+Authorization: Basic ... header will be added to the token request
+automatically, since this is what most OAuth2 providers require. If your
+provider uses a different method of authorization then don't set the client ID
+and secret and add an authorization header manually.TwitterAppOnly.gs for a working
+example.Frequently Asked Questions
+How can I connect to multiple OAuth services?
+createService method forms part of the key
+used when storing and retrieving tokens in the property store. To connect to
+multiple services merely ensure they have different service names. Often this
+means selecting a service name that matches the API the user will authorize:
+function run() {
+ var gitHubService = getGitHubService_();
+ var mediumService = getMediumService_();
+ // ...
+}
+
+function getGitHubService_() {
+ return OAuth2.createService('GitHub')
+ // GitHub settings ...
+}
+
+function getMediumService_() {
+ return OAuth2.createService('Medium')
+ // Medium settings ...
+}
+
+function run() {
+ var copyFromService = getGitHubService_('from');
+ var copyToService = getGitHubService_('to');
+ // ...
+}
+
+function getGitHubService_(label) {
+ return OAuth2.createService('GitHub_' + label)
+ // GitHub settings ...
+}
+OAuth2.getServiceNames(propertyStore).Compatibility
+response_type=token) are not supported.Breaking changes
+
+
-https://script.google.com/macros/d/{SCRIPT ID}/usercallback.Service.getToken_() to Service.getToken(), since
there OAuth providers that return important information in the token response.Troubleshooting
You do not have permission to call fetch
Troubleshooting
+You do not have permission to call fetch
+https://www.googleapis.com/auth/script.external_request scope used by this library
(and eventually the UrlFetchApp request you are making to an API).