How can I automatically handle Google API OAuth2 token renewal without manual browser interaction?

I’m working with Google Drive API and running into an issue where my OAuth2 tokens expire after about a week. Every time this happens, I have to manually go through the browser authentication flow again to get new credentials. This is really annoying for an automated script that should run without any user input. Is there some way to handle this token refresh automatically so I don’t have to babysit it every few days?

import os
import json
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials

# Required permissions for Drive access
PERMISSIONS = ['https://www.googleapis.com/auth/drive.metadata.readonly']

def authenticate_drive():
    """Handles authentication for Google Drive API access.
    Returns a service object for making API calls.
    """
    user_creds = None
    
    # Check if we have stored credentials
    if os.path.exists('stored_creds.json'):
        user_creds = Credentials.from_authorized_user_file('stored_creds.json', PERMISSIONS)
    
    # Handle expired or missing credentials
    if not user_creds or not user_creds.valid:
        if user_creds and user_creds.expired and user_creds.refresh_token:
            user_creds.refresh(Request())
        else:
            auth_flow = InstalledAppFlow.from_client_secrets_file(
                'client_secret.json', PERMISSIONS)
            user_creds = auth_flow.run_local_server(port=8080)
        
        # Store credentials for future use
        with open('stored_creds.json', 'w') as cred_file:
            cred_file.write(user_creds.to_json())
    
    return build('drive', 'v3', credentials=user_creds)

def list_recent_files():
    """Fetches and displays recent files from Drive."""
    drive_service = authenticate_drive()
    
    print('Fetching recent files from Drive...')
    file_list = drive_service.files().list(
        pageSize=5,
        fields="nextPageToken, files(id, name, modifiedTime)"
    ).execute()
    
    files = file_list.get('files', [])
    
    if not files:
        print('No files found in Drive.')
        return
    
    for file_item in files:
        print(f"File: {file_item['name']} (ID: {file_item['id']})")

if __name__ == '__main__':
    list_recent_files()

Your code already has the automatic refresh built in - that user_creds.refresh(Request()) line handles token renewal without needing the browser. But there’s a common gotcha that’s probably causing your issue.

When you first authenticate, make sure you set up your Google Cloud Console as “Desktop application” not “Web application”. Web app type makes refresh tokens super inconsistent. You definitely want Desktop for scripts like this.

Also check your client_secret.json file. Sometimes refresh fails silently if there’s a permission mismatch between your stored credentials and OAuth client settings. I had this same problem - script worked fine for days then suddenly needed manual auth again. Turns out I’d accidentally changed scope permissions in the console, which killed the stored refresh token.

The refresh should work automatically, but you’re missing something crucial. If your app’s still in testing mode in Google Cloud Console, that’s your problem. Test apps get their refresh tokens killed after 7 days or when you hit user limits. Production apps keep their tokens.

Hit up your OAuth consent screen and publish the app for production. Yeah, you’ll probably need to fill out some verification stuff, but it’s worth it for scripts that run on their own. I switched mine months ago and haven’t had to manually auth once since.

Throw a try-catch around your refresh call too - log what error you’re actually getting. Sometimes the refresh token dies from sitting idle too long, not just the access token. When that happens, you’re stuck doing the manual auth dance again.

check if google nuked your refresh token - they do that when they think your app is suspicious or hasn’t been used for a while. happens a lot with server scripts or on sketchy networks. add error handling around that refresh() call and see what’s failing. also, sync your system clock - token validation can break with time drift.