How Kaneo Handles API Authentication: Better Auth Implementation Explained

Kaneo uses Better Auth with a global authenticateApiRequest middleware that supports both Bearer session tokens and API keys via the x-api-key header.

Kaneo is an open-source project management platform built by usekaneo/kaneo. Understanding how Kaneo handles API authentication is essential for developers integrating with its REST API or deploying self-hosted instances. This guide breaks down the authentication flow, middleware implementation, and configuration based on the actual source code.


Better Auth Core Configuration

Kaneo's authentication foundation lives in apps/api/src/auth.ts. This file exports a configured betterAuth instance that powers the entire API security layer.

The configuration enables multiple authentication methods through a plugin architecture:

  • bearer() — Accepts standard Bearer tokens for session-based authentication
  • apiKey({ apiKeyHeaders: "x-api-key", ... }) — Enables API key authentication with optional rate limiting
  • deviceAuthorization — Supports OAuth device flow for CLI and automation clients
  • magicLink, emailOTP, genericOAuth — Provide passwordless sign-in options
  • anonymous — Creates temporary guest accounts when guest access is enabled
  • adminPlugin — Grants admin role based on database role fields
  • openAPI() — Exposes authentication metadata for API documentation

Global Authentication Middleware

All protected API routes flow through the authenticateApiRequest middleware defined in apps/api/src/utils/authenticate-api-request.ts. This middleware is registered globally in apps/api/src/index.ts via:

api.use("*", async (c, next) => {
  await authenticateApiRequest(c);
  await next();
});

Authentication Flow

The middleware executes a prioritized credential check:

  1. Credential extraction — Parses the Authorization header for Bearer tokens and checks x-api-key for API keys
  2. API key verification — Calls verifyApiKey (from apps/api/src/utils/verify-api-key.ts) when an API key is present; valid keys inject userId, apiKey, and clear session data
  3. Session resolution — Attempts auth.api.getSession via Better Auth when no API key is found
  4. Token type handling — Bearer tokens first try API key lookup (allowing tokens to serve dual purpose), then fall back to session retrieval with cookie stripping
  5. Cookie fallback — Uses getSession(c.req.raw.headers) when no Bearer token is supplied

Invalid or missing credentials trigger a 401 Unauthorized HTTPException.

Context Population

Successful authentication sets these Hono context fields for downstream handlers:

Field Description
userId Authenticated user's UUID (from session or API key)
user Full User object (session-based auth only)
session Better Auth session record
apiKey API key metadata when key-based auth is used
userEmail Convenience copy of user's email (empty for API key auth)

Using Authentication in API Routes

Manual Middleware Application

For fine-grained control, call authenticateApiRequest directly within handlers:

import { authenticateApiRequest } from "./utils/authenticate-api-request";

const handler = async (c: Context) => {
  await authenticateApiRequest(c);
  const userId = c.get("userId");
  const apiKey = c.get("apiKey");
  // Proceed with authorized operations
};

Automatic Protection

Routes inherit authentication automatically through the global middleware:

api.get("/task/:id", async (c) => {
  const taskId = c.req.param("id");
  const userId = c.get("userId"); // Already validated
  // Fetch task with authorized user context
});

Client-Side Authentication Examples

API Key Authentication

Include your API key in the x-api-key header:

fetch("https://kaneo.example.com/api/task/123", {
  headers: {
    "x-api-key": "sk_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  },
});

Bearer Session Token

Use JWT session tokens after login:

fetch("https://kaneo.example.com/api/task/123", {
  headers: {
    "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
  },
});

OpenAPI Security Scheme

The OpenAPI specification in apps/api/src/openapi.ts declares a global Bearer security scheme that maps to this authentication system. This ensures consistent documentation and client generation across all protected endpoints.

Additional security-related logic includes:


Summary

  • Kaneo builds on Better Auth with a plugin-extensible architecture in apps/api/src/auth.ts
  • Global middleware authenticateApiRequest centralizes all API authentication logic
  • Dual credential support: API keys (x-api-key header) and Bearer session tokens
  • Context injection provides downstream handlers with userId, user, session, and apiKey fields
  • Automatic route protection via api.use("*", ...) in apps/api/src/index.ts
  • OpenAPI integration ensures consistent security documentation

Frequently Asked Questions

What authentication methods does Kaneo support?

Kaneo supports session-based authentication via Bearer tokens, API key authentication via the x-api-key header, OAuth device flow, magic links, email OTP, and anonymous guest access. The genericOAuth plugin enables integration with external identity providers.

How do I authenticate API requests to Kaneo?

Pass either an x-api-key header with a valid API key or an Authorization: Bearer <token> header with a session token. The global middleware in apps/api/src/utils/authenticate-api-request.ts automatically validates credentials and populates request context.

Where is the API authentication configured in Kaneo?

The main configuration is in apps/api/src/auth.ts, which instantiates Better Auth with plugins for bearer tokens, API keys, and various sign-in methods. The global middleware is defined in apps/api/src/utils/authenticate-api-request.ts and registered in apps/api/src/index.ts.

What happens when authentication fails?

The authenticateApiRequest middleware throws a 401 Unauthorized HTTPException for invalid, missing, or malformed credentials. This ensures consistent error handling across all protected endpoints without requiring manual checks in individual route handlers.

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 →