
Handling State and Authentication in Google Apps Script Apps
Hey everyone, welcome back to MageSheet.
To wrap up our first pillar focus on Apps Script Web Apps, we need to talk about the elephant in the room: State Management and Authentication.
When you build a Secure Client Portal or an Inventory web app, your users expect a modern Single Page Application (SPA) experience. They log in once, the screen transitions to a dashboard, and they click around without the browser hard-loading a new URL.
But Apps Script's HtmlService is stateless. Every time the frontend calls google.script.run.getSomeData(), the backend spins up completely fresh. It has no memory of the user who just logged in two seconds ago.
So how do we keep a user logged in, maintain their state securely, and prevent unauthorized data access?
The Fallacy of Client-Side Authentication
A common, dangerous mistake developers make in Apps Script is trusting the frontend.
The Bad Architecture:
- User enters PIN on frontend.
- Frontend calls
google.script.run.verifyPin(pin). - Backend returns
true. - Frontend saves a JavaScript variable:
let isLoggedIn = true; let currentUserId = 'C-101'; - Frontend calls
google.script.run.fetchInvoices(currentUserId).
Why is this terrible? Because anyone can open Google Chrome Developer Tools, type google.script.run.fetchInvoices('C-102') into the console, and steal another client's data! The backend essentially says, "I trust whoever the frontend says it is computing for."
The Proper Solution: Session Tokens
To build a secure SPA in Apps Script, we must generate a temporary Session Token on the backend during login, hand it to the frontend, and require the frontend to pass that token back on every single subsequent request.
We can store these active sessions in two ways:
- A hidden "Active Sessions" sheet.
- The
CacheService(Much faster and cleaner!).
Let's look at how to implement secure, server-side verified sessions using Google Apps Script's built-in CacheService.
1. The Secure Login Endpoint
When the user enters their credentials, the backend validates them, generates a random unique token, stores that token in the Cache (linked to the User ID), and returns the token to the frontend.
// Code.gs
function loginSecurely(email, pin) {
// Assume isValidUser() checks the Google Sheet and returns the internal user ID
const userId = isValidUser(email, pin);
if (!userId) {
return { success: false, error: "Invalid login" };
}
// 1. Generate a secure, random Session Token
const sessionToken = Utilities.getUuid();
// 2. Store the token in the Apps Script Cache for 2 hours (7200 seconds)
const cache = CacheService.getScriptCache();
cache.put(sessionToken, userId, 7200);
// 3. Return the token to the frontend
return { success: true, token: sessionToken };
}
2. Guarding the Data Endpoints
Now, let's rewrite our fetchInvoices function. We no longer ask the frontend who they are. We only ask for their token.
// Code.gs
function fetchInvoices(sessionToken) {
// 1. Verify the token exists in the cache
const cache = CacheService.getScriptCache();
const userId = cache.get(sessionToken);
// 2. If the token is invalid or expired, reject the request completely.
if (!userId) {
return { success: false, error: "Session expired or invalid. Please log in again." };
}
// 3. We know EXACTLY who the user is now, verified securely by the server.
// We fetch only their data.
const allInvoices = getInvoicesFromSheet(); // helper function
const userInvoices = allInvoices.filter(inv => inv.clientId === userId);
return { success: true, data: userInvoices };
}
3. Managing State on the Frontend
Now, your Index.html frontend just needs to hold onto that token and pass it along with every API call it makes via google.script.run.
<script>
// The global state of our SPA
let APP_STATE = {
sessionToken: null
};
// Called when the user clicks Login
function handleLogin() {
const email = document.getElementById('email').value;
const pin = document.getElementById('pin').value;
google.script.run
.withSuccessHandler(function(res) {
if(res.success) {
// Store the token in state
APP_STATE.sessionToken = res.token;
loadDashboard();
} else {
alert(res.error);
}
})
.loginSecurely(email, pin);
}
// Called to load the actual data
function loadDashboard() {
// We pass the token, NOT the user ID
google.script.run
.withSuccessHandler(function(res) {
if(res.success) {
renderInvoices(res.data);
} else {
// Token expired, send them back to login screen
showLoginScreen();
}
})
.fetchInvoices(APP_STATE.sessionToken);
}
</script>
Summary
By leveraging CacheService.getScriptCache() and UUIDs, you can build a highly secure, stateful Single Page Application architecture directly on top of Google Apps Script.
There are no databases to spin up, no Redis clusters to manage, and no monthly server fees. Google handles the cache eviction automatically after the time limit you set.
If your enterprise relies heavily on Google Workspace, don't let data security hold you back from building custom tools. With the right architecture, Apps Script is just as secure and robust as traditional web stacks.
Need help building complex B2B applications, or integrating secure workflows between your Magento storefront and your Google Workspace? Contact MageSheet today to discuss custom consulting solutions.


