How to Connect ChatGPT to Google Drive (with OAuth)
🤖 AI Agent? You can point your AI agent at this article and it can follow the steps to connect automatically. Everything it needs is here: the API endpoints, request/response formats, scopes, and working code samples. The TapAuth API is two calls —
POST https://tapauth.ai/api/v1/grantsto create a grant, thenGET https://tapauth.ai/api/v1/token/{id}(with Bearer auth) to retrieve the access token. No API key needed — your agent can follow the code examples below end-to-end.
ChatGPT can analyze documents, summarize reports, and extract data from complex files — but only if you can get those files to it. Copy-pasting from Google Drive works for one document. For an agent that needs to read, search, and process files across an entire Drive, you need OAuth. And Google's OAuth implementation is one of the most complex in the industry.
This is exactly the kind of problem TapAuth was built to solve. TapAuth is the token layer for AI agents — one API call, and your agent gets a valid, scoped, user-approved Google Drive token with automatic refresh. No OAuth implementation. No consent screen configuration. No refresh token management. Your agent asks for a token and gets one.
This guide walks through the full manual Google OAuth flow for Drive (so you understand the complexity you're avoiding) and then the TapAuth approach. Google is arguably the most complex OAuth provider to implement correctly — and one of the most important for real-world agent use cases.
Why Google OAuth is the hardest provider for agents
Google does OAuth correctly by the spec. That's the problem — the spec is complex, and Google implements all of it plus their own requirements on top:
- The consent screen gauntlet. Before writing any code, you need to configure an OAuth consent screen in Google Cloud Console. You pick scopes, set up branding, and — if you want more than 100 users — submit for Google's verification review. This can take weeks. Unverified apps show a scary warning that most users won't click through, and agents can't click through it at all.
- Refresh tokens are one-shot. Google only sends a refresh token on the first authorization. If you don't store it, you never get another one unless the user revokes and re-authorizes. This is the most common Google OAuth bug: everything works in development, then tokens expire in production and can't be refreshed because the refresh token was lost during the first auth.
- The
access_type=offlinerequirement. To get a refresh token at all, you must passaccess_type=offlinein the authorization URL. Forget this parameter, and you get an access token that expires in one hour with no way to refresh it. The default isonline— meaning the default behavior gives you tokens your agent can't maintain. - Scope creep verification. Google categorizes scopes as "sensitive" and "restricted." Drive scopes are sensitive. If you request restricted scopes (like full Drive access), you need an additional security assessment on top of the consent screen review. Most agent use cases can avoid restricted scopes, but the categorization isn't always intuitive.
- Token expiration is aggressive. Google access tokens expire after exactly 3,600 seconds (one hour). No exceptions, no extensions. Your agent needs to refresh tokens proactively or handle 401 responses and retry — either way, you're building token lifecycle management.
This is the most common OAuth provider developers implement, and it's still the one that generates the most Stack Overflow questions. For an AI agent that just wants to read a Google Doc, you're looking at a significant infrastructure investment.
What the manual Google Drive OAuth flow looks like
Here's the server you need to run for Google's web OAuth flow. This handles the happy path — production code needs token refresh, error recovery, consent screen edge cases, and scope change handling layered on top.
import express from 'express';
const app = express();
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID!;
const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET!;
const REDIRECT_URI = 'http://localhost:3000/google/callback';
// Step 1: Redirect user to Google's consent screen
// Note: access_type=offline is REQUIRED for refresh tokens
// Note: prompt=consent forces Google to re-issue refresh token
app.get('/google/auth', (req, res) => {
const params = new URLSearchParams({
client_id: GOOGLE_CLIENT_ID,
redirect_uri: REDIRECT_URI,
response_type: 'code',
scope: 'https://www.googleapis.com/auth/drive.readonly',
access_type: 'offline', // Without this, no refresh token
prompt: 'consent', // Without this, refresh token only on first auth
});
res.redirect(
`https://accounts.google.com/o/oauth2/v2/auth?${params}`
);
});
// Step 2: Handle the callback
app.get('/google/callback', async (req, res) => {
const code = req.query.code as string;
const error = req.query.error as string;
if (error) {
// Google returns errors in the redirect — not in the token exchange
res.status(400).send(`Authorization failed: ${error}`);
return;
}
if (!code) {
res.status(400).send('No authorization code received');
return;
}
// Step 3: Exchange code for tokens
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: GOOGLE_CLIENT_ID,
client_secret: GOOGLE_CLIENT_SECRET,
code,
grant_type: 'authorization_code',
redirect_uri: REDIRECT_URI,
}),
});
const data = await tokenResponse.json();
if (data.error) {
console.error('Google OAuth error:', data.error_description);
res.status(500).send(`Token exchange failed: ${data.error_description}`);
return;
}
// CRITICAL: Store the refresh_token — Google only sends it once!
// If you lose it, user must revoke access and re-authorize
const accessToken = data.access_token; // Expires in 1 hour
const refreshToken = data.refresh_token; // Only present on first auth
const expiresIn = data.expires_in; // Always 3600
if (!refreshToken) {
console.warn(
'No refresh token received — was this a re-authorization? ' +
'The user may need to revoke access at myaccount.google.com ' +
'and re-authorize to get a new refresh token.'
);
}
await storeTokens({ accessToken, refreshToken, expiresIn });
res.send('Connected to Google Drive!');
});
// Step 4: Refresh tokens before they expire
async function getValidToken(): Promise<string> {
const stored = await getStoredTokens();
if (stored.expiresAt > Date.now() + 60_000) {
return stored.accessToken; // Still valid (with 1-min buffer)
}
if (!stored.refreshToken) {
throw new Error(
'Token expired and no refresh token available. ' +
'User must re-authorize.'
);
}
// Refresh the access token
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: GOOGLE_CLIENT_ID,
client_secret: GOOGLE_CLIENT_SECRET,
grant_type: 'refresh_token',
refresh_token: stored.refreshToken,
}),
});
const data = await response.json();
if (data.error) {
throw new Error(`Token refresh failed: ${data.error_description}`);
}
// Note: Google does NOT return a new refresh token on refresh
// You must keep using the original one
await storeTokens({
accessToken: data.access_token,
refreshToken: stored.refreshToken, // Keep the original
expiresIn: data.expires_in,
});
return data.access_token;
}
// Step 5: Use the token with Drive API
async function listDriveFiles(query?: string) {
const token = await getValidToken();
const params = new URLSearchParams({
pageSize: '20',
fields: 'files(id, name, mimeType, modifiedTime)',
});
if (query) params.set('q', query);
const response = await fetch(
`https://www.googleapis.com/drive/v3/files?${params}`,
{
headers: { Authorization: `Bearer ${token}` },
}
);
if (!response.ok) {
if (response.status === 401) {
// Token expired between check and use — retry once
const freshToken = await getValidToken();
// ... retry logic
}
throw new Error(`Drive API error: ${response.status}`);
}
return response.json();
}
app.listen(3000);
That's a lot of code for an agent that just wants to read some documents. And this is the simplified version — production code needs scope change detection, concurrent refresh protection (two requests trying to refresh at the same time), and handling of Google's various revocation scenarios (user revokes in Google settings, admin revokes via Workspace admin panel, token revoked for inactivity after 6 months).
The TapAuth approach: one API call, any provider
TapAuth is an access gateway for AI agents. It handles the entire Google OAuth flow — consent screen, refresh tokens, the one-shot refresh token problem, hourly expiration — and gives your agent the same simple interface it uses for every other provider.
// Step 1: Create a grant — tell TapAuth what you need
const grantResponse = await fetch(
'https://tapauth.ai/api/v1/grants',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TAPAUTH_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
provider: 'google',
scopes: ['https://www.googleapis.com/auth/drive.readonly'],
}),
}
);
const { grant_id, grant_secret, approve_url } = await grantResponse.json();
// Step 2: The user approves at the approve_url
// TapAuth handles the consent screen, access_type=offline, prompt=consent
console.log(`Approve access: ${approve_url}`);
// Step 3: Retrieve the token (poll until approved)
const tokenResponse = await fetch(
`https://tapauth.ai/api/v1/token/${grant_id}`,
{
headers: { 'Authorization': `Bearer ${grant_secret}` },
}
);
const { access_token } = await tokenResponse.json();
// Now use the token directly with Google Drive API
const driveResponse = await fetch(
'https://www.googleapis.com/drive/v3/files?' +
new URLSearchParams({
pageSize: '20',
fields: 'files(id, name, mimeType, modifiedTime)',
q: "mimeType='application/vnd.google-apps.document'",
}),
{
headers: { Authorization: `Bearer ${access_token}` },
}
);
const { files } = await driveResponse.json();
console.log(`Found ${files.length} Google Docs`);
Same two API calls as connecting to GitHub, Slack, or Gmail. TapAuth stores the refresh token securely (no one-shot problem), refreshes automatically before expiration (no 3,600-second cliff), and handles the consent screen configuration (no weeks-long verification process for your OAuth app). ChatGPT doesn't need to know any of Google's OAuth complexity — it gets a working token and uses the Drive API directly.
When the token expires in an hour, TapAuth refreshes it. When Google requires re-consent after 6 months of inactivity, TapAuth handles that too. Your agent code stays the same: request a token, use the Drive API. Authentication is infrastructure, and TapAuth makes it invisible.
What you can build with ChatGPT + Google Drive + TapAuth
With Drive authentication handled, ChatGPT can work directly with your documents, spreadsheets, and files:
- Document summarizer: "Read the Q4 planning doc and give me a bullet-point summary of the key decisions and action items." ChatGPT can fetch the document content via Drive API and apply its reasoning to real workspace documents.
- Spreadsheet analyst: "Pull the latest revenue data from the finance spreadsheet and tell me which product lines are trending down." ChatGPT reads Sheets data through the Drive API and provides analysis without you copying and pasting cells.
- File organizer: "Find all documents modified in the last week that mention the product launch and move them to the Launch Planning folder." ChatGPT can search, read metadata, and organize files programmatically.
- Meeting prep: "Read the agenda doc, the last three meeting notes, and the project tracker — give me a briefing for tomorrow's sync." ChatGPT aggregates context across multiple Drive files and synthesizes a useful summary.
Each of these requires a Google Drive token with appropriate scopes. Without TapAuth, each also requires maintaining the full OAuth infrastructure described above — consent screens, refresh token storage, hourly token rotation. With TapAuth, you write the Drive API calls and nothing else.
Scopes and permissions: what to request for Google Drive
Google's Drive scopes range from narrow to extremely broad. Start with the least privilege your agent needs:
| Scope | What it allows | When to use it |
|---|---|---|
drive.readonly | Read all files and metadata | Document reading, search, analysis |
drive.file | Access files created by this app only | Agent creates and manages its own files |
drive.metadata.readonly | Read file names, dates, sizes — not content | File organization, search, inventory |
drive | Full read/write access to all Drive files | File management, moving, renaming (use sparingly) |
documents.readonly | Read Google Docs content | Document-specific reading (narrower than drive.readonly) |
For most ChatGPT use cases, drive.readonly is the right choice. It lets your agent read any file the user has access to without being able to modify or delete anything. Use drive.file if your agent only needs to work with files it creates itself — this is the narrowest write scope available.
Avoid the full drive scope unless your agent genuinely needs to move, rename, or delete files. Google classifies it as a restricted scope, which triggers additional verification requirements and shows users a more alarming consent prompt. TapAuth lets you configure scopes per grant — your reading agent gets drive.readonly while your file manager gets drive, each with appropriate user consent.
Stop wrestling with Google's OAuth complexity
Google has the most complex OAuth implementation of any major provider — consent screen verification, one-shot refresh tokens, hourly expiration, scope categorization, and multiple revocation scenarios. Building this correctly takes days. Maintaining it takes ongoing attention every time Google changes their requirements.
TapAuth absorbs all of that complexity into one API call. Connect ChatGPT to Google Drive the same way you'd connect it to GitHub, Slack, or any other provider — two API calls, and your agent has a working token that stays fresh.
Get started — connect ChatGPT to Google Drive in under five minutes. No consent screen configuration. No refresh token juggling. No hourly expiration cliffs. Just tokens when your agent needs them.