How Kaneo API Handles Authentication with Better Auth: A Deep Dive into the Source Code

Kaneo's API uses Better Auth as its single authentication engine, configured in apps/api/src/auth.ts with enterprise-grade features including OAuth, magic links, API keys, and device-code flows for CLI tools.

The open-source project management platform Kaneo delegates all authentication concerns to the Better Auth library. This article examines how the @kaneo/api package implements a comprehensive auth system—from database adapters to rate-limiting—based on the actual source code in the usekaneo/kaneo repository.

Core Architecture: The auth.ts Configuration File

The heart of Kaneo's authentication system resides in apps/api/src/auth.ts. Here, betterAuth() is instantiated with a sophisticated configuration spanning database adapters, social providers, plugins, and security hooks.

Database Integration with Drizzle

Better Auth connects to PostgreSQL through the Drizzle ORM:

// apps/api/src/auth.ts
drizzleAdapter(db, {
  provider: "pg",
  schema: {
    user,
    account,
    session,
    verification,
    workspace, // Kaneo's custom extension
    // ... additional tables
  }
})

All required tables—user, account, session, verification, and Kaneo's custom workspace table—are exported from the shared schema at apps/api/src/database/schema.ts.

User Model Extensions

The default Better Auth user model receives a locale field for internationalized email flows:

// apps/api/src/auth.ts (lines 94-100)
user: {
  additionalFields: {
    locale: {
      type: "string",
      input: true,
    },
  },
}

Account Linking and Trusted Providers

OAuth account linking is enabled for verified emails, restricted to whitelisted providers:

// apps/api/src/auth.ts (lines 102-110)
account: {
  accountLinking: {
    enabled: true,
    trustedProviders: ["github", "google", "discord", "custom"],
  },
}

Authentication Methods in Kaneo

Email and Password Authentication

Password-based authentication uses bcrypt hashing with automatic post-registration sign-in:

// apps/api/src/auth.ts (lines 116-127)
emailAndPassword: {
  enabled: true,
  minPasswordLength: 8,
  maxPasswordLength: 128,
  hash: async (password) => await bcryptHash(password),
  verify: async ({ password, hash }) => await bcryptVerify(password, hash),
  autoSignInAfterRegistration: true,
}

Social Authentication Providers

GitHub, Google, and Discord are configured via environment variables. GitHub credentials are resolved through a helper function:

// apps/api/src/auth.ts (lines 128-140)
socialProviders: {
  github: getGithubSsoOAuthCredentials(),
  google: {
    clientId: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  },
  discord: {
    clientId: process.env.DISCORD_CLIENT_ID,
    clientSecret: process.env.DISCORD_CLIENT_SECRET,
  },
}

The Plugin Ecosystem

Kaneo leverages 11 Better Auth plugins to extend core functionality:

Plugin Purpose Configuration Highlights
anonymous Guest user access Disabled via DISABLE_GUEST_ACCESS env var
lastLoginMethod Audit trail Tracks how users authenticated
magicLink Passwordless email login Uses sendMagicLinkEmail utility
emailOTP One-time passwords Disabled with DISABLE_EMAIL_OTP_SIGN_IN
organization Workspace/team management Maps to Kaneo's workspace tables with custom metadata
genericOAuth Custom OAuth provider Configurable via environment variables
bearer Token-based API access Enables Authorization: Bearer <token>
apiKey Service-to-service auth x-api-key header with optional rate-limiting
deviceAuthorization CLI authentication Device-code flow with whitelisted client IDs
adminPlugin Role-based access defaultRole: "user", adminRoles: ["admin"]
openAPI API documentation Auto-generates spec for auth endpoints

API Key Authentication Implementation

The apiKey plugin at apps/api/src/auth.ts lines 268-278 enables machine-to-machine authentication:

apiKey({
  apiKeyHeaders: ["x-api-key"],
  rateLimit: {
    enabled: true,
    limit: 100,
    window: 60,
  },
})

Device Authorization for CLI Tools

The deviceAuthorization plugin supports secure CLI login with built-in verification URI construction:

// apps/api/src/auth.ts (lines 280-283)
deviceAuthorization({
  clientIds: ["kaneo-cli", "kaneo-desktop"],
  verificationUri: `${clientUrl}/auth/device`,
})

Security Hardening

Rate-Limiting Strategy

A two-tier rate-limiting system protects the auth endpoints:

  • Global limit: 100 requests per 10-second window
  • Cloud-mode restrictions: Tighter limits on /sign-up/email and /organization/invite-member
// apps/api/src/auth.ts (lines 496-506)
rateLimit: {
  defaultLimits: {
    window: 10,
    max: 100,
  },
  customRules: {
    "/sign-up/email": cloudModeLimits,
    "/organization/invite-member": cloudModeLimits,
  },
}

Session Management and Cookies

