How Kaneo's Authentication System Works with Better Auth and API Keys

Kaneo uses the Better Auth library as its single source of truth for authentication, configured in apps/api/src/auth.ts with support for email/password, OAuth, magic links, bearer tokens, and API key authentication via the x-api-key header.

Kaneo's backend authentication is powered entirely by Better Auth, a modern TypeScript authentication library. The @kaneo/api package centralizes all auth concerns in a single configuration file that wires together PostgreSQL-backed sessions, multiple sign-in methods, and fine-grained access controls.

Better Auth Configuration Architecture

The auth engine is instantiated in apps/api/src/auth.ts using betterAuth({...}) with a server-side base path of /api/auth and custom trusted origins. The configuration spans nearly 600 lines and defines every aspect of how users prove their identity and access the system.

Database Adapter and Schema

Better Auth connects to PostgreSQL through the Drizzle ORM adapter:

// apps/api/src/auth.ts L75-L91
drizzleAdapter(db, { 
  provider: "pg", 
  schema: { 
    user, account, session, verification, 
    workspace, workspaceMember, ... 
  } 
})

All required tables—including user, account, session, verification, and Kaneo's custom workspace tables—are defined in apps/api/src/database/schema.ts and passed directly to the adapter.

Extended User Model

The user schema adds a locale field for localized email flows:

// apps/api/src/auth.ts L94-L100
{
  type: "string",
  input: true
}

This field is captured during registration and used to render emails in the user's preferred language.

Authentication Methods in Kaneo

Kaneo supports multiple authentication strategies through Better Auth plugins. Each method can be enabled or disabled via environment variables.

Email and Password Authentication

Password login uses bcrypt hashing with custom hash and verify callbacks:

// apps/api/src/auth.ts L116-L127
emailAndPassword: {
  enabled: true,
  async hash(password) { /* bcrypt implementation */ },
  async verify({ password, hash }) { /* bcrypt verify */ },
  autoSignInAfterRegistration: true,
}

The autoSignInAfterRegistration flag eliminates an extra round-trip after account creation.

Social OAuth Providers

GitHub, Google, and Discord are pre-configured. GitHub credentials are resolved through a helper function:

// apps/api/src/auth.ts L128-L140
socialProviders: {
  github: {
    clientId: getGithubSsoOAuthCredentials().clientId,
    clientSecret: getGithubSsoOAuthCredentials().clientSecret,
  },
  google: { /* env-based */ },
  discord: { /* env-based */ },
}

The organization plugin enables automatic account linking when OAuth accounts share a verified email with an existing local account. Trusted providers are whitelisted: github, google, discord, and custom.

Passwordless authentication flows are handled by dedicated plugins:

  • Magic Link: Sends time-limited sign-in links via sendMagicLinkEmail
  • Email OTP: One-time passcodes (disabled when DISABLE_EMAIL_OTP_SIGN_IN is true)

Both methods integrate with the same session system as password logins.

API Key Authentication

The apiKey Plugin

Kaneo supports machine-to-machine authentication through the apiKey plugin, configured at lines 268-278:

// apps/api/src/auth.ts L268-L278
apiKey({
  apiKeyPrefixes: {
    default: "kaneo_",
    nextJs: "kaneo_nextjs_",
  },
  apiKeySession: { enabled: false },
  rateLimit: { enabled: true },
})

How to Use API Keys

API keys are passed in the x-api-key header:

// Client request example
await fetch(`${process.env.KANEO_API_URL}/api/tasks`, {
  headers: { "x-api-key": "kaneo_live_abc123xyz789" },
});

