Authorization Scopes in Google Apps Script: Convenience vs. Security
Updated: February 2024
Authorization scopes are a crucial element of any Google Apps Script, as they determine what your script is allowed to do in your name. More importantly, they set the boundaries for the script, providing security (more on that here). That is why it is vital to understand how to set the required scopes properly and which scopes to use.
In this post, we will cover:
1. How to set scopes in Apps Script
a. Setting Scopes Automatically (Implicitly)
b. Setting Scopes Manually (Explicitly)
c. JSDoc annotation for implied scopes
2. What Scopes to Use
3. Takeaways
1. How to set scopes in Apps Script
There are two ways to identify the permission scopes required for a script: automatically (implicitly) and manually (explicitly). There is also a way to limit some automatically set scopes with a JSDoc annotation.
A. Setting Scopes Automatically
In automatic mode, when you run the script, Google checks if it uses functions requiring specific permissions, such as UrlFetchApp
, SpreadsheetApp
(often they end with “App”). If it does, Google will ask you for the permissions required to work with those functions. This method is very convenient for the developer, yet it does compromise security.
In the example above, Google will ask you to grant the script two permissions: full access to all your spreadsheets (SpreadsheetApp
) and access to external services (UrlFetchApp
):
Access to external services is acceptable – there is no other way to achieve the same result, and it is difficult to misuse on its own. However, full access to all spreadsheets is too broad. In most cases, you want to write data to the sheet to which the script is attached. Thus, you only need access there, which would be much safer. There is a special permission for that, which you must manually set.
C. Setting Scopes Manually
The best security practice is to request as narrow permissions as possible, which would still be enough to do the job. In this case, if anything goes wrong (whether it’s a bug in the code or a security breach), the scope of the problem will be limited. To do this with Google Apps Script, you need to identify the required permissions explicitly.
Every Apps Script project has an appscript.json
manifest file, which is hidden by default (I guess to promote bad security practices). To make it accessible, you need to open the project’s settings and enable Show "appsscript.json" manifest file in editor
:
After that, the manifest file will appear in the list of files and always be at the top. By default, it will contain only basic details of the project:
You need to add the oauthScopes
property with the values to set the proper authorization scopes. Following the example above, to require permission to access external services and the spreadsheet, the script is attached to, the file should look as follows:
{
"timeZone": "America/Toronto",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets.currentonly",
"https://www.googleapis.com/auth/script.external_request"
]
}
If you set permissions explicitly, you must provide an exhaustive list of permissions the script needs. Otherwise, performing an action you do not have permission for will cause an error.
C. JSDoc annotation for implied scopes
There is a middle ground between setting the scopes automatically or manually from the security perspective. If you need to access only the document the script is bound to (Google Sheets, Docs, Slides, or Forms), you can force the authorization dialog to ask only for access to files in which the script is used rather than to all the documents of this type.
To do so, you need to add this JSDoc annotation in the top level of your code (documentation):
/** @OnlyCurrentDoc */
It is equivalent to the setting of https://www.googleapis.com/auth/*.currentonly
scopes but with the added benefit that other scopes will be added automatically.
In case you use a library that sets @OnlyCurrentDoc
flag, there is an annotation that negates it:
/** @NotOnlyCurrentDoc */
It is equivalent to not using any annotations or setting a broad https://www.googleapis.com/auth/*
scope.
2. What Scopes to Use
There are three types of authorization scopes: regular, sensitive (containing user data like email, name, etc.), and restricted (examples). Depending on your use case, you might need to go through a special authorization process to use sensitive or restricted scopes. If you have fewer than 100 users of a given script, you usually do not have to do this, but you will be subject to the unverified app screen.
You can find a list of the scopes for many Google services here.
Let’s look at the most commonly used scopes:
- Access only to the document the script is attached to – a must-have and the easiest way to make your sheets safer. These scopes are so secure that on their own, they do not require a security warning (and with this trick, you can work with the data from other sheets without more extensive permissions):
- Sheets:
https://www.googleapis.com/auth/spreadsheets.currentonly
- Docs:
https://www.googleapis.com/auth/documents.currentonly
- Slides:
https://www.googleapis.com/auth/presentations.currentonly
- Forms:
https://www.googleapis.com/auth/forms.currentonly
- Sheets:
- Read-only scopes. These are the next best choices if you need to read data from documents other than the one where the script is installed.
- Sheets:
https://www.googleapis.com/auth/spreadsheets.readonly
- Docs:
https://www.googleapis.com/auth/documents.readonly
- Slides:
https://www.googleapis.com/auth/presentations.readonly
- Drive:
https://www.googleapis.com/auth/drive.readonly
- Sheets:
- Complete access to all documents of a given type. These are some of the most dangerous scopes possible and are highly discouraged unless absolutely necessary. Even then, use them with security precautions.
- Sheets:
https://www.googleapis.com/auth/spreadsheets
- Docs:
https://www.googleapis.com/auth/documents
- Slides:
https://www.googleapis.com/auth/presentations
- Forms:
https://www.googleapis.com/auth/forms
- Drive:
https://www.googleapis.com/auth/drive
- Sheets:
- Making requests to external services via
UrlFetchApp
:https://www.googleapis.com/auth/script.external_request
- Getting the email of the user running the script via
Session.getActiveUser().getEmail()
:https://www.googleapis.com/auth/userinfo.email
- Accessing the user interface (UI):
https://www.googleapis.com/auth/script.container.ui
. This scope is mainly for complex UI interactions when creating custom interfaces. It does not include simple alerts and custom menus – you can do those without this permission. - Creating and deleting triggers, working with user’s permissions via ScriptApp:
https://www.googleapis.com/auth/script.scriptapp
. Use this scope with caution, as it allows the creation of triggers, and triggers can run the script in your name without your knowledge. You can manage existing triggers here.
3. Takeaways
We have reviewed several ways to set authorization scopes. These are the main takeaways:
- Always set scopes explicitly without relying on auto-detection, which is often too permissive. It takes a minute to set up the scopes manually in the very beginning, but it makes your life easier and more secure later.
- Use as narrow scopes as possible.
- If you rely on implied scopes, use
@OnlyCurrentDoc
JSDoc annotation to limit the implied scope ofSpreadsheetApp
,DocumentApp
,SlidesApp
, andFormApp
to only the current document. - Bonus point: learn how to audit scopes you have already granted.