Implementation:Helicone Helicone GCP Service Account Auth
| Knowledge Sources | |
|---|---|
| Domains | Authentication, Cloud Infrastructure, AI Gateway |
| Last Updated | 2026-02-14 06:32 GMT |
Overview
A GCP service account JWT authentication module designed for Cloudflare Workers that generates RS256-signed JWTs using the Web Crypto API and exchanges them for Google OAuth2 access tokens.
Description
The gcpServiceAccountAuth.ts module provides Google Cloud Platform authentication for environments where standard Google auth libraries are unavailable (such as Cloudflare Workers). It implements the full OAuth2 service account flow:
- Parses a GCP service account JSON to extract the private key and client email
- Constructs a JWT header (
RS256) and claims (issuer, scope, audience, expiration) - Signs the JWT using the Web Crypto API's
RSASSA-PKCS1-V1_5algorithm - Exchanges the signed JWT for an access token via Google's OAuth2 token endpoint
- Optionally caches tokens using a
CacheProviderwith SHA-256 hashed cache keys
The cache key is constructed from the organization ID, a SHA-256 hash of the service account JSON, and the requested scopes, ensuring cache isolation across tenants. Tokens are cached with a 5-minute safety margin before their actual expiration. When no CacheProvider is supplied, the module falls back to generating a fresh token on every call.
Internal helper functions handle PEM key cleaning, ArrayBuffer-to-base64url conversion, and string encoding needed for the Web Crypto API's PKCS8 key import.
Usage
Use getGoogleAccessToken to obtain a Google OAuth2 access token for authenticating with Google Vertex AI or other GCP services from within the Helicone AI Gateway or any Cloudflare Workers environment.
Code Reference
Source Location
- Repository: Helicone
- File: packages/cost/auth/gcpServiceAccountAuth.ts
Signature
export async function getGoogleAccessToken(
serviceAccountJson: string,
orgId?: string,
scopes?: string[], // defaults to ["https://www.googleapis.com/auth/cloud-platform"]
cacheProvider?: CacheProvider
): Promise<string>
// Internal helpers (not exported)
function str2ab(str: string): ArrayBuffer
function arrayBufferToBase64Url(buffer: ArrayBuffer): string
function objectToBase64url(obj: any): string
async function signJWT(content: string, privateKey: string): Promise<string>
async function hashString(str: string): Promise<string>
async function generateGoogleAccessToken(
serviceAccount: ServiceAccount,
scopes: string[]
): Promise<GoogleTokenResponse>
Import
import { getGoogleAccessToken } from "@helicone/cost/auth/gcpServiceAccountAuth";
I/O Contract
| Parameter | Type | Required | Description |
|---|---|---|---|
serviceAccountJson
|
string
|
Yes | GCP service account JSON as a string |
orgId
|
string
|
No | Organization ID for cache key isolation |
scopes
|
string[]
|
No | OAuth2 scopes (defaults to cloud-platform)
|
cacheProvider
|
CacheProvider
|
No | Distributed cache for token reuse |
| Return | Type | Description |
|---|---|---|
| Access token | string
|
Google OAuth2 access token valid for up to 1 hour |
| Internal Interface | Fields | Description |
|---|---|---|
ServiceAccount
|
project_id, private_key, client_email, token_uri, ...
|
Parsed GCP service account JSON structure |
GoogleTokenResponse
|
access_token, expires_in, token_type
|
Response from Google's OAuth2 token endpoint |
Usage Examples
import { getGoogleAccessToken } from "@helicone/cost/auth/gcpServiceAccountAuth";
// Basic usage without caching
const token = await getGoogleAccessToken(
process.env.GCP_SERVICE_ACCOUNT_JSON!
);
// With caching and org isolation
const token = await getGoogleAccessToken(
serviceAccountJson,
"org-123",
["https://www.googleapis.com/auth/cloud-platform"],
redisCacheProvider
);
// Use the token with Vertex AI
const response = await fetch(
`https://us-central1-aiplatform.googleapis.com/v1/projects/my-project/locations/us-central1/publishers/google/models/gemini-pro:generateContent`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ contents: [{ parts: [{ text: "Hello" }] }] }),
}
);