{"id":13527951,"url":"https://github.com/googleworkspace/apps-script-oauth2","last_synced_at":"2025-05-14T06:14:39.916Z","repository":{"id":19604629,"uuid":"22855593","full_name":"googleworkspace/apps-script-oauth2","owner":"googleworkspace","description":"An OAuth2 library for Google Apps Script.","archived":false,"fork":false,"pushed_at":"2024-10-21T08:45:57.000Z","size":2425,"stargazers_count":1547,"open_issues_count":45,"forks_count":429,"subscribers_count":82,"default_branch":"main","last_synced_at":"2024-10-29T15:34:39.748Z","etag":null,"topics":["apps-script","google-apps-script","google-workspace","gsuite","oauth"],"latest_commit_sha":null,"homepage":"https://developers.google.com/apps-script/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/googleworkspace.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-08-11T21:32:06.000Z","updated_at":"2024-10-29T01:33:01.000Z","dependencies_parsed_at":"2024-01-24T12:04:52.221Z","dependency_job_id":"714e0c6c-7142-42f6-a65e-36fd5ee1a287","html_url":"https://github.com/googleworkspace/apps-script-oauth2","commit_stats":{"total_commits":344,"total_committers":54,"mean_commits":6.37037037037037,"dds":0.6337209302325582,"last_synced_commit":"f57c727da61be0c51e12a465368bb669216996ff"},"previous_names":["gsuitedevs/apps-script-oauth2","googlesamples/apps-script-oauth2"],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/googleworkspace%2Fapps-script-oauth2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/googleworkspace%2Fapps-script-oauth2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/googleworkspace%2Fapps-script-oauth2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/googleworkspace%2Fapps-script-oauth2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/googleworkspace","download_url":"https://codeload.github.com/googleworkspace/apps-script-oauth2/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248469101,"owners_count":21108958,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apps-script","google-apps-script","google-workspace","gsuite","oauth"],"created_at":"2024-08-01T06:02:07.961Z","updated_at":"2025-04-11T19:44:43.256Z","avatar_url":"https://github.com/googleworkspace.png","language":"JavaScript","readme":"OAuth2 for Apps Script is a library for Google Apps Script that provides the\nability to create and authorize OAuth2 tokens as well as refresh them when they\nexpire. This library uses Apps Script's\n[StateTokenBuilder](https://developers.google.com/apps-script/reference/script/state-token-builder)\nand `/usercallback` endpoint to handle the redirects.\n\n## Connecting to a Google API\n\nIf you are trying to connect to a Google API from Apps Script you might not need\nto use this library at all. Apps Script has a number of easy-to-use,\n[built-in services][built_in], as well as a variety of\n[advanced services][advanced] that wrap existing Google REST APIs.\n\nEven if your API is not covered by either, you can still use Apps Script to\nobtain the OAuth2 token for you. Simply\n[edit the script's manifest][edit_manifest] to\n[include the additional scopes][additional_scopes] that your API requires.\nWhen the user authorizes your script they will also be asked to approve those\nadditional scopes. Then use the method [`ScriptApp.getOAuthToken()`][scriptapp]\nin your code to access the OAuth2 access token the script has acquired and pass\nit in the `Authorization` header of a `UrlFetchApp.fetch()` call.\n\nVisit the sample [`NoLibrary`](samples/NoLibrary) to see an example of how this\ncan be done.\n\n[built_in]: https://developers.google.com/apps-script/reference/calendar/\n[advanced]: https://developers.google.com/apps-script/advanced/admin-sdk-directory\n[edit_manifest]: https://developers.google.com/apps-script/concepts/manifests#editing_a_manifest\n[additional_scopes]: https://developers.google.com/apps-script/concepts/scopes#setting_explicit_scopes\n[scriptapp]: https://developers.google.com/apps-script/reference/script/script-app#getoauthtoken\n\n## Setup\n\nThis library is already published as an Apps Script, making it easy to include\nin your project. To add it to your script, do the following in the Apps Script\ncode editor:\n\n1. Click on the menu item \"Resources \u003e Libraries...\"\n2. In the \"Find a Library\" text box, enter the script ID\n   `1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF` and click the\n   \"Select\" button.\n3. Choose a version in the dropdown box (usually best to pick the latest\n   version).\n4. Click the \"Save\" button.\n\nAlternatively, you can copy and paste the files in the [`/dist`](dist) directory\ndirectly into your script project.\n\nIf you are [setting explicit scopes](https://developers.google.com/apps-script/concepts/scopes#setting_explicit_scopes)\nin your manifest file, ensure that the following scope is included:\n\n* `https://www.googleapis.com/auth/script.external_request`\n\n## Redirect URI\n\nBefore you can start authenticating against an OAuth2 provider, you usually need\nto register your application with that OAuth2 provider and obtain a client ID\nand secret. Often a provider's registration screen requires you to enter a\n\"Redirect URI\", which is the URL that the user's browser will be redirected to\nafter they've authorized access to their account at that provider.\n\nFor this library (and the Apps Script functionality in general) the URL will\nalways be in the following format:\n\n    https://script.google.com/macros/d/{SCRIPT ID}/usercallback\n\nWhere `{SCRIPT ID}` is the ID of the script that is using this library. You\ncan find your script's ID in the Apps Script code editor by clicking on\nthe menu item \"File \u003e Project properties\".\n\nAlternatively you can call the service's `getRedirectUri()` method to view the\nexact URL that the service will use when performing the OAuth flow:\n\n```js\n/**\n * Logs the redirect URI to register.\n */\nfunction logRedirectUri() {\n  var service = getService_();\n  Logger.log(service.getRedirectUri());\n}\n```\n\n## Usage\n\nUsing the library to generate an OAuth2 token has the following basic steps.\n\n### 1. Create the OAuth2 service\n\nThe OAuth2Service class contains the configuration information for a given\nOAuth2 provider, including its endpoints, client IDs and secrets, etc. This\ninformation is not persisted to any data store, so you'll need to create this\nobject each time you want to use it. The example below shows how to create a\nservice for the Google Drive API.\n\nEnsure the method is private (has an underscore at the end of the name) to\nprevent clients from being able to call the method to read your client ID and\nsecret.\n\n```js\nfunction getDriveService_() {\n  // Create a new service with the given name. The name will be used when\n  // persisting the authorized token, so ensure it is unique within the\n  // scope of the property store.\n  return OAuth2.createService('drive')\n\n      // Set the endpoint URLs, which are the same for all Google services.\n      .setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')\n      .setTokenUrl('https://accounts.google.com/o/oauth2/token')\n\n      // Set the client ID and secret, from the Google Developers Console.\n      .setClientId('...')\n      .setClientSecret('...')\n\n      // Set the name of the callback function in the script referenced\n      // above that should be invoked to complete the OAuth flow.\n      .setCallbackFunction('authCallback')\n\n      // Set the property store where authorized tokens should be persisted.\n      .setPropertyStore(PropertiesService.getUserProperties())\n\n      // Set the scopes to request (space-separated for Google services).\n      .setScope('https://www.googleapis.com/auth/drive')\n\n      // Below are Google-specific OAuth2 parameters.\n\n      // Sets the login hint, which will prevent the account chooser screen\n      // from being shown to users logged in with multiple accounts.\n      .setParam('login_hint', Session.getEffectiveUser().getEmail())\n\n      // Requests offline access.\n      .setParam('access_type', 'offline')\n\n      // Consent prompt is required to ensure a refresh token is always\n      // returned when requesting offline access.\n      .setParam('prompt', 'consent');\n}\n```\n\n### 2. Direct the user to the authorization URL\n\nApps Script UI's are not allowed to redirect the user's window to a new URL, so\nyou'll need to present the authorization URL as a link for the user to click.\nThe URL is generated by the service, using the function `getAuthorizationUrl()`.\n\n```js\nfunction showSidebar() {\n  var driveService = getDriveService_();\n  if (!driveService.hasAccess()) {\n    var authorizationUrl = driveService.getAuthorizationUrl();\n    var template = HtmlService.createTemplate(\n        '\u003ca href=\"\u003c?= authorizationUrl ?\u003e\" target=\"_blank\"\u003eAuthorize\u003c/a\u003e. ' +\n        'Reopen the sidebar when the authorization is complete.');\n    template.authorizationUrl = authorizationUrl;\n    var page = template.evaluate();\n    DocumentApp.getUi().showSidebar(page);\n  } else {\n  // ...\n  }\n}\n```\n\n### 3. Handle the callback\n\nWhen the user completes the OAuth2 flow, the callback function you specified\nfor your service will be invoked. This callback function should pass its\nrequest object to the service's `handleCallback` function, and show a message\nto the user.\n\n```js\nfunction authCallback(request) {\n  var driveService = getDriveService_();\n  var isAuthorized = driveService.handleCallback(request);\n  if (isAuthorized) {\n    return HtmlService.createHtmlOutput('Success! You can close this tab.');\n  } else {\n    return HtmlService.createHtmlOutput('Denied. You can close this tab');\n  }\n}\n```\n\nIf the authorization URL was opened by the Apps Script UI (via a link, button,\netc) it's  possible to automatically close the window/tab using\n`window.top.close()`. You can see an example of this in the sample add-on's\n[Callback.html](samples/Add-on/Callback.html#L47).\n\n### 4. Get the access token\n\nNow that the service is authorized you can use its access token to make\nrequests to the API. The access token can be passed along with a `UrlFetchApp`\nrequest in the \"Authorization\" header.\n\n```js\nfunction makeRequest() {\n  var driveService = getDriveService_();\n  var response = UrlFetchApp.fetch('https://www.googleapis.com/drive/v2/files?maxResults=10', {\n    headers: {\n      Authorization: 'Bearer ' + driveService.getAccessToken()\n    }\n  });\n  // ...\n}\n```\n\n\n### Logout\n\nTo logout the user or disconnect the service, perhaps so the user can select a\ndifferent account, use the `reset()` method:\n\n```js\nfunction logout() {\n  var service = getDriveService_()\n  service.reset();\n}\n```\n\n## Best practices\n\n### Token storage\n\nIn almost all cases you'll want to persist the OAuth tokens after you retrieve\nthem. This prevents having to request access from the user every time you want\nto call the API. To do so, make sure you set a properties store when you define\nyour service:\n\n```js\nreturn OAuth2.createService('Foo')\n    .setPropertyStore(PropertiesService.getUserProperties())\n    // ...\n```\n\nApps Script has [property stores][property_stores] scoped to the user, script,\nor document. In most cases you'll want to choose user-scoped properties, as it\nis most common to have each user of your script authorize access to their own\naccount. However there are uses cases where you'd want to authorize access to\na shared resource and then have all users of the script (or on the same\ndocument) share that access.\n\nWhen using a service account or 2-legged OAuth flow, where users aren't prompted\nfor authorization, storing tokens is still beneficial as there can be rate\nlimits on generating new tokens. However there are edge cases where you need to\ngenerate lots of different tokens in a short amount of time, and persisting\nthose tokens to properties can exceed your `PropertiesService` quota. In those\ncases you can omit any form of token storage and just retrieve new ones as\nneeded.\n\n[property_stores]: https://developers.google.com/apps-script/reference/properties/properties-service\n\n### Caching\n\nScripts that use the library heavily should enable caching on the service, so as\nto not exhaust their `PropertiesService` quotas. To enable caching, simply add\na `CacheService` cache when configuring the service:\n\n```js\nreturn OAuth2.createService('Foo')\n    .setPropertyStore(PropertiesService.getUserProperties())\n    .setCache(CacheService.getUserCache())\n    // ...\n```\n\nMake sure to select a cache with the same scope (user, script, or document) as\nthe property store you configured.\n\n### Locking\n\nA race condition can occur when two or more script executions are both trying to\nrefresh an expired token at the same time. This is sometimes observed in\n[Gmail Add-ons](https://developers.google.com/gmail/add-ons/), where a user\nquickly paging through their email can trigger the same add-on multiple times.\n\nTo prevent this, use locking to ensure that only one execution is refreshing\nthe token at a time. To enable locking, simply add a `LockService` lock when\nconfiguring the service:\n\n```js\nreturn OAuth2.createService('Foo')\n    .setPropertyStore(PropertiesService.getUserProperties())\n    .setCache(CacheService.getUserCache())\n    .setLock(LockService.getUserLock())\n    // ...\n```\n\nMake sure to select a lock with the same scope (user, script, or document) as\nthe property store and cache you configured.\n\n## Advanced configuration\n\nSee below for some features of the library you may need to utilize depending on\nthe specifics of the OAuth provider you are connecting to. See the [generated\nreference documentation](http://googleworkspace.github.io/apps-script-oauth2/Service_.html)\nfor a complete list of methods available.\n\n#### Setting the token format\n\nOAuth services can return a token in two ways: as JSON or an URL encoded\nstring. You can set which format the token is in with\n`setTokenFormat(tokenFormat)`. There are two ENUMS to set the mode:\n`TOKEN_FORMAT.FORM_URL_ENCODED` and `TOKEN_FORMAT.JSON`. JSON is set as default\nif no token format is chosen.\n\n#### Setting additional token headers\n\nSome services, such as the FitBit API, require you to set an Authorization\nheader on access token requests. The `setTokenHeaders()` method allows you\nto pass in a JavaScript object of additional header key/value pairs to be used\nin these requests.\n\n```js\n.setTokenHeaders({\n  'Authorization': 'Basic ' + Utilities.base64Encode(CLIENT_ID + ':' + CLIENT_SECRET)\n});\n```\n\nSee the [FitBit sample](samples/FitBit.gs) for the complete code.\n\n#### Setting the token HTTP method\n\nAlmost all services use the `POST` HTTP method when retrieving the access token,\nbut a few services deviate from the spec and use the `PUT` method instead. To\naccomodate those cases you can use the `setTokenMethod()` method to specify the\nHTTP method to use when making the request.\n\n#### Modifying the access token payload\n\nSome OAuth providers, such as the Smartsheet API, require you to\n[add a hash to the access token request payloads](https://smartsheet-platform.github.io/api-docs/#request-an-access-token).\nThe `setTokenPayloadHandler` method allows you to pass in a function to modify\nthe payload of an access token request before the request is sent to the token\nendpoint:\n\n```js\n// Set the handler for modifying the access token request payload:\n.setTokenPayloadHandler(myTokenHandler)\n```\n\nSee the [Smartsheet sample](samples/Smartsheet.gs) for the complete code.\n\n#### Storing token-related data\n\nSome OAuth providers return IDs and other critical information in the callback\nURL along with the authorization code. While it's possible to capture and store\nthese separately, they often have a lifecycle closely tied to that of the token\nand it makes sense to store them together. You can use `Service.getStorage()` to\nretrieve the token storage system for the service and set custom key-value\npairs.\n\nFor example, the Harvest API returns the account ID of the authorized account\nin the callback URL. In the following code the account ID is extracted from the\nrequest parameters and saved saved into storage.\n\n```js\nfunction authCallback(request) {\n  var service = getService_();\n  var authorized = service.handleCallback(request);\n  if (authorized) {\n    // Gets the authorized account ID from the scope string. Assumes the\n    // application is configured to work with single accounts. Has the format\n    // \"harvest:{ACCOUNT_ID}\".\n    var scope = request.parameter['scope'];\n    var accountId = scope.split(':')[1];\n    // Save the account ID in the service's storage.\n    service.getStorage().setValue('Harvest-Account-Id', accountId);\n    return HtmlService.createHtmlOutput('Success!');\n  } else {\n    return HtmlService.createHtmlOutput('Denied.');\n  }\n}\n```\n\nWhen making an authorized request the account ID is retrieved from storage and\npassed via a header.\n\n```js\nif (service.hasAccess()) {\n  // Retrieve the account ID from storage.\n  var accountId = service.getStorage().getValue('Harvest-Account-Id');\n  var url = 'https://api.harvestapp.com/v2/users/me';\n  var response = UrlFetchApp.fetch(url, {\n    headers: {\n      'Authorization': 'Bearer ' + service.getAccessToken(),\n      'User-Agent': 'Apps Script Sample',\n      'Harvest-Account-Id': accountId\n    }\n  });\n  ```\n\nNote that calling `Service.reset()` will remove all custom values from storage,\nin addition to the token.\n\n#### Passing additional parameters to the callback function\n\nThere are occasionally cases where you need to preserve some data through the\nOAuth flow, so that it is available in your callback function. Although you\ncould use the token storage mechanism discussed above for that purpose, writing\nto the PropertiesService is expensive and not neccessary in the case where the\nuser doesn't start or fails to complete the OAuth flow.\n\nAs an alternative you can store small amounts of data in the OAuth2 `state`\ntoken, which is a standard mechanism for this purpose. To do so, pass an\noptional hash of parameter names and values to the `getAuthorizationUrl()`\nmethod:\n\n```js\nvar authorizationUrl = getService_().getAuthorizationUrl({\n  // Pass the additional parameter \"lang\" with the value \"fr\".\n  lang: 'fr'\n});\n```\n\nThese values will be stored along-side Apps Script's internal information in the\nencypted `state` token, which is passed in the authorization URL and passed back\nto the redirect URI. The `state` token is automatically decrypted in the\ncallback function and you can access your parameters using the same\n`request.parameter` field used in web apps:\n\n```js\nfunction authCallback(request) {\n  var lang = request.parameter.lang;\n  // ...\n}\n```\n\n#### Using service accounts\n\nThis library supports the service account authorization flow, also known as the\n[JSON Web Token (JWT) Profile](https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12).\nThis is a two-legged OAuth flow that doesn't require a user to visit a URL and\nauthorize access.\n\nOne common use for service accounts with Google APIs is\n[domain-wide delegation](https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority).\nThis process allows a G Suite domain administrator to grant an\napplication access to all the users within the domain. When the application\nwishes to access the resources of a particular user, it uses the service account\nauthorization flow to obtain an access token. See the sample\n[`GoogleServiceAccount.gs`](samples/GoogleServiceAccount.gs) for more\ninformation.\n\n#### Using alternative grant types\n\nAlthough optimized for the authorization code (3-legged) and service account\n(JWT bearer) flows, this library supports arbitrary flows using the\n`setGrantType()` method. Use `setParam()` or `setTokenPayloadHandler()` to add\nfields to the token request payload, and `setTokenHeaders()` to add any required\nheaders.\n\nThe most common of these is the `client_credentials` grant type, which often\nrequires that the client ID and secret are passed in the Authorization header.\nWhen using this grant type, if you set a client ID and secret using\n`setClientId()` and `setClientSecret()` respectively then an\n`Authorization: Basic ...` header will be added to the token request\nautomatically, since this is what most OAuth2 providers require. If your\nprovider uses a different method of authorization then don't set the client ID\nand secret and add an authorization header manually.\n\nSee the sample [`TwitterAppOnly.gs`](samples/TwitterAppOnly.gs) for a working\nexample.\n\n## Frequently Asked Questions\n\n### How can I connect to multiple OAuth services?\n\nThe service name passed in to the `createService` method forms part of the key\nused when storing and retrieving tokens in the property store. To connect to\nmultiple services merely ensure they have different service names. Often this\nmeans selecting a service name that matches the API the user will authorize:\n\n```js\nfunction run() {\n  var gitHubService = getGitHubService_();\n  var mediumService = getMediumService_();\n  // ...\n}\n\nfunction getGitHubService_() {\n  return OAuth2.createService('GitHub')\n      // GitHub settings ...\n}\n\nfunction getMediumService_() {\n  return OAuth2.createService('Medium')\n      // Medium settings ...\n}\n```\n\nOccasionally you may need to make multiple connections to the same API, for\nexample if your script is trying to copy data from one account to another. In\nthose cases you'll need to devise your own method for creating unique service\nnames:\n\n```js\nfunction run() {\n  var copyFromService = getGitHubService_('from');\n  var copyToService = getGitHubService_('to');\n  // ...\n}\n\nfunction getGitHubService_(label) {\n  return OAuth2.createService('GitHub_' + label)\n      // GitHub settings ...\n}\n```\n\nYou can list all of the service names you've previously stored tokens for using\n`OAuth2.getServiceNames(propertyStore)`.\n\n## Compatibility\n\nThis library was designed to work with any OAuth2 provider, but because of small\ndifferences in how they implement the standard it may be that some APIs\naren't compatible. If you find an API that it doesn't work with, open an issue\nor fix the problem yourself and make a pull request against the source code.\n\nThis library is designed for server-side OAuth flows, and client-side flows with\nimplicit grants (`response_type=token`) are not supported.\n\n## Breaking changes\n\n* Version 20 - Switched from using project keys to script IDs throughout the\nlibrary. When upgrading from an older version, ensure the callback URL\nregistered with the OAuth provider is updated to use the format\n`https://script.google.com/macros/d/{SCRIPT ID}/usercallback`.\n* Version 22 - Renamed `Service.getToken_()` to `Service.getToken()`, since\nthere OAuth providers that return important information in the token response.\n\n## Troubleshooting\n\n### You do not have permission to call fetch\n\nYou are [setting explicit scopes](https://developers.google.com/apps-script/concepts/scopes#setting_explicit_scopes)\nin your manifest file but have forgotten to add the\n`https://www.googleapis.com/auth/script.external_request` scope used by this library\n(and eventually the `UrlFetchApp` request you are making to an API).\n","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogleworkspace%2Fapps-script-oauth2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoogleworkspace%2Fapps-script-oauth2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoogleworkspace%2Fapps-script-oauth2/lists"}