I am trying to implement a Google Cloud Function in which I can fetch user's google calendars (read only). I have a next.js app, in which I request their consent with the scopes, and save the access token + refresh token in Firebase. I can then, using Python, pull their calendar events using those tokens & client secrets.
However, after a couple of hours, when I'm guessing the access token expires, it is not able to generate a new one with the refresh token (even though it should be able to?). I keep getting "Invalid Grant" & "Bad Request". Since this will be running as a cloud function, I cannot have users granting access every time.
These are the full logs:
Expiry: None
Valid: True
file_cache is only supported with oauth2client<4.0.0
Getting upcoming events from 2025-03-31T07:00:00Z to 2025-04-01T07:00:00Z (adjusted for timezone: America/Los_Angeles)...
Refreshing credentials due to a 401 response. Attempt 1/2.
^^^^^^^^^
File "/home/codespace/.python/current/lib/python3.12/site-packages/googleapiclient/_helpers.py", line 130, in positional_wrapper
return wrapped(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/codespace/.python/current/lib/python3.12/site-packages/googleapiclient/http.py", line 923, in execute
resp, content = _retry_request(
^^^^^^^^^^^^^^^
File "/home/codespace/.python/current/lib/python3.12/site-packages/googleapiclient/http.py", line 191, in _retry_request
resp, content = http.request(uri, method, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/codespace/.python/current/lib/python3.12/site-packages/google_auth_httplib2.py", line 245, in request
self.credentials.refresh(self._request)
File "/home/codespace/.python/current/lib/python3.12/site-packages/google/oauth2/credentials.py", line 409, in refresh
) = reauth.refresh_grant(
^^^^^^^^^^^^^^^^^^^^^
File "/home/codespace/.python/current/lib/python3.12/site-packages/google/oauth2/reauth.py", line 366, in refresh_grant
_client._handle_error_response(response_data, retryable_error)
File "/home/codespace/.python/current/lib/python3.12/site-packages/google/oauth2/_client.py", line 69, in _handle_error_response
raise exceptions.RefreshError(
google.auth.exceptions.RefreshError: ('invalid_grant: Bad Request', {'error': 'invalid_grant', 'error_description': 'Bad Request'})
Here is the code I am using to fetch calendar events:
creds = Credentials(
token=access_token,
refresh_token=refresh_token,
token_uri=token_uri,
client_id=google_client_id,
client_secret=google_client_secret
)
if self.creds.expired and self.creds.refresh_token:
logger.info("getting new credentials")
self.creds.refresh(GoogleRequest())
print(self.creds.expiry)
print(self.creds.valid)
calendar_service = build("calendar", "v3", credentials=self.creds)
timezone="America/Los_Angeles"
local_tz = pytz.timezone(timezone)
# Get today's midnight in the local timezone
now_local = datetime.datetime.now(local_tz).replace(hour=0, minute=0, second=0, microsecond=0)
# Convert to UTC
now_utc = now_local.astimezone(pytz.UTC)
now_format = now_utc.strftime('%Y-%m-%dT%H:%M:%SZ')
future_utc = (now_local + datetime.timedelta(days=1)).astimezone(pytz.UTC)
future_format = future_utc.strftime('%Y-%m-%dT%H:%M:%SZ')
logger.info(f'Getting upcoming events from {now_format} to {future_format} (adjusted for timezone: {timezone})...')
events_result = calendar_service.events().list(
calendarId='primary',
timeMin=now_format,
timeMax=future_format,
maxResults=10,
singleEvents=True,
orderBy='startTime'
).execute()
events = events_result.get("items", [])
And here is the code to grant permissions via my next.js app:
const handleGrantCalendarAccess = async () => {
const provider = new GoogleAuthProvider();
provider.addScope('.readonly');
provider.setCustomParameters({
access_type: 'offline',
include_granted_scopes:'true',
prompt: 'consent'
});
try {
setLoading(true);
const user = auth.currentUser;
let result;
if (user) {
// Reauthenticate the user if already signed in
result = await reauthenticateWithPopup(user, provider);
} else {
// Sign in the user if not already signed in
result = await signInWithPopup(auth, provider);
}
const credential = GoogleAuthProvider.credentialFromResult(result);
const userEmail = user?.email;
const userName = user?.displayName;
const accessToken = credential ? credential.accessToken : null;
const refreshToken = (result.user as any).stsTokenManager.refreshToken;
const idToken = await user?.getIdToken();
//upload to Firebase
I have been stuck on this for quite a while, so help is greatly appreciated!