However, there comes a point where you need to add your HTML to your Apps Script code and test it.
The Problem
Your Apps Script HTML is buried inside nested iframes and processed by Google’s front-end parser, which often hides error locations. This makes debugging in the Apps Script Editor a nightmare. Let’s ease that pain!
Cross-Site Request Forgery (CSRF) is a web security vulnerability that allows a nefarious entity to take actions on a website that are unintended by the user.
Typically, this is done by tricking the user into using another website resembling the intended site and then submitting a form or clicking a button. The dodgy site then sends a request with its own payload of nasty stuff on the user’s behalf.
Google Workspace and Google Apps Script’s HMTL Service API protect the user with OAuth2 authorization standards and embed dialogues and sidebars in restrictive iframes to sandbox these environments. However, there may be a requirement to further protect your users from unintentionally sending form data using the google.script.run Client-side API that sends data back to Google Apps Script, with a server-side generated anti-CSRF token.
Indeed, when completing a CASA Tier 2 security assessment for a Google Workspace Editor add-on this was a requirement for me to not only pass the assessment but to also meet GDPR requirements.
An anti-CSRF token will allow us to create a unique ID for the current sidebar or dialogue session. We can store this token server-side in the User Properties of the Properties Service and then add the token to a hidden input element in our form client-side on our dialogues and sidebars. We can then send this token along with our form payload back to Apps Script where we can first validate the token before continuing.
The following script and tutorial provide a brief example of how to do this.
Table of Contents
The Example Dialogue
We will first open a Google Sheet (but you can open a Google Doc or Slide and do the same thing) and create a bound Google Apps Script.
Our simple tasks will be to:
Create a menu to access a modal dialogue in the Google Sheet.
Create the modal dialogue with a form containing a radio selection and a submit button.
On submission, the form is validated with the anti-CSRF token before
Create a Menu Item and Modal Dialogue in Google Sheets
onOpen runDialogue functions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//
/**
* Creates a menu item to access the dialogue.
*/
functiononOpen(){
constui=SpreadsheetApp.getUi()
ui.createMenu("Anti-CSRF token")
.addItem("Run dialogue","runDialogue")
.addToUi()
};
/**
* Creates the dialogue as a template so we can directly add the getCsrfToken to the sheet.
Next, we build the dialogue. Here, we invoke HtmlService to create a template from a file, referencing the ‘Index.html’ file as our source file. We will take this approach to use scriptlets in our HTML to define our CSRF token.
Finally, we will call the UI method again to display the HTML in a modal dialogue.
Create the Front-end ‘Index’ HTML Page
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
This is where we are inserting a custom token into the current dialogue session. we have named it “_csrf”. You can also see that we have used Google Apps Script’s HTML printing scriptlets to display the anti-CSRF token returned from the getCsrfToken() function.
This function is called from the Google Apps Script side and generated as a part of the HTML template-building process. More on this function later.
Line 20-21: The submit button is added outside the form so as not to generate an error when clicked.
A response line is also added to display if the token is correct or not.
Submitting the Form
submit()
Lines 43-64
When the ‘Submit’ button is clicked, the submit() function is invoked.
Here we retrieve the form element and use the new FormData() constructor to gather all the form responses including our hidden CSRF token.
Next, we iterate over the form data entries sorting the keys and values in the payload object.
Finally, we use the google.script.run API to send a stringified version of the payload back serverside.
We also invoke the withSuccessHandler method to return a message once validation of the token has been carried out.
onSuccess(e)
Once the CSRF token has been validated against the stored token value serverside (Apps Script-side) a stringified object will be returned back to the HTML file containing a hasError boolean property and a text string property.
If there was no match between the sent CSRF token and the stored token, we change our message colour to red.
Then we report the message in the ‘response’ span under the submit button.
Generate the CSRF Token in Google Apps Script
getCsrfToken()
1
2
3
4
5
6
7
8
9
10
11
12
13
//
/**
* Generates the anti CSRF token for for the current user and stores it for the current session.
* Token is stores in the users properties service.
* A new one is created each time the dialogue is built.
Next, the token is stored in the user’s property service. This way the token is only accessible for that use for this script. We will generate and store this token each time the user opens dialogue to make it even more challenging for an attacker to breach.
Finally, the token is returned so it can be stored in the HTML file.
message.text="Your choice has been adde successfully!"
// Continue with form content validation.
// ...
returnJSON.stringify(message)
}
Lines 6-10: When the user submits the form, it is sent to the formInputs() function. Here we first parse the stringified form data back to an object and then set up a message object that we will return when our script is complete.
On line 14, we grab our stored CSRF token value so that we can compare it against the one coming in.
Then on lines 17- 27, we check if the form CSRF value matches the store CSRF token value. If it doesn’t, then we return the message variable with our error text.
If the tokens match, then you can carry on a validate your other form inputs before continuing with your data.
That’s it. That’s the whole script. Google Apps Script makes it really easy to implement this security token.
Is It Really Necessary to Add CSRF Tokens to your Google Workspace Dialogues and Sidebars?
Well…probably not. Particularly if you do not intend to publish your Add-on to the public. However, if you do have some restricted scopes that need to be authorised by your users then part of the CASA Tier 2 Assessment then it probably isn’t a huge deal.
The likelihood of someone finding or caring about your dialogues and then trying to exploit them along with Google OAuth and Iframe restrictions would make it pretty hard for a baddie to do any damage to your Google Workspace environment. But, you never know.
Need help with Google Workspace development?
Go something to solve bigger than Chat GPT?
I can help you with all of your Google Workspace development needs, from custom app development to integrations and security. I have a proven track record of success in helping businesses of all sizes get the most out of Google Workspace.
If you’ve landed on this page you’re probably wondering why your hyperlinks are not working in your Google Workspace dialogue (dialog for my U.S. friends) box or sidebar.
This affects all locations where you can build a sidebar or dialogue with Google Apps Scripts, Sheets, Docs, Slides and Forms.
You might even hit F12 in your browser to inspect the code and found this dreaded error:
Unsafe attempt to initiate navigation for frame with origin ‘https://docs.google.com’ from frame with URL ‘https://n-yyi3lctp…<<fileID>>…-0lu-script.googleusercontent.com/userCodeAppPanel’. The frame attempting navigation of the top-level window is sandboxed, but the flag of ‘allow-top-navigation’ or ‘allow-top-navigation-by-user-activation’ is not set.
Why your Links aren’t working in your Google Workspace Dialogs and Sidebars
So what’s going on?
Dialogues and sidebars in Google Workspace are set in iframes. Essentially, this is a nested webpage on your main page. Take a look at the examples, below. I’m in Chrome here and I have selected the Developer Tools Element Selector (Ctrl + Shift + C for PC) and clicked on the Sidebar and Dialogue box respectively.
For the Sidebar.
And for the dialogue box.
When you create a simple HTML hyperlink in your anchor tag like this:
<a href="https://yagisanatode.com">Website</a>
You are asking the iframe to open a URL in its parent window, your Sheet, Doc, Slide or Form, and browsers generally don’t like to let you do this.
The Solution
The solution is really easy. Simply add target="_blank" to your anchor element:
The target ‘_blank’ attribute will generally open the link in a new tab for most browsers. Note that a user may change their settings to open in a new window instead of a new tab.
Target ‘_blank’ implicitly adds the rel="noopener" behaviour on most browsers to prevent the destination link from tampering with the original source.
Easter eggs, a bonus Google Sheet formula for linking images in a cell and a fun way to call object methods from a Ui method are all in the example sheet.
Once you made a copy of the Sheet, click on the ‘More’ custom menu item and select from the Sidebar or Dialogue box implementation to see it in action. You will have to Authorise the scopes the first time though.
To look at the code, go to Extensions > Apps Script.
I can help you with all of your Google Workspace development needs, from custom app development to integrations and security. I have a proven track record of success in helping businesses of all sizes get the most out of Google Workspace.
Google Apps Script: Dev Tools, Color Picker, Side Bar, Custom Prompt, HtmlService, onOpen, Sidebar, Dialog Box
I wanted to update one of my free Google Add-on apps that works with colour. What I had is just the standard HTML color input element where the user selects from the palette and that hexadecimal colour code is returned to Google Apps Script to be used in the App. The problem is that it is really hard to get a good colour match between the palette and Google’s own colour range that is accessible from the fill or text colour buttons.
Take a look at the comparison between the HTML color input element and the Google Sheet background colour palette in the image below.
That’s not a user-friendly tool to match colours with the standard Google palette.
So in the back of my mind, I had always wanted to create a tool for a sidebar or dialogue box that would allow the user to easily access the standard colours or use the custom palette provided by the HTML color input.
After finding a bit of time in my recent summer break I came up with this.
I was working on a Google Apps Script project lately in Google Sheets that set up parameters in a sidebar and then ran the process once the user clicked the “Submit” button. The problem was that the process was taking a while and that “Submit” Button was ripe to be clicked multiple times by the impatient user before the server-side code could even finish its operation.
To fix this I needed to disable the submit button once it had been clicked and then enable it again once the server-side process was complete. Here, I need to:
Disable the button and get the data from the client-side Javascript inside my sidebar’s HTML file.
Do something awesome with it server-side.
Upon the completion of the server-side awesome, call back to the HTML file and enable the button again.
The Example
I have a very simple Google Sheets Side Bar with a “Submit” and “Cancel” button. When the “Submit” button is clicked it calls the function submittington (can he get any more creative? No. No he can’t).
This function then disables the “Submit” button sends a variable to the client-side code.gs and displays it stylishly in the sheet multiple times for the users viewing pleasure. After the code.gs function is executed, it calls back to the client-side submittington function and enables the button.