# How OpenSEO Authorizes Chat Agent Connections Using `resolveUserContextFromHeaders`

> Learn how OpenSEO authorizes chat agent connections using resolveUserContextFromHeaders to extract user identity from HTTP headers and enforce authentication before WebSocket upgrades.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: how-to-guide
- Published: 2026-09-04

---

**OpenSEO validates every chat agent connection by extracting user identity from HTTP headers via the `resolveUserContextFromHeaders` function in [`src/middleware/ensure-user/resolve.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/resolve.ts), constructing a typed `UserContext` that enforces authentication before WebSocket upgrades occur.**

The OpenSEO repository implements a robust authentication layer to protect its real-time chat agent endpoints. At the core of this security model sits the `resolveUserContextFromHeaders` utility, which transforms incoming request headers into a verified user identity object. This function ensures that only authenticated users can establish persistent WebSocket connections for chat interactions.

## The Authentication Pipeline in [`src/middleware/ensure-user/resolve.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/resolve.ts)

### Header Inspection and Token Extraction

The function begins by examining the incoming request headers for authentication credentials. It checks the `Authorization` header for a `Bearer` token or falls back to session cookies when present. The raw JWT string is extracted by stripping the `Bearer ` prefix, preparing it for cryptographic verification.

### Token Verification via Better-Auth

Once extracted, the token is passed to the `@/better-auth` provider for validation. This step verifies the token's digital signature, checks expiration timestamps, and validates the scope claims. If the signature is invalid or the token has expired, the function immediately halts processing.

### User Lookup and Context Construction

Upon successful token verification, the authentication provider returns the user's database record. The function then assembles a `UserContext` object containing:

- `userId`: The primary key identifying the user
- `organizationId`: The organization for multi-tenant isolation
- `permissions`: Capability flags derived from the user's role
- `sessionId`: Optional telemetry identifier for audit trails

This typed context object becomes the authoritative identity representation for the remainder of the request lifecycle.

## Protecting Chat Agent Connections

WebSocket upgrade requests for chat agents traverse the same authentication middleware as standard HTTP requests. When a client attempts to establish a chat session, the server invokes `resolveUserContextFromHeaders` before completing the WebSocket handshake. The function attaches the resolved `UserContext` to the request object, allowing downstream handlers to enforce organization-specific rate limits and verify chat session permissions.

If authentication fails at any stage, the function throws an `UnauthenticatedError`, causing the server to reject the connection before any chat agent logic executes. This prevents unauthorized access to real-time communication channels.

### Server-Side Implementation Example

```typescript
import { resolveUserContextFromHeaders } from '@/middleware/ensure-user/resolve';
import { createChatAgent } from '@/services/chatAgent';

export async function POST(request: Request) {
  const userContext = await resolveUserContextFromHeaders(request.headers);
  
  const agent = await createChatAgent({
    userId: userContext.userId,
    organizationId: userContext.organizationId,
  });

  return new Response(JSON.stringify({ wsUrl: agent.wsUrl }), { status: 200 });
}

```

### Client-Side Header Requirements

```typescript
async function startChat() {
  const token = localStorage.getItem('authToken');
  const response = await fetch('/api/chat', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
  });
  
  const { wsUrl } = await response.json();
  const socket = new WebSocket(wsUrl);
}

```

## Error Handling and Security Boundaries

The `resolveUserContextFromHeaders` function operates as a security gate. When the `Authorization` header is missing, malformed, or contains an invalid token, the function raises an `UnauthenticatedError`. This exception bubbles up to the server's error handling middleware in [`src/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server.ts), which returns a 401 Unauthorized response to the client.

All protected routes, including the chat agent initialization endpoint wrapped by [`src/middleware/ensureUser.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensureUser.ts), rely on this function to guarantee that `userId` and `organizationId` are present before executing business logic. This design ensures that chat messages are always properly attributed to verified users and that organization-level billing boundaries remain intact.

## Summary

- The `resolveUserContextFromHeaders` function in [`src/middleware/ensure-user/resolve.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/resolve.ts) serves as the primary authentication boundary for OpenSEO's chat agent connections.
- It extracts JWT tokens from the `Authorization` header or cookies, verifies them via the better-auth provider, and constructs a typed `UserContext` object.
- WebSocket upgrade requests undergo the same header validation as HTTP requests, preventing unauthorized persistent connections.
- The function throws `UnauthenticatedError` for missing or invalid credentials, blocking access before chat agent logic executes.
- Downstream services consume the `UserContext` to enforce multi-tenant isolation and attach user attribution to chat messages.

## Frequently Asked Questions

### What happens if the Authorization header is missing when connecting to a chat agent?

The `resolveUserContextFromHeaders` function detects the absence of credentials and immediately throws an `UnauthenticatedError`. This error propagates through the middleware stack in [`src/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server.ts), resulting in a 401 Unauthorized response that prevents the WebSocket connection from being established.

### Which authentication provider does OpenSEO use to verify the JWT tokens?

According to the source code in [`src/middleware/ensure-user/resolve.ts`](https://github.com/every-app/open-seo/blob/main/src/middleware/ensure-user/resolve.ts), the function delegates token verification to the `@/better-auth` provider. This provider handles cryptographic signature validation, expiration checks, and scope verification before returning the user record.

### Can `resolveUserContextFromHeaders` handle both Bearer tokens and session cookies?

Yes. The function inspects the `Authorization` header for `Bearer` tokens first, but it also checks for session cookies in the `cookie` header when present. This dual-mode support allows both API clients and browser-based users to authenticate chat agent connections using their preferred credential mechanism.

### How does the UserContext enforce multi-tenancy in chat applications?

The `UserContext` object contains an `organizationId` field populated during the user lookup phase. Downstream handlers in [`src/services/chatAgent.ts`](https://github.com/every-app/open-seo/blob/main/src/services/chatAgent.ts) use this field to isolate chat sessions, enforce rate limits, and validate billing entitlements specific to the user's organization, ensuring strict data separation between tenants.