I’ve built a web application that uses the Google Drive API across multiple pages. On each page, I need to make authenticated API calls to Google Drive. When I log in on a page, I obtain an access token via Google login and my API requests work correctly. However, refreshing or navigating to a different page forces a re-login every time.
To solve this, I implemented an automatic Google login on the main page and store the access token in Redis. The idea is to reuse the same token across pages so that users don’t have to log in again. The problem is: when I use the token stored in Redis for my API requests, I receive a 401 error.
Even though the token I get from logging in on each page exactly matches the one stored in Redis, API calls fail with the following error:
error:
code: 401
errors: [{…}]
message: "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity-sign-in/web/devconsole-project."
status: "UNAUTHENTICATED"
Token Storage (on page load):
$(document).ready(async function() {
if ("{{.DriveCookie}}" === "") {
iziToast.info({
title: 'Warning!',
message: 'Drive connection not established. Connecting...',
position: 'topCenter'
});
const accessToken = await getAccessToken();
// Post the access token to store it in Redis
const formData = new FormData();
formData.append("driveCookie", accessToken);
formData.append("slug", "[USER_SLUG]"); // Censored
fetch("/user/driveCookie", {
method: "POST",
body: formData
})
.then(response => response.json())
.then(data => {
iziToast.success({
title: 'Success!',
message: 'Drive connection established.',
position: 'topCenter'
});
})
.catch(error => {
console.error("Error:", error);
alert("An error occurred!");
});
}
});
File Upload Function (API Request):
async function loadFile() {
const form = document.getElementById('uploadDocumentForm');
const formData = new FormData(form);
const fileInput = document.getElementById('documentFile');
if (!fileInput.files.length) {
iziToast.error({
title: 'Error',
message: 'Please select a file.',
position: 'topCenter'
});
return;
}
const file = fileInput.files[0];
let fileType = file.name.split('.').pop();
if (fileType.includes('-')) {
fileType = fileType.split('-')[0];
}
if(fileType === 'docx'){
const accessToken = await getAccessToken();
const accessTokenString = accessToken.toString();
const metadata = {
name: file.name,
mimeType: file.type,
parents: ['[PARENT_FOLDER_ID]'] // Censored
};
const formData2 = new FormData();
formData2.append("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" }));
formData2.append("file", file);
try {
const response = await fetch("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart", {
method: "POST",
headers: new Headers({ "Authorization": "Bearer " + accessTokenString }),
body: formData2
});
const result = await response.json();
if (response.ok) {
formData.append('driveID', result.id);
} else {
console.error('Upload error:', result);
iziToast.error({
title: 'Error',
message: 'Error uploading file.',
position: 'topCenter'
});
}
} catch (error) {
console.error('Error:', error);
iziToast.error({
title: 'Error',
message: 'Error uploading file.',
position: 'topCenter'
});
return;
}
}
}
async function getAccessToken() {
return new Promise((resolve, reject) => {
// Simplified for demonstration; returns the cached token
resolve(cachedAccessToken);
});
}
To check whether the access token is stored correctly in Redis, I printed both the previously logged-in access token stored in Redis and the newly obtained access token from the latest login to the console :
Previously logged-in access token stored in Redis: ya29.a0AXeO80S-***************ME8yaCgYKAfcSARASFQHGX2MiEqNw2FBDguC2VN4xZdFq0Q0175 // Censored
Access token obtained from a new login: ya29.a0AXeO80S-***************ME8yaCgYKAfcSARASFQHGX2MiEqNw2FBDguC2VN4xZdFq0Q0175 // Censored
Summary:
When I perform a Google login on each page, the access token is valid and my API calls work.
I store the access token in Redis (using the above token storage code) to avoid repeated logins.
However, when I use the Redis-stored token for a Drive API call (as shown in loadFile()), I receive a 401 error, even though the token from Redis is identical to the one obtained during login.
Any insights into why the API request fails with the stored token—even when both tokens match—and how to resolve this issue would be greatly appreciated.
I stored the access token in Redis to avoid re-login across pages. I expected the stored token to work for Drive API requests, but instead, I received a 401 UNAUTHENTICATED error, even though the token matched the one from login.