How Kaneo’s Authentication Middleware Works: A Deep Dive into API Security
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, 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, 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 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 ownerapiKey: The full API key object containing metadatauserandsession: Set tonullto 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, 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:
// 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:
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 inapps/api/src/index.tscatches every request before route handlers execute. - Multi-strategy validation: The
authenticateApiRequesthelper unifies API key (x-api-keyheader), Bearer token (Authorizationheader), and cookie-based session authentication into a single flow. - Consistent error handling: All authentication failures return
401 Unauthorized, while successful requests populatec.get("user"),c.get("session"), orc.get("apiKey")depending on the credential type. - Sentry integration: Every authenticated request tags the user ID for error tracking, while internal paths like
/api/mcpbypass 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 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, 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 (specifically the api.use("*") call) and the validation helper in 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.
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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →