OAuth 2.0 Authorization Script for Google Drive API
Prerequisites
- Completed Lesson 1 — you have a Google Cloud project with Drive API enabled and an OAuth
credentials/gdrive-oauth.jsonfile - Node.js 18+ installed
googleapisnpm package installed
npm install googleapis
Project Structure
We will create two files:
your-project/
├── credentials/
│ ├── gdrive-oauth.json ← Downloaded from Google Cloud Console
│ └── gdrive-token.json ← Created by the auth script (auto-generated)
├── src/
│ ├── gdrive-auth.js ← Auth helper (shared by all scripts)
│ └── gdrive-authorize.js ← One-time authorization (run once)
├── .gitignore
└── package.json
Add to .gitignore:
credentials/
Credential Files Reference
| File | Contains | Created how | Automatic? | Security |
|---|---|---|---|---|
credentials/gdrive-oauth.json | OAuth client ID + client secret | Download manually from Google Cloud Console → Credentials → OAuth client | No — one-time manual download | Secret — anyone with this can impersonate your app |
credentials/gdrive-token.json | Access token + refresh token | Generated automatically when you run npm run gdrive-auth | Semi — you run the command once, it saves the file | Secret — anyone with this can read/write your Drive files without logging in |
credentials/gdrive-service-account.json | Private key for a service account | Download manually from Google Cloud Console → Service Accounts → Keys | No — one-time manual download | Secret — full access to whatever the service account can reach |
Never commit these files to git. The
credentials/directory should always be in.gitignore. If someone clones your repo, they runnpm run gdrive-authto generate their own tokens with their own Google account.
Service accounts vs OAuth: Most projects use OAuth (the first two files). Service accounts are a separate auth method — a robot account with its own email, useful for server-to-server automation without user interaction. If your project only uses OAuth, you don't need a service account file.
File 1: src/gdrive-auth.js — Authentication Helper
This module is imported by both the authorization script and the upload script.
const { google } = require('googleapis')
const path = require('path')
const fs = require('fs')
const CREDS_PATH = path.join(
__dirname,
'..',
'credentials',
'gdrive-oauth.json',
)
const TOKEN_PATH = path.join(
__dirname,
'..',
'credentials',
'gdrive-token.json',
)
const SCOPES = ['https://www.googleapis.com/auth/drive.file']
function loadOAuthClient() {
if (!fs.existsSync(CREDS_PATH)) {
console.error(
'\n❌ OAuth credentials not found at: credentials/gdrive-oauth.json',
)
console.error(
' Download from Google Cloud Console → Credentials → OAuth client\n',
)
process.exit(1)
}
const content = JSON.parse(fs.readFileSync(CREDS_PATH, 'utf-8'))
const creds = content.installed || content.web
if (!creds) {
console.error('\n❌ Invalid OAuth credentials. Use "Desktop app" type.\n')
process.exit(1)
}
return new google.auth.OAuth2(
creds.client_id,
creds.client_secret,
`http://localhost:${process.env.GDRIVE_AUTH_PORT || 3456}`,
)
}
function getDriveClient() {
const oAuth2Client = loadOAuthClient()
if (!fs.existsSync(TOKEN_PATH)) {
console.error('\n❌ No saved token. Run: npm run gdrive-auth\n')
process.exit(1)
}
const token = JSON.parse(fs.readFileSync(TOKEN_PATH, 'utf-8'))
oAuth2Client.setCredentials(token)
// Auto-save refreshed tokens
oAuth2Client.on('tokens', (newTokens) => {
const merged = { ...token, ...newTokens }
fs.writeFileSync(
TOKEN_PATH,
JSON.stringify(merged, null, 2) + '\n',
'utf-8',
)
})
return google.drive({ version: 'v3', auth: oAuth2Client })
}
module.exports = { loadOAuthClient, getDriveClient, SCOPES, TOKEN_PATH }
Key details
SCOPES:drive.fileonly allows access to files created by this app — it cannot see or modify your other Drive files. This is the most restrictive scope that still allows uploads.- Redirect URI: Uses
http://localhost:3456— the port can be changed via theGDRIVE_AUTH_PORTenvironment variable if 3456 is in use. - Auto token refresh: When the access token expires, the
googleapislibrary automatically uses the refresh token. Theon("tokens")listener saves the new token to disk.
File 2: src/gdrive-authorize.js — One-Time Authorization
This script runs once to get your consent and save a refresh token.
const http = require('http')
const { URL } = require('url')
const fs = require('fs')
const { loadOAuthClient, SCOPES, TOKEN_PATH } = require('./gdrive-auth')
const PORT = 3456
async function authorize() {
const oAuth2Client = loadOAuthClient()
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
prompt: 'consent',
scope: SCOPES,
})
console.log('\n🔐 Google Drive Authorization\n')
console.log(' Opening your browser...\n')
console.log(" If it doesn't open, visit this URL manually:\n")
console.log(` ${authUrl}\n`)
// Open browser (macOS)
const { exec } = require('child_process')
exec(`open "${authUrl}"`)
return new Promise((resolve, reject) => {
const server = http.createServer(async (req, res) => {
try {
const url = new URL(req.url, `http://localhost:${PORT}`)
const code = url.searchParams.get('code')
if (!code) {
res.writeHead(400)
res.end('No authorization code received')
return
}
const { tokens } = await oAuth2Client.getToken(code)
fs.writeFileSync(
TOKEN_PATH,
JSON.stringify(tokens, null, 2) + '\n',
'utf-8',
)
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end(`
<html><body style="font-family: sans-serif; text-align: center; padding: 50px;">
<h1>✅ Authorization successful!</h1>
<p>You can close this tab and go back to the terminal.</p>
</body></html>
`)
console.log(' ✅ Token saved to credentials/gdrive-token.json\n')
server.close()
resolve()
} catch (err) {
res.writeHead(500)
res.end('Authorization failed: ' + err.message)
server.close()
reject(err)
}
})
server.listen(PORT, () => {
console.log(
` Waiting for authorization on http://localhost:${PORT}...\n`,
)
})
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.error(
`\n❌ Port ${PORT} is in use. Set GDRIVE_AUTH_PORT or close the process.\n`,
)
}
reject(err)
})
})
}
authorize().catch((err) => {
console.error('\n❌ Authorization failed:', err.message)
process.exit(1)
})
Add npm Scripts
In package.json:
{
"scripts": {
"gdrive-auth": "node src/gdrive-authorize.js"
}
}
Running the Authorization
npm run gdrive-auth
What happens
- The script starts a local HTTP server on port 3456
- Your browser opens to Google's consent page
- You log in with the Google account that owns the Drive folders
- Google shows a consent screen — click Allow
- Google redirects to
http://localhost:3456with an authorization code - The script exchanges the code for access + refresh tokens
- Tokens are saved to
credentials/gdrive-token.json - The browser shows "Authorization successful!" — close the tab
"This app isn't verified" warning
If your OAuth consent screen is set to External and not verified by Google, you'll see a warning. This is normal for personal projects:
- Click Advanced
- Click Go to your-app-name (unsafe)
- Click Continue
This warning does not appear if your consent screen is set to Internal (Google Workspace only).
Port conflict
If port 3456 is already in use (e.g., by nginx or another service), change it:
GDRIVE_AUTH_PORT=4567 npm run gdrive-auth
Token Lifecycle
The saved gdrive-token.json contains:
| Field | Purpose | Lifetime |
|---|---|---|
access_token | Used for API calls | ~1 hour |
refresh_token | Used to get new access tokens | Indefinite (until revoked) |
expiry_date | When the access token expires | Auto-managed |
The getDriveClient() function in gdrive-auth.js automatically refreshes expired tokens and saves the new ones to disk. You only need to run npm run gdrive-auth once — unless you revoke access or the refresh token expires.
What's Next
In the next lesson, we'll build the upload script that watches local directories and automatically uploads new files to Google Drive folders.