Sessions are database-backed with 5-minute cookie caching and cross-domain HTTPS support:

// apps/api/src/auth.ts (lines 336-341)
session: {
  cookieCache: {
    enabled: true,
    maxAge: 60 * 5, // 5 minutes
  },
  cookie: {
    sameSite: "none",
    secure: true,
  },
}

Authentication Hooks: Before and After

The before hook (createAuthMiddleware) implements critical security checks:

  • Disabled login form detection
  • Guest invitation abuse prevention in cloud mode
  • Disposable email validation
  • Turnstile CAPTCHA verification

The after hook attaches the active workspace ID to new sessions:

// apps/api/src/auth.ts (lines 600-630)
after: createAuthMiddleware(async (ctx) => {
  if (ctx.context.newSession) {
    await db.update(session)
      .set({ activeWorkspaceId: workspaceId })
      .where(eq(session.id, ctx.context.newSession.id));
  }
})

Workspace and Organization Integration

The organization plugin bridges Better Auth's organization model with Kaneo's workspace concept:

  • Custom fields: Workspace description stored in metadata
  • Invitation flow: Emails sent via sendWorkspaceInvitationEmail
  • Relaxed verification: requireEmailVerificationOnInvitation: false
// apps/api/src/auth.ts (lines 212-255)
organization({
  allowUserToCreateOrganization: true,
  sendInvitationEmail: sendWorkspaceInvitationEmail,
  overrideInvite: true, // Bypasses default invite logic
})

Registration Flow and Instance Administration

The first user created becomes an automatic instance admin through a PostgreSQL advisory-locked transaction:

// apps/api/src/auth.ts (lines 520-595)
before: createAuthMiddleware(async (ctx) => {
  // Registration policy checks
  if (process.env.DISABLE_REGISTRATION) throw forbidden();
  if (process.env.DISABLE_PASSWORD_REGISTRATION && isPasswordFlow) throw forbidden();
  
  // First-user admin promotion with advisory lock
  const result = await db.transaction(async (trx) => {
    await trx.execute("SELECT pg_advisory_xact_lock(1)");
    const userCount = await trx.select({ count: sql<number>`count(*)` }).from(user);
    // ... promote to admin if count === 0
  });
})

Practical Code Examples

Sign Up with Email and Password

// Server-side API call
await auth.api.signUpEmail({
  email: "alice@example.com",
  password: "StrongP@ssw0rd",
});
await auth.api.magicLink({
  email: "bob@example.com",
});

Authenticate with API Key

const response = await fetch("https://api.kaneo.io/api/tasks", {
  headers: { "x-api-key": "kan_live_xxxxxxxxxxxx" },
});

Device Code Flow for CLI

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

Key Files and Their Roles

File Responsibility
apps/api/src/auth.ts Central Better Auth configuration, all plugins, security settings
apps/api/src/database/schema.ts Drizzle schema for auth tables
apps/api/src/utils/check-registration-allowed.ts Registration policy enforcement
apps/api/src/utils/openapi-spec.ts OpenAPI generation for auth endpoints
apps/web/src/lib/auth-client.ts Frontend auth client wrapper
apps/web/src/routes/auth/* Frontend authentication pages

Summary

  • Better Auth is the sole authentication engine in Kaneo's API, configured comprehensively in apps/api/src/auth.ts
  • Database-backed sessions use Drizzle with PostgreSQL, with custom workspace/organization integration
  • Multiple auth methods include email/password, OAuth (GitHub/Google/Discord), magic links, OTP, API keys, and device codes
  • Security layers comprise bcrypt hashing, rate-limiting, disposable email checks, and cloud-mode restrictions
  • Plugin architecture extends core functionality without custom auth code, leveraging Better Auth's ecosystem

Frequently Asked Questions

What authentication methods does Kaneo support?

Kaneo supports email/password, OAuth (GitHub, Google, Discord), magic link, email OTP, anonymous/guest access, API keys, and device authorization for CLI tools. All methods route through Better Auth's unified plugin system in apps/api/src/auth.ts.

How does Kaneo handle user registration restrictions?

Registration is controlled through environment variables (DISABLE_REGISTRATION, DISABLE_PASSWORD_REGISTRATION) and invitation-based flows. The first user is automatically promoted to admin via a PostgreSQL advisory-locked transaction to prevent race conditions.

Can I use API keys for service-to-service authentication?

Yes. The apiKey plugin enables x-api-key header authentication with configurable rate-limiting. API keys can optionally create sessions and are validated against the database with automatic revocation support.

How does Kaneo map Better Auth organizations to its workspace model?

The organization plugin is configured with overrideInvite: true and custom email handlers. Better Auth's organization tables are extended with Kaneo-specific metadata fields, while invitation emails use the sendWorkspaceInvitationEmail utility instead of default templates.

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 →