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/emailand/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",
});
Initiate Magic Link Login
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}`);
Link GitHub OAuth Account
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →