OpenWork User Authentication and Authorization: A Complete Guide to MCP Connection Security

OpenWork handles user authentication and authorization through MCP (Model-Control-Plane) connections that support OAuth, API key, or no authentication, with each authenticated session tracked via the mcp-session-id header and validated against capability-based access controls.

OpenWork's enterprise architecture treats every external interaction as a managed MCP connection that explicitly declares its authentication method. The system enforces fine-grained authorization by validating credentials against connection-specific capabilities, ensuring that users and services can only access resources explicitly granted to their session.

Understanding OpenWork's Authentication Architecture

The MCP Connection as the Security Boundary

At the core of OpenWork's security model is the MCP connection, defined in packages/types/src/den/mcp-connection-action.ts. Each connection must declare an authType that determines how the client will authenticate:

// packages/types/src/den/mcp-connection-action.ts
export const mcpConnectionActionSchema = z.object({
  connectionId: z.string().uuid(),
  authType: z.enum(["oauth", "apikey", "none"]),
  // ... additional connection metadata
});

The authType enum restricts connections to three distinct modes: "oauth" for delegated user access, "apikey" for service-to-service authentication, or "none" for public endpoints. This declaration creates the security boundary that downstream handlers in packages/enterprise-mcp-mock-server/src/protocol/mcp-handler.ts enforce.

Supported Authentication Methods

OAuth provides PKCE-protected authorization-code flows suitable for user-facing applications. API keys offer static credential support for automated services. The "none" option exists for specific public health-check or metadata endpoints, though production deployments typically require one of the validated methods.

Implementing OAuth Authentication in OpenWork

The PKCE Authorization Code Flow

When authType is set to "oauth", OpenWork initiates a standard OAuth 2.0 flow with PKCE (Proof Key for Code Exchange). The client first redirects the user to the provider's authorization endpoint:

// Constructing the OAuth authorization URL
const authUrl = new URL(provider.authorizationPath, provider.baseUrl);
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("client_id", provider.clientId);
authUrl.searchParams.set("redirect_uri", redirectUri);
authUrl.searchParams.set("scope", provider.scopes.join(" "));
authUrl.searchParams.set("code_challenge", pkceChallenge);
authUrl.searchParams.set("code_challenge_method", "S256");

// Redirect user to authUrl

After user consent, the authorization code is exchanged for tokens. This implementation pattern is validated in the test suite at packages/enterprise-mcp-mock-server/test/wire-client.ts:

// Exchanging the authorization code for tokens
const tokenRes = await fetch(provider.tokenPath, {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "authorization_code",
    code: authorizationCode,
    client_id: provider.clientId,
    code_verifier: pkceVerifier,
    redirect_uri: redirectUri,
  }),
});

const { access_token, refresh_token } = await tokenRes.json();

The access_token becomes the credential for subsequent MCP requests, while the ee/apps/den-web/app/(den)/_components/auth-screen.tsx component handles the browser-side UI orchestration of this flow.

API Key Authentication for Service-to-Service Communication

For machine-to-machine communication where OAuth flows are impractical, OpenWork supports static API keys. When authType is "apikey", the client must include the credential in the Authorization header using the ApiKey scheme:

// Creating an API key connection
const connectionPayload = {
  connectionId: crypto.randomUUID(),
  authType: "apikey",
  // API key metadata configured separately
};

// Making authenticated requests
const response = await fetch(`${baseUrl}/rpc`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `ApiKey ${apiKeySecret}`,
  },
  body: JSON.stringify({
    jsonrpc: "2.0",
    method: "tools/call",
    params: { tool: "internal_data_sync" }
  }),
});

The server-side validation logic in ee/apps/den-api/src/auth.ts extracts and verifies these credentials before allowing the request to proceed to the MCP handler.

Session Management and Authorization

The mcp-session-id Header

Once authenticated via OAuth or API key, OpenWork establishes an MCP session. The server returns a unique session identifier in the mcp-session-id response header during the initialization phase:

// Initializing a session after authentication
const initRes = await fetch(`${baseUrl}/initialize`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${access_token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ connectionId: myConnectionId }),
});

const sessionId = initRes.headers.get("mcp-session-id");
// Store sessionId for subsequent requests

According to the implementation in packages/enterprise-mcp-mock-server/src/protocol/mcp-handler.ts, this header must accompany every subsequent JSON-RPC call. The handler validates that the session is active and matches the presented credentials, preventing session fixation attacks and enabling granular revocation.

Capability-Based Access Control

Authorization in OpenWork extends beyond simple authentication to capability-based access control. Each MCP connection maintains a list of permitted operations (capabilities) such as "read-workspace" or "manage-models".

The ee/apps/den-api/src/auth-protection.ts middleware examines the authenticated session's capabilities before executing RPC methods:

// Example RPC request with session tracking
await fetch(`${baseUrl}/rpc`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${access_token}`,
    "mcp-session-id": sessionId,
  },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: 1,
    method: "tools/call",
    params: { tool: "search_capabilities", args: {} },
  }),
});

If the connection's capability list does not include the requested operation, the server returns a 403 Forbidden error. This enforcement point ensures that even if credentials are compromised, the blast radius is limited to the specific capabilities granted to that connection.

Key Source Files and Implementation Details

OpenWork's authentication stack is distributed across several critical files:

Summary

  • OpenWork uses MCP connections as the fundamental security boundary, requiring explicit authType declaration of "oauth", "apikey", or "none"
  • OAuth implementations follow PKCE-protected authorization code flows, with token exchange patterns validated in the wire-client test suite
  • API key authentication uses static Authorization: ApiKey <secret> headers for service-to-service communication
  • Session persistence relies on the mcp-session-id header returned during initialization and validated by the MCP handler on every subsequent request
  • Capability-based authorization restricts operations based on permissions attached to the connection, enforced by the auth-protection middleware
  • Credential continuity is maintained across connection revisions while preventing session fixation through isolated session management

Frequently Asked Questions

How does OpenWork prevent session fixation attacks when updating MCP connections?

OpenWork prevents session fixation by binding the mcp-session-id to the specific connection revision while requiring fresh validation of OAuth tokens or API keys. When a connection is updated, existing sessions remain valid only if the underlying credentials haven't been revoked, but new capabilities require re-authorization. This isolation is tested in the scenario continuity test suite.

Can OpenWork handle multiple authentication methods for the same user?

Yes. OpenWork supports multiple concurrent MCP connections per user, each with distinct authType values. A user might maintain one connection using OAuth for browser-based interactions and another using API keys for CLI automation. Each connection has isolated sessions and capability sets, allowing granular control over access patterns.

What happens if the mcp-session-id header is missing or invalid?

If the mcp-session-id header is missing, malformed, or references a revoked session, the MCP handler in packages/enterprise-mcp-mock-server/src/protocol/mcp-handler.ts returns a 401 Unauthorized or 403 Forbidden response. The client must re-initialize the connection by re-authenticating (OAuth) or re-presenting the API key to obtain a new valid session ID.

How are OAuth refresh tokens managed in OpenWork?

OpenWork stores OAuth refresh tokens securely on the server side associated with the MCP connection metadata. When an access token expires, the server can automatically use the refresh token to obtain new credentials without requiring user interaction. This implementation detail is handled within the enterprise MCP client package, ensuring that long-running sessions maintain valid authentication without exposing refresh tokens to client-side code.

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 →