Unlike bearer tokens from user sessions, API keys:

  • Use configurable prefixes for key type identification
  • Optionally create ephemeral sessions (disabled in Kaneo's config)
  • Have independent rate-limiting rules

Bearer Token Alternative

The bearer plugin enables standard OAuth-style bearer token authentication for API clients that obtain tokens through user flows rather than static keys.

Workspace (Organization) Integration

Kaneo maps Better Auth's organization concept to its own workspace model. This plugin handles:

  • Role-based access: Dynamic permissions within workspaces
  • Team management: Grouping members into functional teams
  • Invitations: Email-based workspace invites via sendWorkspaceInvitationEmail

Custom metadata fields store workspace descriptions:

// apps/api/src/auth.ts L212-L255
{
  customFields: {
    description: { type: "string", nullable: true }
  },
  requireEmailVerificationOnInvitation: false,
}

The requireEmailVerificationOnInvitation: false setting streamlines onboarding by bypassing email verification for invited users.

Session and Security Controls

Session Management

Sessions are database-backed with a 5-minute cookie cache:

// apps/api/src/auth.ts L336-L341
session: {
  cookieCache: {
    enabled: true,
    maxAge: 5 * 60, // 5 minutes
  },
}

Cookie attributes are hardened for production:

  • sameSite: "none" for cross-subdomain deployments
  • secure: true when HTTPS is available

Rate Limiting

Global and per-endpoint rate limits protect against abuse:

// apps/api/src/auth.ts L496-L506
rateLimit: {
  windowMs: 10000, // 10 seconds
  quota: 100,
  customRules: {
    "/sign-up/email": { quota: 3 },
    "/organization/invite-member": { quota: 10 },
  },
}

Stricter limits apply to registration and invitation endpoints when IS_CLOUD mode is active.

Authentication Hooks

Better Auth's hook system implements Kaneo's custom business logic.

Before Hook

The createAuthMiddleware before hook enforces:

  • Disabled login form checks
  • Guest invitation abuse prevention in cloud mode
  • Disposable email blocking
  • Turnstile verification for suspicious registrations

After Hook

Post-authentication, the after hook attaches the active workspace ID to the session record after successful sign-up or sign-in, ensuring consistent workspace context across requests.

Device Authorization for CLI Tools

The deviceAuthorization plugin enables OAuth-style device flows for command-line clients:

// apps/api/src/auth.ts L280-L283
deviceAuthorization({
  verificationUri: (clientUrl, deviceCode) => 
    `${clientUrl}/verify-device?code=${deviceCode}`,
  clientIds: ["kaneo-cli"],
})

CLI tools initiate flow with:

const device = await auth.api.deviceAuthorization({
  clientId: "kaneo-cli",
});
console.log(`Visit ${device.verificationUri} and enter ${device.userCode}`);

Key Files Reference

File Purpose
apps/api/src/auth.ts Central Better Auth configuration (~600 lines)
apps/api/src/database/schema.ts Drizzle tables for users, sessions, workspaces
apps/api/src/utils/check-registration-allowed.ts Registration policy enforcement
apps/web/src/lib/auth-client.ts Frontend React wrapper
apps/web/src/routes/auth/* Sign-up, sign-in, magic-link UI routes

Summary

  • Better Auth serves as Kaneo's unified authentication engine, configured in apps/api/src/auth.ts
  • Database layer: Drizzle adapter with PostgreSQL, all auth tables in shared workspace schema
  • Human authentication: Email/password, OAuth (GitHub/Google/Discord), magic links, email OTP, anonymous guests
  • Machine authentication: API keys via x-api-key header, bearer tokens, device authorization flow for CLI
  • Workspace mapping: Better Auth organizations mapped to Kaneo workspaces with custom roles and invitations
  • Security: bcrypt password hashing, rate limiting, hardened cookies, registration hooks with disposable email checks

Frequently Asked Questions

How do I authenticate API requests to Kaneo?

Use the x-api-key header with a key generated through the API key plugin. Keys use the kaneo_ prefix by default. Bearer tokens from user sessions are also accepted via the bearer plugin.

Can I disable password registration while keeping OAuth sign-in?

Yes. Set DISABLE_PASSWORD_REGISTRATION=true to block email/password sign-ups while leaving OAuth providers active. The before hook enforces this check in apps/api/src/auth.ts.

How does Kaneo handle the first user created?

The registration flow checks if any users exist. If not, the new user is promoted to instance admin within a PostgreSQL transaction guarded by an advisory lock, preventing race conditions in multi-replica deployments.

What's the difference between workspace invitations and regular sign-up?

Invited users skip email verification (requireEmailVerificationOnInvitation: false) and are directly added to the target workspace. Standard registration may require verification depending on your configuration, and users start without workspace membership.

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 →