OpenSEO Authentication Mechanisms: User Access, Project Scoping, and API Integration Security
OpenSEO employs a layered authentication architecture combining Cloudflare Access JWTs for user sessions, project-level organization scoping via middleware, Google OAuth 2.0 for third-party integrations, and Basic Auth for DataForSEO API calls, with a local_noauth fallback for self-hosted deployments.
The every-app/open-seo repository implements a robust, multi-layered authentication model that secures both the internal UI and external API integrations. Understanding these authentication mechanisms is critical for developers deploying self-hosted instances or integrating with the platform's DataForSEO-backed tooling. The system distinguishes between user-session authentication via Cloudflare Access, organization-scoped project authorization, and third-party service authentication using OAuth 2.0 and API keys.
User Session Authentication: Cloudflare Access and Local Fallback
OpenSEO supports two distinct modes for authenticating user sessions, controlled by the configuration in src/lib/auth-mode.ts.
Cloudflare Access JWT Validation
For production deployments, OpenSEO uses Cloudflare Access to secure all server-function entry points. When a request arrives at the MCP transport layer in src/server/mcp/transport.ts, the getAuthenticatedContext helper extracts and validates the signed JWT generated by Cloudflare Access.
The validated token attaches an auth object to the context containing:
userIduserEmailorganizationId- Token
scopes
Self-Hosted Local No-Auth Mode
For self-hosted deployments, the system provides a local_noauth fallback. In this mode, the middleware uses a static admin identity rather than validating external JWTs.
// src/server/mcp/transport.ts
export async function handler(request: Request, context: RequestContext) {
const { authMode } = context;
if (authMode === "local_noauth") {
// Admin identity for self-hosted mode
context.auth = { userId: "admin", userEmail: "admin@localhost", organizationId: "local-org", scopes: [] };
} else {
const auth = getAuthenticatedContext(context);
if (!auth) return new Response("MCP auth context required", { status: 403 });
context.auth = auth;
}
// …continue handling
}
Project-Level Organization Scoping
After user authentication, OpenSEO enforces organization boundaries through the ensureUserMiddleware defined in src/middleware/ensureUser.ts. This middleware resolves the project's organization and verifies the authenticated user belongs to that organization before allowing operations on specific projects.
// src/middleware/ensureUser.ts
export const ensureUserMiddleware = createMiddleware({
async before({ context }) {
const auth = getAuthenticatedContext(context);
if (!auth) throw new HttpError(401, "Unauthenticated");
const project = await resolveProject(context.params.projectId, auth.organizationId);
if (!project) throw new HttpError(403, "Project not in your org");
return { auth, project };
},
});
This project-scoping mechanism applies to all server functions in src/serverFunctions/* that operate on specific projects, ensuring users cannot access data outside their organizational boundaries.
Google OAuth 2.0 Integration for Third-Party APIs
OpenSEO integrates with Google Search Console and Google Analytics 4 using OAuth 2.0 authentication. The implementation resides in src/server/features/google/oauth-config.ts, which detects self-hosted Google OAuth configurations via the hasSelfHostedGoogleOAuthConfig function.
Search Console and Analytics Authentication
When self-hosted OAuth is configured, the system uses stored refresh tokens to obtain access tokens with specific scopes:
https://www.googleapis.com/auth/webmasters.readonlyfor Search Consolehttps://www.googleapis.com/auth/analytics.readonlyfor GA4
// src/serverFunctions/gsc.ts
import { hasSelfHostedGoogleOAuthConfig } from "@/server/features/google/oauth-config";
export const getGscData = serverFn(async (ctx) => {
if (!hasSelfHostedGoogleOAuthConfig()) {
throw new Error("gsc_oauth_not_configured");
}
const token = await getGoogleAccessToken(); // refresh token flow
const res = await fetch("https://www.googleapis.com/webmasters/v3/sites", {
headers: { Authorization: `Bearer ${token}` },
});
return await res.json();
});
The src/serverFunctions/ga4.ts file implements similar logic for Analytics 4 data retrieval, using the same OAuth configuration detection.
DataForSEO API Authentication
For DataForSEO-backed tooling, OpenSEO uses HTTP Basic Authentication with API keys passed via environment variables. The credentials are never exposed in the client bundle and are accessed server-side in src/server/mcp/tools/*.
// src/server/mcp/tools/site-audit-tools.ts
export async function fetchSiteAudit(context: McpContext, url: string) {
const { baseUrl, apiKey, apiSecret } = context.auth;
const response = await fetch(`${baseUrl}/site-audit`, {
method: "POST",
headers: {
Authorization: `Basic ${btoa(`${apiKey}:${apiSecret}`)}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ url }),
});
return response.json();
}
MCP Transport Layer and Token Scoping
The MCP (Marketplace-API) transport layer in src/server/mcp/transport.ts validates Bearer tokens from the Authorization header. It extracts the organizationId and required scopes, injecting the same auth object used by the UI middleware to ensure consistent authorization across all entry points.
Requests include Authorization: Bearer <token> headers, and the transport validates these tokens before processing tool invocations, ensuring that billing and quota enforcement remain tied to the correct organization.
Summary
- Cloudflare Access or
local_noauthhandles user-session authentication via JWT validation insrc/server/mcp/transport.ts - Project scoping enforces organization boundaries through
ensureUserMiddlewareinsrc/middleware/ensureUser.ts - Google OAuth 2.0 provides secure access to Search Console and Analytics 4 data using refresh tokens configured in
src/server/features/google/oauth-config.ts - DataForSEO API keys use HTTP Basic Auth via server-side environment variables, accessible in
src/server/mcp/tools/* - All authentication flows propagate the
organizationIdto ensure correct billing and quota enforcement across the platform
Frequently Asked Questions
How does OpenSEO handle authentication in self-hosted environments?
Self-hosted deployments use the local_noauth mode defined in src/lib/auth-mode.ts, which bypasses Cloudflare Access and assigns a static admin identity with userId: "admin" and organizationId: "local-org". This allows single-user or development instances to function without external identity providers while maintaining the same authorization structure as production deployments.
What OAuth scopes does OpenSEO request for Google integrations?
OpenSEO requests read-only scopes for Google services: https://www.googleapis.com/auth/webmasters.readonly for Search Console data and https://www.googleapis.com/auth/analytics.readonly for Google Analytics 4. These scopes restrict the application to read-only access of site performance data without write permissions.
How is the DataForSEO API key secured?
The DataForSEO API credentials are stored in environment variables on the server and accessed only through server-side tool implementations in src/server/mcp/tools/*. The keys are never bundled in client-side JavaScript, and all API requests use HTTP Basic Authentication headers generated server-side using btoa() encoding of the key and secret combination.
How does project-level authorization prevent cross-organization access?
The ensureUserMiddleware in src/middleware/ensureUser.ts resolves the project using the request's projectId parameter and validates that the project's organization matches the authenticated user's organizationId. If the organizations do not match, the middleware throws a 403 error, preventing users from accessing projects outside their organizational scope.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →