# How OpenWork Implements OAuth Flows for Managed MCP Connections in `local-managed-mcp.ts`

> Learn how local-managed-mcp.ts uses secure OAuth 2.0 flows for MCP connections with HMAC signed tokens vault encrypted credentials and automatic refresh logic.

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

---

**[`local-managed-mcp.ts`](https://github.com/different-ai/openwork/blob/main/local-managed-mcp.ts) implements a complete, cryptographically secure OAuth 2.0 lifecycle for Model Context Protocol connections using HMAC-signed state tokens, vault-encrypted credentials, and automatic refresh logic.**

The OpenWork repository (`different-ai/openwork`) provides enterprise-grade Model Context Protocol (MCP) integrations through its local-managed connection handler. This article examines how [`apps/server/src/local-managed-mcp.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/local-managed-mcp.ts) orchestrates the OAuth 2.0 authorization flow, state verification, and encrypted credential persistence that powers secure MCP server connections.

## Starting the Authorization Flow

The OAuth process begins with **`startLocalManagedMcpAuthorization`** (lines 992–1015), which initiates the authentication dance and generates a cryptographically secure state token.

First, the function marks the connection status as **connecting** and writes a provisional runtime entry. It then calls **`createAuthorizationState`** to build a JSON payload (`AuthorizationStatePayload`) containing the workspace ID, connection ID, redirect URI, expiration timestamp, and a random nonce. This payload is base-64 encoded and HMAC-signed using the vault encryption key (`vaultKey`) via `createHmac`, producing a tamper-proof state parameter.

The signed state is passed to **`enterpriseClient().connect`** along with the redirect URI. If the provider returns an instant *connected* status, the flow completes immediately. Otherwise, the function returns the provider’s **`authorizeUrl`** for browser redirection.

```typescript
// Initiate OAuth from an API endpoint
const publicConn = await startLocalManagedMcpAuthorization(
  serverConfig,
  workspaceId,
  "my-mcp-connection",
);

// If authorization is required, redirect the user:
// publicConn.authorizeUrl → "https://provider.com/oauth/authorize?..."

```

## Handling the OAuth Callback

After user consent, the provider redirects to the callback URL handled by **`completeLocalManagedMcpAuthorization`** (lines 1017–1044). This function validates the returned state and exchanges the authorization code for tokens.

The **`verifyAuthorizationState`** helper splits the incoming state string, recomputes the expected HMAC signature, and validates it against the provided signature using **`timingSafeEqual`** to prevent timing attacks. It also verifies expiration timestamps and ensures the connection ID matches the stored record.

Once validated, the function retrieves the stored `EnterpriseMcpConnection` via **`enterpriseConnection`** and invokes **`enterpriseClient().completeAuthorization`** with the authorization code. After successful token exchange, it runs **`verifyTools`** to confirm the tool catalog is accessible, writes the final runtime entry via `writeManagedRuntimeEntry`, and returns the public connection metadata.

```typescript
// Callback handler invoked by the OAuth provider
export async function oauthCallback(req: Request) {
  const url = new URL(req.url);
  const state = url.searchParams.get("state")!;
  const code = url.searchParams.get("code")!;

  const { connection, workspaceId } = await completeLocalManagedMcpAuthorization(
    serverConfig,
    state,
    code,
  );

  // connection.status is now "connected" with hasCredential: true
  return Response.json(connection);
}

```

## Token Persistence and Refresh Mechanisms

Credential storage follows a zero-trust encryption model powered by **`createPersistence`**, which returns an `EnterpriseMcpOAuthPersistence` object wired into the enterprise MCP SDK.

All persistence callbacks (`load`, `save`, `invalidate`) operate within encrypted vault transactions (`withVaultMutation`) using **AES-256-GCM** encryption. The **`authorizationStorageKey`** generates unique HMAC-based keys for each authentication transaction, ensuring isolation between connection instances.

When tokens expire or API requests fail authentication, **`markReconnectWhenCredentialIsGone`** detects the missing or invalid credentials and updates the connection status to **reconnect_required**. This signal triggers the UI to prompt the user for re-authorization. The system automatically handles token refresh via **`enterpriseClient().refreshCredentials`** when possible.

```typescript
// Automatic refresh triggered by the enterprise client
await enterpriseClient().refreshCredentials({
  connection: await enterpriseConnection(serverConfig, workspaceId, "my-mcp-connection"),
  redirectUri: localManagedMcpCallbackUrl(serverConfig),
});

```

## Core Implementation Details

### State Generation and CSRF Protection

The **`createAuthorizationState`** function constructs a JSON payload containing:
- `workspaceId` and `connectionId` for context binding
- `redirectUri` for validation
- `expiresAt` timestamp (short-lived)
- `nonce` for replay protection

This payload is serialized, base-64 encoded, and signed with the **`vaultKey`** using HMAC-SHA256. The final state string follows the format `${encoded}.${signature}`, providing cryptographic integrity checks against CSRF and session fixation attacks.

### Enterprise MCP Client Architecture

The **`enterpriseClient()`** factory creates a configured client via `createEnterpriseMcpClient` wrapped with **`guardedFetch`**. This client handles both the initial `connect` request and subsequent `completeAuthorization` calls, enforcing URL validation through [`local-managed-mcp-url-guard.ts`](https://github.com/different-ai/openwork/blob/main/local-managed-mcp-url-guard.ts) to prevent Server-Side Request Forgery (SSRF) against private networks.

### Vault Encryption and Key Rotation

The vault system derives encryption keys from **`process.env.OPENWORK_ENCRYPTION_KEY`** or a configuration-provided secret. When the encryption key changes, the vault enters a quarantined state and rebuilds from a non-secret index, forcing credential reconnection to ensure forward secrecy.

### Runtime Integration

Authorized connections are persisted to the workspace runtime via **[`runtime-opencode-config-store.ts`](https://github.com/different-ai/openwork/blob/main/runtime-opencode-config-store.ts)**. The `writeManagedRuntimeEntry` function inserts a remote MCP configuration pointing to the local gateway (`gatewayPath`) authenticated with a bearer token (`gatewayToken`), enabling seamless tool invocation across the OpenWork platform.

## Summary

- **`startLocalManagedMcpAuthorization`** initiates OAuth flows with HMAC-signed state tokens to prevent CSRF attacks.
- **`completeLocalManagedMcpAuthorization`** validates callbacks using constant-time signature comparison (`timingSafeEqual`) before exchanging codes for tokens.
- All credentials are encrypted with **AES-256-GCM** and stored in a versioned vault with automatic invalidation on key rotation.
- The **`markReconnectWhenCredentialIsGone`** handler ensures graceful degradation when tokens expire or are revoked.
- Runtime entries bridge authorized connections into the Opencode execution environment via `gatewayPath` and `gatewayToken` authentication.

## Frequently Asked Questions

### How does OpenWork prevent CSRF attacks in the OAuth flow?

OpenWork binds each authorization request to a cryptographically signed state token generated by **`createAuthorizationState`**. This token includes the connection ID, workspace ID, and a nonce, all HMAC-signed with the vault key. During callback processing, **`verifyAuthorizationState`** recomputes the signature and uses **`timingSafeEqual`** to prevent timing attacks, ensuring the state parameter was not tampered with or replayed from a different session.

### What encryption standard protects credentials in [`local-managed-mcp.ts`](https://github.com/different-ai/openwork/blob/main/local-managed-mcp.ts)?

The implementation uses **AES-256-GCM** encryption for all sensitive data, including client registrations, access tokens, refresh tokens, and PKCE code verifiers. The encryption keys are derived from `process.env.OPENWORK_ENCRYPTION_KEY` and managed through the vault abstraction layer, ensuring that credentials remain encrypted at rest and are only decrypted within secure mutation contexts.

### How does the system handle expired or invalidated OAuth tokens?

When a token expires or an API request returns an authentication error, the **`markReconnectWhenCredentialIsGone`** utility detects the missing credential and transitions the connection status to **reconnect_required**. This status is surfaced to the UI, prompting the user to re-authorize. If the provider supports refresh tokens, **`enterpriseClient().refreshCredentials`** automatically attempts to obtain new access tokens before flagging the connection for manual reconnection.

### Where is the connection state stored during the authorization process?

Transient authorization state (including the signed state parameter and PKCE verifiers) is stored in the encrypted vault via **`authorizationStorageKey`**, keyed by the connection ID. Once authorization completes, persistent connection metadata is written to **[`runtime-opencode-config-store.ts`](https://github.com/different-ai/openwork/blob/main/runtime-opencode-config-store.ts)**, which maintains the gateway configuration and bearer tokens required for ongoing MCP tool invocations within the workspace runtime.