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

> Explore how Kaneo API leverages Better Auth for robust authentication. Discover OAuth, magic links, API keys, and device-code flows by diving into the source code.

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

---

**Kaneo's API uses Better Auth as its single authentication engine, configured in [`apps/api/src/auth.ts`](https://github.com/usekaneo/kaneo/blob/main/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`](https://github.com/usekaneo/kaneo/blob/main/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:

```typescript
// 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`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/database/schema.ts).

### User Model Extensions

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

```typescript
// 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:

```typescript
// 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:

```typescript
// 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:

```typescript
// 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`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/auth.ts) lines 268-278 enables machine-to-machine authentication:

```typescript
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:

```typescript
// 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`

```typescript
// 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:

```typescript
// 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:

```typescript
// 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`

```typescript
// 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:

```typescript
// 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

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

```

### Initiate Magic Link Login

```typescript
await auth.api.magicLink({
  email: "bob@example.com",
});

```

### Authenticate with API Key

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

```

### Device Code Flow for CLI

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

```

### Link GitHub OAuth Account

```typescript
await auth.api.socialSignIn({
  provider: "github",
  code: "<github-auth-code>",
});

```

## Key Files and Their Roles

| File | Responsibility |
|------|---------------|
| [`apps/api/src/auth.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/auth.ts) | Central Better Auth configuration, all plugins, security settings |
| [`apps/api/src/database/schema.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/database/schema.ts) | Drizzle schema for auth tables |
| [`apps/api/src/utils/check-registration-allowed.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/utils/check-registration-allowed.ts) | Registration policy enforcement |
| [`apps/api/src/utils/openapi-spec.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/utils/openapi-spec.ts) | OpenAPI generation for auth endpoints |
| [`apps/web/src/lib/auth-client.ts`](https://github.com/usekaneo/kaneo/blob/main/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`](https://github.com/usekaneo/kaneo/blob/main/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`](https://github.com/usekaneo/kaneo/blob/main/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.