How to Secure CopilotKit Endpoints with Authentication and Authorization

CopilotKit does not provide built-in authentication; instead, it exposes a flexible onRequest hook in the CopilotRuntimeHooks interface that runs before every request reaches the runtime, allowing you to implement API keys, JWT validation, or OAuth by throwing a Response object with HTTP error codes.

The CopilotKit runtime exposes HTTP endpoints through server adapters like Express, Hono, and Elysia. Rather than enforcing a specific security model, the framework delegates authentication and authorization to user-defined lifecycle hooks. This design lets you secure CopilotKit endpoints with any validation strategy while maintaining a clean separation between transport concerns and business logic.

Understanding the Authentication Flow

When you deploy CopilotKit as an Express server, requests traverse a specific pipeline before reaching the core runtime. Understanding this flow is essential for implementing effective security controls.

Router Creation and Request Handling

The createCopilotExpressHandler function in packages/runtime/src/v2/runtime/endpoints/express.ts constructs an Express router that forwards every incoming request to a node handler. This handler acts as a bridge between the Express transport layer and the CopilotKit runtime.

The typical flow follows these steps:

  1. Router initialization – createCopilotExpressHandler returns an express.Router configured with your basePath and optional CORS settings.
  2. Lifecycle hook execution – If provided, the onRequest hook from CopilotRuntimeHooks executes synchronously before any CopilotKit operation.
  3. Runtime forwarding – If the hook completes without throwing, the request reaches createCopilotRuntimeHandler in packages/runtime/src/v2/runtime/core/runtime.ts to execute agent operations.

The onRequest Hook Lifecycle

The onRequest hook defined in packages/runtime/src/v2/runtime/core/hooks.ts receives the incoming Request object and runs for every endpoint—including info, run, connect, stop, and transcribe operations. Because this hook executes synchronously and sits at the edge of the runtime, throwing a Response immediately short-circuits request processing, preventing unauthenticated traffic from reaching your agents.

The hook signature accepts an object containing the request, allowing you to inspect headers, query parameters, or body payloads before making authorization decisions.

Implementing Basic Authentication

To secure your CopilotKit endpoints, provide an onRequest implementation when creating the server handler. The following example from packages/runtime/src/v2/runtime/endpoints/express.ts (lines 72-86) demonstrates rejecting requests lacking an Authorization header:

import { createCopilotExpressHandler } from "@copilotkit/runtime";
import { CopilotRuntime } from "@copilotkit/runtime";

const runtime = new CopilotRuntime();

app.use(
  createCopilotExpressHandler({
    runtime,
    basePath: "/api/copilotkit",
    cors: true,
    hooks: {
      onRequest: ({ request }) => {
        if (!request.headers.get("authorization")) {
          // CopilotKit translates the thrown Response into an HTTP 401
          throw new Response("Unauthorized", { status: 401 });
        }
      },
    },
  })
);

When the hook throws a Response object, CopilotKit automatically propagates the status code and message to the HTTP client. This mechanism works identically across all supported server frameworks because the core runtime handles the Response translation in packages/runtime/src/v2/runtime/endpoints/express-fetch-bridge.ts.

Advanced Authorization Patterns

For scenarios requiring role-based access control or token validation, implement asynchronous checks within the hook. The following pattern validates a JWT and enforces fine-grained permissions:

hooks: {
  onRequest: async ({ request }) => {
    const authHeader = request.headers.get("authorization");
    const token = authHeader?.replace(/^Bearer /, "");
    
    if (!token) {
      throw new Response("Unauthorized", { status: 401 });
    }
    
    const user = await verifyJwt(token);
    
    if (!user || !user.canRunAgent) {
      throw new Response("Forbidden", { status: 403 });
    }
    
    // Optionally attach user context to the request for downstream use
    request.user = user;
  },
}

This approach allows you to:

  • Validate API keys – Compare header values against secrets stored in environment variables.
  • Implement OAuth/OpenID Connect – Integrate with libraries like passport or express-jwt to perform token validation.
  • Enforce rate limiting – Inspect the request origin and implement custom throttling logic before the runtime processes the request.

Alternative Middleware Approaches

While the onRequest hook provides centralized security, you can also layer Express middleware before the CopilotKit router for reusable authentication logic. This pattern works well when multiple routes share the same validation strategy:

// Custom middleware runs first
app.use("/api/copilotkit", validateApiKey);
app.use(
  createCopilotExpressHandler({
    runtime,
    basePath: "/api/copilotkit",
    hooks: {
      // Additional hooks can perform authorization checks
      onRequest: ({ request }) => {
        // Access user data attached by preceding middleware
        if (!request.user?.permissions?.includes("copilot:execute")) {
          throw new Response("Forbidden", { status: 403 });
        }
      },
    },
  })
);

Because the runtime remains agnostic to specific security schemes, you can mix hook-based and middleware-based approaches to satisfy complex compliance requirements without modifying the core CopilotKit source.

Summary

  • No built-in auth layer: CopilotKit delegates security to user-defined hooks rather than enforcing a specific authentication scheme.
  • Centralized control: The onRequest hook in CopilotRuntimeHooks runs before every endpoint (info, run, connect, stop, transcribe), providing a single checkpoint for securing the entire API surface.
  • Short-circuit mechanism: Throwing a Response object with an error status (401, 403) prevents unauthenticated requests from reaching the runtime in packages/runtime/src/v2/runtime/core/runtime.ts.
  • Flexible implementation: Support API keys, JWTs, OAuth, or custom schemes by inspecting the Request object and implementing validation logic in the hook or preceding middleware.

Frequently Asked Questions

Does CopilotKit provide built-in JWT or OAuth support?

No. According to the CopilotKit/CopilotKit source code, the framework intentionally avoids built-in authentication to remain agnostic to security requirements. You implement JWT validation, OAuth flows, or API key checks inside the onRequest hook or through preceding middleware. The CopilotRuntimeHooks interface provides the extension point, but the specific validation logic is your responsibility.

Which endpoints does the onRequest hook protect?

The onRequest hook secures every CopilotKit runtime endpoint, including info, run, connect, stop, and transcribe operations. Because the hook executes in packages/runtime/src/v2/runtime/endpoints/express.ts before forwarding to the core runtime, it acts as a universal gatekeeper for the entire API surface defined by your basePath.

Can I use async operations inside the onRequest hook?

Yes. While the hook runs before the request reaches the runtime, it supports asynchronous operations. You can call external authentication services, query databases for user permissions, or validate tokens against remote JWKS endpoints. Ensure you await these operations before throwing a Response or allowing the request to continue.

How do I handle CORS when implementing authentication?

The createCopilotExpressHandler accepts an optional cors parameter that applies CORS headers at the router level. Configure CORS before implementing authentication checks to ensure preflight requests succeed. If you require credentials in cross-origin requests, set cors: { origin: true, credentials: true } and ensure your onRequest hook validates the Origin header when necessary.

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 →