← Back to Home

How to Set Up YouTube OAuth2 Authentication

Updated January 14, 2026
youtubeoauth2authenticationgoogle cloudcredentials

Setting Up YouTube OAuth2 Authentication

YouTube Data API requires OAuth2 authentication for uploading videos and managing channels.

Prerequisites

  1. Google Cloud Project
  2. YouTube Data API v3 enabled
  3. OAuth 2.0 Client ID and Secret

Step 1: Create Google Cloud Project

  1. Go to Google Cloud Console
  2. Create a new project
  3. Enable YouTube Data API v3:
    • Navigate to APIs & Services → Library
    • Search "YouTube Data API v3"
    • Click "Enable"

Step 2: Create OAuth Credentials

  1. Navigate to APIs & Services → Credentials
  2. Click "Create Credentials" → "OAuth client ID"
  3. Configure:
    • Application type: Desktop app
    • Name: Your App YouTube Uploader
  4. Click "Create"
  5. Save your Client ID and Client Secret

Step 3: Configure Redirect URI

The redirect URI is constructed automatically:

http://localhost:{port}/oauth/callback

Default port: 9671

If port is in use, change it in config and register new redirect URI in Google Cloud Console.

Step 4: Store Credentials

Create config.json (gitignored):

{
  "youtube": {
    "enabled": true,
    "clientId": "YOUR_CLIENT_ID.apps.googleusercontent.com",
    "clientSecret": "GOCSPX-YOUR_CLIENT_SECRET",
    "redirectUri": "/oauth/callback",
    "port": 9671,
    "defaultPrivacy": "private",
    "autoUpload": false
  }
}

Step 5: Implement OAuth Flow

import { google } from 'googleapis';
import { OAuth2Client } from 'google-auth-library';

interface YouTubeConfig {
  clientId: string;
  clientSecret: string;
  redirectUri: string;
  port: number;
}

class AuthManager {
  private oauth2Client: OAuth2Client;
  private config: YouTubeConfig;

  constructor(config: YouTubeConfig) {
    this.config = config;
    this.oauth2Client = new OAuth2Client(
      config.clientId,
      config.clientSecret,
      `http://localhost:${config.port}${config.redirectUri}`
    );
  }

  getOAuth2Client(): OAuth2Client {
    return this.oauth2Client;
  }

  async authenticate(): Promise<void> {
    // Check for existing tokens
    if (this.hasStoredTokens()) {
      this.oauth2Client.setCredentials(this.loadStoredTokens());
      return;
    }

    // Generate auth URL
    const authUrl = this.oauth2Client.generateAuthUrl({
      access_type: 'offline',
      scope: [
        'https://www.googleapis.com/auth/youtube.upload',
        'https://www.googleapis.com/auth/youtube.readonly',
        'https://www.googleapis.com/auth/yt-analytics.readonly',
      ],
    });

    // Open browser for user consent
    console.log(`Opening browser: ${authUrl}`);
    await this.openBrowser(authUrl);

    // Wait for callback...
    const code = await this.waitForCallback();

    // Exchange code for tokens
    const { tokens } = await this.oauth2Client.getToken(code);
    this.oauth2Client.setCredentials(tokens);
    this.saveTokens(tokens);
  }
}

OAuth Scopes

Scope Purpose
https://www.googleapis.com/auth/youtube.upload Upload videos
https://www.googleapis.com/auth/youtube.readonly View channel data
https://www.googleapis.com/auth/yt-analytics.readonly View analytics (24hr views)

Step 6: Handle Callback

import http from 'http';

async function waitForCallback(): Promise<string> {
  return new Promise((resolve, reject) => {
    const server = http.createServer((req, res) => {
      if (req.url?.startsWith('/oauth/callback')) {
        const url = new URL(req.url, `http://localhost:${this.config.port}`);
        const code = url.searchParams.get('code');

        if (code) {
          res.writeHead(200, { 'Content-Type': 'text/plain' });
          res.end('Authentication successful! You can close this window.');
          server.close();
          resolve(code);
        } else {
          res.writeHead(400);
          res.end('No code found');
          reject(new Error('No authorization code'));
        }
      }
    });

    server.listen(this.config.port);
  });
}

Token Storage

import fs from 'fs';
import path from 'path';

const TOKEN_FILE = '.youtube-tokens.json';

function saveTokens(tokens: any): void {
  fs.writeFileSync(TOKEN_FILE, JSON.stringify(tokens, null, 2));
}

function loadStoredTokens(): any {
  if (fs.existsSync(TOKEN_FILE)) {
    return JSON.parse(fs.readFileSync(TOKEN_FILE, 'utf-8'));
  }
  return null;
}

function hasStoredTokens(): boolean {
  return fs.existsSync(TOKEN_FILE);
}

Re-authentication

To re-authenticate (different account or new scopes):

rm .youtube-tokens.json
# Run app again - will trigger OAuth flow