OAuth2 for Apps Script

repository·main·Indexed 23 days ago

https://github.com/googleworkspace/apps-script-oauth2

A library for Google Apps Script (version 1.43.0) that simplifies creating, authorizing, and refreshing OAuth2 tokens. It manages the OAuth2 flow, including redirect URIs and token persistence, and supports Authorization Code, Service Account (JWT Profile), and custom grant types. The library provides built-in support for token storage via PropertiesService, caching with CacheService, and race-condition prevention using LockService.

Tokens
4.3K
Snippets
9
Records
23
Agent score
83%

What's inside apps-script-oauth2

  1. Understand the Sample Add-on architecture

    main
    The Sample Add-on demonstrates how to build a Google Sheets add-on that connects to a third-party service (GitHub) using the apps-script-oauth2 library. It follows best practices for managing the OAuth2 flow within an add-on environment, specifically handling the communication between the authorization callback page and the add-on sidebar.
  2. Use service accounts (JWT Profile)

    main
    The library supports the two-legged service account authorization flow (JSON Web Token Profile). This flow does not require user interaction and is ideal for server-to-server communication or Google domain-wide delegation. Refer to the GoogleServiceAccount.gs sample for implementation details.
  3. How to use OAuth2 for Apps Script

    main

    The library follows a four-step workflow to authorize and use an API:

    1. Create the Service: Initialize an OAuth2Service object with your client credentials, endpoints, and configuration.
    2. Direct the User: Since Apps Script cannot perform automatic redirects, generate an authorization URL using getAuthorizationUrl() and present it to the user as a clickable link (e.g., in a sidebar or dialog).
    3. Handle the Callback: Create a callback function (specified via setCallbackFunction) that receives the request and passes it to service.handleCallback(request) to complete the flow.
    4. Make Requests: Once authorized, retrieve the token via service.getAccessToken() and include it in the Authorization: Bearer <token> header of your UrlFetchApp calls.
    /**
     * 1. Create the OAuth2 service
     */
    function getDriveService_() {
      return OAuth2.createService('drive')
          .setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
          .setTokenUrl('https://accounts.google.com/o/oauth2/token')
          .setClientId('...')
          .setClientSecret('...')
          .setCallbackFunction('authCallback')
          .setPropertyStore(PropertiesService.getUserProperties())
          .setScope('https://www.googleapis.com/auth/drive')
          .setParam('login_hint', Session.getEffectiveUser().getEmail())
          .setParam('access_type', 'offline')
          .setParam('prompt', 'consent');
    }
    
    /**
     * 2. Direct the user to the authorization URL
     */
    function showSidebar() {
      var driveService = getDriveService_();
      if (!driveService.hasAccess()) {
        var authorizationUrl = driveService.getAuthorizationUrl();
        var template = HtmlService.createTemplate(
            '<a href="<?= authorizationUrl ?>" target="_blank">Authorize</a>. Reopen the sidebar when the authorization is complete.');
        template.authorizationUrl = authorizationUrl;
        var page = template.evaluate();
        DocumentApp.getUi().showSidebar(page);
      }
    }
    
    /**
     * 3. Handle the callback
     */
    function authCallback(request) {
      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');
      }
    }
    
    /**
     * 4. Get the access token
     */
    function makeRequest() {
      var driveService = getDriveService_();
      var response = UrlFetchApp.fetch('https://www.googleapis.com/drive/v2/files?maxResults=10', {
        headers: {
          Authorization: 'Bearer ' + driveService.getAccessToken()
        }
      });
    }
  4. Connecting to a Google API without the library

    main

    If you are connecting to a standard Google API, you may not need this library. You can use Apps Script's built-in or advanced services.

    Alternatively, you can use ScriptApp.getOAuthToken() to get the token for the script's current authorization scope. To do this:

    1. Add the required scopes to your script's manifest.
    2. Use ScriptApp.getOAuthToken() to retrieve the token.
    3. Pass the token in the Authorization header of a UrlFetchApp.fetch() call.
  5. Communicate between tabs using Intercom.js

    main
    In an add-on environment, the OAuth2 callback occurs on a separate page/tab from the add-on sidebar. This sample uses the intercom.js library to facilitate communication between these two contexts. Specifically, once the callback page completes the authorization flow, it sends a message to the sidebar so the sidebar can refresh its content and reflect the new authorization state.
  6. Connect to multiple OAuth services

    main

    To manage multiple OAuth services or multiple connections to the same API, ensure each service is initialized with a unique name in OAuth2.createService(name). The name is used as part of the key in the underlying property store.

    To list all previously stored service names, use OAuth2.getServiceNames(propertyStore).

  7. Install OAuth2 for Apps Script

    main

    You can add this library to your Google Apps Script project using the Script ID.

    1. In the Apps Script editor, go to Resources > Libraries...
    2. Enter the Script ID: 1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF
    3. Click Select.
    4. Choose a version (the latest is usually recommended).
    5. Click Save.

    Alternatively, you can copy the files from the /dist directory directly into your project.

    Important: If you are setting explicit scopes in your appsscript.json manifest, you must include the following scope: https://www.googleapis.com/auth/script.external_request

  8. Setup the Sample Web App using clasp

    main

    To deploy a local copy of the Sample Web App to Google Apps Script, use the clasp command line tool. Ensure you have installed clasp and executed clasp login before proceeding.

    1. Initialize the script project: Run the following commands in the project directory:
      clasp create "Sample Web App"
      clasp push
      clasp open
    2. Retrieve the Redirect URI: In the Apps Script editor that opens, run the logRedirectUri function. Open the logs (View > Logs) and copy the displayed URL.
    3. Configure GitHub OAuth: Go to the GitHub Developer console, create a new OAuth App, and paste the copied URL into the Authorization callback URL field.
    4. Configure Credentials: In Code.gs, uncomment the CLIENT_ID and CLIENT_SECRET variables and paste the values from your GitHub OAuth App.
    5. Deploy the application:
      clasp deploy
    6. Access the Web App: Construct the execution URL using your deployment ID: https://script.google.com/macros/s/<DEPLOYMENT_ID>/exec.
    clasp create "Sample Web App"
    clasp push
    clasp open
    # ... after editing Code.gs ...
    clasp deploy
  9. Best practices: Token storage, Caching, and Locking

    main

    To build a production-ready integration, follow these three patterns:

    Token Storage

    Always use .setPropertyStore() to persist tokens so users don't have to re-authorize every time. Use PropertiesService.getUserProperties() for user-specific access, or PropertiesService.getScriptProperties() for shared access.

    Caching

    To avoid hitting PropertiesService quotas, enable caching by adding .setCache(CacheService.getUserCache()). Ensure the cache scope matches your property store scope.

    Locking

    To prevent race conditions where multiple executions attempt to refresh an expired token simultaneously, use .setLock(LockService.getUserLock()). Ensure the lock scope matches your property store and cache scopes.

    return OAuth2.createService('Foo')
        .setPropertyStore(PropertiesService.getUserProperties())
        .setCache(CacheService.getUserCache())
        .setLock(LockService.getUserLock())
  10. Connect to a Google API without the OAuth2 library

    main

    If the Google API you are targeting is already supported by Google Apps Script scopes, you do not need the apps-script-oauth2 library. You can use the native ScriptApp.getOAuthToken() method to retrieve the access token and pass it to the API via UrlFetchApp.fetch().

    Steps to implement:

    1. Add Scopes to Manifest: Edit your script's manifest file (appsscript.json) to include the specific OAuth2 scopes required by the target API.
    2. Retrieve Token: Use ScriptApp.getOAuthToken() to get the current authorization token.
    3. Make API Call: Include the token in the Authorization header of your request using the Bearer scheme.
  11. Pass state parameters through the OAuth flow

    main

    To preserve small amounts of data through the redirect (e.g., a user's language preference) without writing to the expensive PropertiesService, use the OAuth2 state parameter.

    1. Pass an object of parameters to getAuthorizationUrl({ key: value }).
    2. These values are encrypted into the state token and passed to the provider.
    3. The provider returns the state to your redirect URI.
    4. Access the values in your callback function via request.parameter.key.