Skip to content

Conversation

@rogiervandenberg
Copy link

This PR adds support for Google Service Account authentication to the Google Ads API library, enabling server-to-server authentication without requiring OAuth2 refresh tokens.

🎯 Problem Solved

Closes #493, #83

The library previously only supported OAuth2 authentication which requires:

  • Client ID and Client Secret
  • Refresh tokens for each customer
  • Manual token management and renewal

This approach is problematic for server-to-server applications where:

  • You don't want to store user refresh tokens
  • You need automated, unattended access to Google Ads
  • You want to use Google Cloud service accounts for authentication

✨ Features Added

Service Account Authentication

  • Full support for Google Cloud service account authentication
  • No refresh tokens required
  • Compatible with existing OAuth2 authentication (non-breaking change)
  • Works with both gRPC and REST API calls
  • Type-safe implementation with TypeScript support

Clean API Design

  • Simple, intuitive API that follows Google Auth Library patterns
  • Maintains backward compatibility with existing OAuth2 code
  • Proper error handling for authentication failures

💻 Usage

Before (OAuth2 only)

const client = new GoogleAdsApi({
  client_id: "CLIENT_ID",
  client_secret: "CLIENT_SECRET",
  developer_token: "DEVELOPER_TOKEN",
});

const customer = client.Customer({
  customer_id: "CUSTOMER_ID",
  refresh_token: "REFRESH_TOKEN", // Required!
});

After (With Service Account Support)

import { GoogleAdsApi } from 'google-ads-api';
import { auth, JWT } from 'google-auth-library';

// Create JWT auth client from service account key
const authClient = auth.fromJSON(serviceAccountKey) as JWT;
authClient.scopes = ["https://www.googleapis.com/auth/adwords"];
await authClient.authorize();

// Create GoogleAdsApi client with service account auth
const client = new GoogleAdsApi({
  auth_client: authClient,
  developer_token: "DEVELOPER_TOKEN",
});

// No refresh_token needed!
const customer = client.Customer({
  customer_id: "CUSTOMER_ID",
});

🔧 Implementation Details

Core Changes

  • Enhanced ClientOptions: Added ServiceAccountClientOptions type alongside existing OAuth2ClientOptions
  • Updated Service class: Modified authentication logic to handle both OAuth2 and service account flows
  • Type safety: Added proper TypeScript interfaces and type guards
  • Backward compatibility: All existing OAuth2 code continues to work unchanged

Authentication Flow

  • gRPC calls: Service account auth client is used with grpc.credentials.createFromGoogleCredential()
  • REST calls: Access tokens are obtained directly from the service account auth client
  • Token caching: Maintains existing access token caching for performance

📚 Documentation

  • Added comprehensive documentation in SERVICE_ACCOUNT_AUTH.md
  • Included practical examples showing service account setup and usage
  • Updated type definitions with proper interfaces

🧪 Testing

  • Added comprehensive test suite for service account authentication
  • Tests verify both OAuth2 and service account authentication paths
  • Type guards properly distinguish between authentication methods
  • All existing tests continue to pass

Breaking Changes: None - this is fully backward compatible, new service account authentication is opt-in and both methods can coexist.

Dependencies: Uses existing google-auth-library dependency (updated to compatible version)

@psociety
Copy link

psociety commented Oct 6, 2025

@rogiervandenberg I wanted to try it and i installed it via npm i "https://github.com/rogiervandenberg/google-ads-api/tarball/add-service-account-support" but that doesn't seem to make the build.

Running npm run build was met with alot of error TS1056: Accessors are only available when targeting ECMAScript 5 and higher explosions.

Do you you know how could i properly install it as a package while it's not merged?
Edit: seems like: npm install rogiervandenberg/google-ads-api#add-service-account-support does the job!

@miwelc
Copy link

miwelc commented Oct 10, 2025

Hello, just wanted to leave here this issue I've encountered while trying to make this PR work: grpc/grpc-node#2993
Reverting google-auth-library to 9.15.1 fixes it.
It may be related to how headers are passed down the line: https://github.com/rogiervandenberg/google-ads-api/blob/3c8771ffd72f63347cd51fd69471149b6ec9e764/src/client.ts#L87

…brary v10

- Implement dual CommonJS/ESM build system
- Fix gRPC credentials creation for compatibility with google-auth-library v10
- Resolve ESM import issues for stream-chain and stream-json
- Replace dynamic require with top-level import in service.ts
TypeScript still emits extensionless relative imports in the ESM
build, so Node’s loader can’t resolve files when the package is
installed from npm. The new fix-esm script runs after tsc, walks
build/esm, and appends the correct .js (or index.js) suffix based on
the filesystem. Wiring it into `npm run build` ensures the published
artifacts always have Node-compliant specifiers without touching the
TypeScript sources.
@rogiervandenberg
Copy link
Author

I've updated the PR to update to google-auth-library v10 and fix the way headers are passed down.

Fixed CREDENTIALS_MISSING error by switching to grpc.credentials.createFromMetadataGenerator. This resolves the incompatibility between google-auth-library v10 and the previous gRPC credential method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Service Account Authentication

3 participants