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

> Discover how Kaneo's authentication system leverages Better Auth for robust security. Explore support for email, OAuth, magic links, bearer tokens, and API keys.

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

---

**Kaneo uses the Better Auth library as its single source of truth for authentication, configured in [`apps/api/src/auth.ts`](https://github.com/usekaneo/kaneo/blob/main/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`](https://github.com/usekaneo/kaneo/blob/main/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:

```typescript
// 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`](https://github.com/usekaneo/kaneo/blob/main/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:

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

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

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

### Magic Links and Email OTP

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:

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

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

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

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

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

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

```

CLI tools initiate flow with:

```typescript
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`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/auth.ts) | Central Better Auth configuration (~600 lines) |
| [`apps/api/src/database/schema.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/database/schema.ts) | Drizzle tables for users, sessions, workspaces |
| [`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/web/src/lib/auth-client.ts`](https://github.com/usekaneo/kaneo/blob/main/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`](https://github.com/usekaneo/kaneo/blob/main/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`](https://github.com/usekaneo/kaneo/blob/main/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.