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

> Learn how OpenWork secures MCP connections with OAuth, API keys, or no authentication. Discover session tracking and capability-based access controls for robust user authentication and authorization.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: how-to-guide
- Published: 2026-08-02

---

**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`](https://github.com/different-ai/openwork/blob/main/packages/types/src/den/mcp-connection-action.ts). Each connection must declare an `authType` that determines how the client will authenticate:

```typescript
// 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`](https://github.com/different-ai/openwork/blob/main/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:

```typescript
// 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`](https://github.com/different-ai/openwork/blob/main/packages/enterprise-mcp-mock-server/test/wire-client.ts):

```typescript
// 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:

```typescript
// 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`](https://github.com/different-ai/openwork/blob/main/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:

```typescript
// 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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/auth-protection.ts) middleware examines the authenticated session's capabilities before executing RPC methods:

```typescript
// 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:

- **[`packages/types/src/den/mcp-connection-action.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/den/mcp-connection-action.ts)** – Defines the `authType` Zod schema and connection action types
- **[`packages/enterprise-mcp-mock-server/src/protocol/mcp-handler.ts`](https://github.com/different-ai/openwork/blob/main/packages/enterprise-mcp-mock-server/src/protocol/mcp-handler.ts)** – Implements session creation, `mcp-session-id` header management, and request validation
- **[`packages/enterprise-mcp-mock-server/test/wire-client.ts`](https://github.com/different-ai/openwork/blob/main/packages/enterprise-mcp-mock-server/test/wire-client.ts)** – Contains the reference implementation for OAuth PKCE flows
- **`ee/apps/den-web/app/(den)/_components/auth-screen.tsx`** – React component handling OAuth UI flows
- **[`ee/apps/den-api/src/auth.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/auth.ts)** – Server-side credential extraction and validation logic
- **[`ee/apps/den-api/src/auth-protection.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/auth-protection.ts)** – Middleware enforcing capability-based authorization on protected routes

## 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`](https://github.com/different-ai/openwork/blob/main/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.