OpenSEO Data Privacy and Security: Securing Google Search Console and Analytics Integrations

OpenSEO isolates all Google credentials and user data on the server side, encrypts tokens at rest, and performs OAuth token exchange exclusively in backend server functions to ensure sensitive information never reaches the client browser.

OpenSEO is an open-source SEO platform that connects to Google Search Console and Google Analytics 4 to deliver performance insights. When handling these OpenSEO data privacy and security requirements for Google integrations, the application implements a zero-trust architecture that keeps API keys, access tokens, and search performance data strictly confined to the server. This design ensures that private user information and third-party credentials remain encrypted, transient, and isolated from potential client-side exposure.

Server-Side OAuth Token Exchange

OpenSEO handles the entire Google OAuth flow through secure server functions, preventing credential leakage to the frontend.

When a user initiates a connection to Google Search Console or GA4, the frontend redirects the browser to Google’s consent screen. After consent, Google returns a short-lived authorization code to the OpenSEO backend at endpoints defined in src/serverFunctions/gsc.ts and src/serverFunctions/ga4.ts. The backend then exchanges this code for access and refresh tokens using the client secret stored in environment variables.

// src/serverFunctions/gsc.ts – server‑side OAuth callback
export async function handleGscOAuthCallback(req: Request) {
  const { code } = req.query;
  // Exchange auth code for tokens (server‑side only)
  const tokenResp = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    body: new URLSearchParams({
      client_id: process.env.GOOGLE_CLIENT_ID!,
      client_secret: process.env.GOOGLE_CLIENT_SECRET!,
      code: code as string,
      grant_type: 'authorization_code',
      redirect_uri: `${process.env.BASE_URL}/api/gsc/callback`,
    }),
  });
  const { access_token, refresh_token } = await tokenResp.json();

  // Persist encrypted tokens
  await db.gscTokens.create({
    userId: req.user.id,
    encryptedAccess: encrypt(access_token),
    encryptedRefresh: encrypt(refresh_token),
  });
}

This server-side exchange ensures that process.env.GOOGLE_CLIENT_SECRET and the resulting bearer tokens never traverse the public internet to the browser.

Encrypted Token Storage and Database Schema

All Google authentication tokens are encrypted before persistence and stored in isolated database schemas.

The tokens reside in src/db/gsc.schema.ts for Search Console and src/db/ga4.schema.ts for Analytics 4, with fields designed to hold encrypted values. The application uses server-side encryption utilities to protect access_token and refresh_token values at rest, ensuring that even database backups cannot reveal clear-text Google credentials.

// Example schema pattern from src/db/gsc.schema.ts
// Fields store encrypted blobs, not plain tokens
encryptedAccess: varchar('encrypted_access', { length: 1000 }).notNull(),
encryptedRefresh: varchar('encrypted_refresh', { length: 1000 }).notNull(),

Least-Privilege Data Handling

OpenSEO applies least-privilege principles when querying Google APIs, fetching only the specific metrics required for SEO analysis and discarding personally identifiable information.

All subsequent API calls to Google Search Console and GA4 occur exclusively within server functions such as src/serverFunctions/gsc.ts. The backend attaches the decrypted access token to HTTPS request headers behind the server firewall, queries for specific dimensions like keyword rankings or page performance, and immediately normalizes the results. Unnecessary metadata that could contain user-identifying details is filtered out before storage in the internal audit schema.

// src/serverFunctions/gsc.ts – fetch keyword rankings
export async function fetchGscData(userId: string, siteUrl: string) {
  const tokens = await db.gscTokens.findFirst({ where: { userId } });
  const access = decrypt(tokens!.encryptedAccess);

  const resp = await fetch(
    `https://searchconsole.googleapis.com/webmasters/v3/sites/${encodeURIComponent(
      siteUrl,
    )}/searchAnalytics/query`,
    {
      method: 'POST',
      headers: { Authorization: `Bearer ${access}` },
      body: JSON.stringify({ dimensions: ['query'], rowLimit: 1000 }),
    },
  );
  const { rows } = await resp.json();

  // Keep only the fields Open SEO needs
  return rows.map(r => ({ keyword: r.keys[0], clicks: r.clicks, position: r.position }));
}

This approach minimizes the data footprint and ensures that raw Google API responses containing extraneous user data never reach the application’s persistence layer.

GDPR Compliance and Data Erasure

OpenSEO provides built-in mechanisms for GDPR-compliant data erasure, allowing complete removal of Google-linked data upon user request.

The src/shared/gdpr-erasure.ts module implements a server-side job that purges all tokens, cached reports, and derived analytics associated with a user account. This fulfills the right-to-be-forgotten by removing not just database records in src/db/gsc.schema.ts and src/db/ga4.schema.ts, but also any residual audit logs or temporary cache files containing Google data.

// src/shared/gdpr-erasure.ts
export async function eraseGoogleData(userId: string) {
  await db.gscTokens.deleteMany({ where: { userId } });
  await db.ga4Tokens.deleteMany({ where: { userId } });
  await db.auditData.deleteMany({ where: { userId } });
  // Additional cleanup of cached reports, etc.
}

The detailed erasure procedure is documented in runbooks/gdpr-erasure.md, providing operators with a step-by-step guide for handling deletion requests. The platform’s privacy policy at web/content/legal/privacy.md further outlines these data retention and deletion practices.

Secure Configuration and Self-Hosting Validation

API keys and client secrets are strictly environment-driven and verified against accidental exposure.

OpenSEO requires all Google OAuth credentials to be supplied via environment variables, with .env.example providing the necessary placeholders. To prevent accidental commits of secrets, the repository includes src/shared/selfhost-checks.ts, which validates during deployment that no sensitive strings appear in the client bundle or public source code.

This ensures that GOOGLE_CLIENT_SECRET and GOOGLE_CLIENT_ID remain server-only configurations, inaccessible to build tools or frontend JavaScript bundles.

Summary

Frequently Asked Questions

How does OpenSEO store Google API tokens?

OpenSEO encrypts access and refresh tokens using server-side encryption utilities before persisting them to the database. According to the schemas in src/db/gsc.schema.ts and src/db/ga4.schema.ts, tokens are stored as encrypted blobs rather than plain text, ensuring that database access alone cannot compromise Google account credentials.

Can users completely delete their Google data from OpenSEO?

Yes. The src/shared/gdpr-erasure.ts module provides a dedicated function eraseGoogleData() that deletes all Search Console and Analytics tokens, cached reports, and derived audit data associated with a user ID. This implements the GDPR right-to-erasure and is documented in the operational runbook at runbooks/gdpr-erasure.md.

Are Google credentials ever exposed to the browser?

No. The Google Client Secret stored in process.env.GOOGLE_CLIENT_SECRET is accessed only within server functions like src/serverFunctions/gsc.ts. The OAuth token exchange and all subsequent API calls execute exclusively on the server, with only processed SEO metrics (such as keyword rankings) returned to the client interface.

What happens when a user revokes Google access?

When a user revokes OAuth access through Google’s Account Settings, the refresh token stored in OpenSEO’s database becomes invalid. The next server-side attempt to refresh the access token in src/serverFunctions/ga4.ts or src/serverFunctions/gsc.ts will fail, triggering a logout or re-authentication flow that requires the user to complete the OAuth consent process again before data can be accessed.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →