# How Kaneo’s Authentication Middleware Works: A Deep Dive into API Security

> Discover how Kaneo's authentication middleware secures your API. Learn its approach to handling API keys, Bearer tokens, and cookies for a unified security layer. Explore the code.

- Repository: [kaneo.app/kaneo](https://github.com/usekaneo/kaneo)
- Tags: deep-dive
- Published: 2026-08-29

---

**Kaneo’s authentication middleware intercepts every incoming HTTP request through a global Hono wildcard handler, delegates validation to the `authenticateApiRequest` helper in [`apps/api/src/utils/authenticate-api-request.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/utils/authenticate-api-request.ts), and unifies API key, Bearer token, and cookie-based authentication into a single consistent security layer.**

The Kaneo project management platform, available at `usekaneo/kaneo`, implements a robust API security model using the **Hono** web framework combined with the **Better Auth** library. Every endpoint—except explicitly excluded internal paths—passes through centralized authentication logic that normalizes user identity into the request context before routing to business logic handlers.

## Global Middleware Registration in Hono

The entry point for all API security begins in [`apps/api/src/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/index.ts), where the Hono router instance (`api`) mounts a wildcard middleware before any feature routes are registered.

### Wildcard Route Handling

The middleware attaches to all paths using `api.use("*", async (c, next) => { … })`. This ensures zero endpoints are accidentally exposed without authentication validation. When a request arrives, the handler immediately wraps execution in `Sentry.withIsolationScope` to isolate user-identifying data to the current request context for error tracking purposes.

### Path Exclusions for Internal Routes

Before processing authentication, the middleware explicitly skips routes that utilize alternative security models. The excluded paths include `/api/mcp`, `/.well-known`, and `billing/webhook`. These endpoints typically handle internal service communication, webhook payloads, or model context protocol requests that require signature verification rather than session tokens.

## The Authentication Helper: authenticateApiRequest

The core validation logic resides in [`apps/api/src/utils/authenticate-api-request.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/utils/authenticate-api-request.ts) within the `authenticateApiRequest` function. This utility parses headers, validates credentials against multiple strategies, and populates the Hono context with standardized user data.

### Bearer Token Parsing

The helper first invokes `parseBearerToken` to extract a `Bearer <token>` value from the `Authorization` header. If the header is malformed or missing, the function immediately throws a `401 Unauthorized` error, terminating the request before reaching route handlers.

### API Key Validation

When an `x-api-key` header is present and no Bearer token exists, the middleware validates the key using `verifyApiKey`. Upon successful validation, the request context receives:
- `userId`: The identifier of the key owner
- `apiKey`: The full API key object containing metadata
- `user` and `session`: Set to `null` to indicate non-session authentication

The user ID is simultaneously attached to the Sentry scope for error correlation.

### Better Auth Session Verification

If a Bearer token is present but fails API key validation, the middleware treats it as a Better Auth session token. The `getSessionFromBearerOnlyHeaders` function calls `auth.api.getSession` **without cookie parsing** to prevent CSRF attacks on API endpoints. When a valid session and user object are returned, they are stored in the context via `c.set("user")` and `c.set("session")`.

### Cookie-Based Fallback

When no Bearer token or API key is supplied, the helper falls back to standard cookie-based session validation using `auth.api.getSession(c.req.raw.headers)`. This supports browser-based clients that rely on traditional session cookies. Missing or invalid cookies result in the same `401 Unauthorized` response to ensure consistent error handling across all authentication paths.

## Context Enrichment and Error Handling

After successful authentication—regardless of the method used—the middleware calls `attachUserToScope(userId)` to tag the Sentry request with the authenticated user’s identifier. The request then proceeds through `eventContext.run({ initiatorId }, next)`, propagating the user identity and optional window ID to downstream event handling and real-time broadcasting systems.

## Better Auth Integration and Hooks

Separate `/auth/*` routes are handled by `auth.handler` from [`apps/api/src/auth.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/auth.ts), which implements OAuth, magic link, device authentication, and local signup flows. These routes are wrapped by the same global middleware, ensuring every authentication endpoint benefits from shared 401 handling and logging.

The `auth` configuration also registers pre- and post-request hooks via `createAuthMiddleware`. These hooks enforce business policies such as disabling local sign-in, blocking disposable email registrations, and limiting workspace creation before Better Auth processes the request body.

## Accessing Authentication Data in Route Handlers

Once the middleware completes, route handlers can access authentication state through the Hono context without additional validation logic:

```typescript
// apps/api/src/custom.ts
import { createRoute } from "hono";
import { z } from "zod";

export const secretApi = api.route(
  "/custom",
  createRoute({
    method: "get",
    path: "/secret",
    tags: ["Custom"],
    summary: "Example protected endpoint",
    // No extra security definition – the global middleware already enforces it.
    request: {},
    responses: {
      200: { description: "OK" },
      401: { description: "Missing or invalid credentials" },
    },
  }),
  async (c) => {
    // Values are guaranteed by the middleware
    const user = c.get("user");          // typed as BetterAuth User | null
    const session = c.get("session");    // typed as BetterAuth Session | null
    const apiKey = c.get("apiKey");      // typed as { id, userId, … } | undefined

    // Business logic – you can branch on auth method if needed
    if (apiKey) {
      // API‑key‑only logic
    } else if (user) {
      // Session‑based logic
    }

    return c.json({ message: `Hello ${user?.name ?? "API client"}!` });
  },
);

```

For custom hooks or external utilities, you can also access the auth object directly:

```typescript
import { auth } from "./auth";

export async function someHook(ctx) {
  const session = await auth.api.getSession({ headers: ctx.req.raw.headers });
  if (!session?.user) throw new HTTPException(401);
  // …do something with session.user
}

```

## Summary

- **Global interception**: The `api.use("*")` middleware in [`apps/api/src/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/index.ts) catches every request before route handlers execute.
- **Multi-strategy validation**: The `authenticateApiRequest` helper unifies API key (`x-api-key` header), Bearer token (`Authorization` header), and cookie-based session authentication into a single flow.
- **Consistent error handling**: All authentication failures return `401 Unauthorized`, while successful requests populate `c.get("user")`, `c.get("session")`, or `c.get("apiKey")` depending on the credential type.
- **Sentry integration**: Every authenticated request tags the user ID for error tracking, while internal paths like `/api/mcp` bypass auth entirely.
- **Better Auth compatibility**: Session tokens are verified without cookies to prevent CSRF, while `/auth/*` routes still benefit from the global middleware’s standardized security model.

## Frequently Asked Questions

### How does Kaneo handle unauthenticated requests?

Kaneo returns a `401 Unauthorized` response immediately upon detecting malformed headers, missing credentials, or invalid tokens. This occurs in [`apps/api/src/utils/authenticate-api-request.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/utils/authenticate-api-request.ts) before the request reaches business logic, ensuring no endpoint accidentally serves data to anonymous users.

### What authentication methods does Kaneo support?

According to the source code in [`apps/api/src/utils/authenticate-api-request.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/utils/authenticate-api-request.ts), Kaneo supports three methods: **API keys** via the `x-api-key` header, **Bearer tokens** containing either API keys or Better Auth session tokens, and traditional **cookie-based sessions** for browser clients. All methods normalize to the same context variables for downstream handlers.

### Where is the authentication middleware defined in the codebase?

The primary middleware logic resides in two locations: the global route handler in [`apps/api/src/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/index.ts) (specifically the `api.use("*")` call) and the validation helper in [`apps/api/src/utils/authenticate-api-request.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/utils/authenticate-api-request.ts). The Better Auth instance configuration, including authentication plugins and hooks, is defined in [`apps/api/src/auth.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/auth.ts).

### How can I access the authenticated user in a route handler?

Route handlers retrieve the user through the Hono context using `c.get("user")` for Better Auth user objects, `c.get("session")` for session data, or `c.get("apiKey")` for API key metadata. These values are type-safe and guaranteed to exist if the middleware allowed the request to proceed, eliminating the need for redundant authentication checks in business logic.