/**
* better-auth Configuration for MCP Server
*
* Factory pattern for Cloudflare Workers - env bindings not available at module level
*
* Features:
* - Social login (Google, Microsoft, GitHub)
* - OIDC Provider plugin for MCP client authentication
* - Admin plugin for user management
* - API Key plugin for programmatic access
*/
import { betterAuth } from 'better-auth';
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
import {
oidcProvider,
admin,
apiKey as apiKeyPlugin,
jwt,
} from 'better-auth/plugins';
import { createDatabase } from './db';
import {
user,
session,
account,
verification,
oauthClient,
oauthAccessToken,
oauthRefreshToken,
oauthConsent,
jwks,
apiKey as apiKeyTable,
} from './db/schema';
import type { Env } from '../types';
/**
* Create a better-auth instance configured for this environment
*
* @param env - Cloudflare Worker environment bindings
* @returns Configured better-auth instance
*/
export function createAuth(env: Env) {
// Create Drizzle database from D1 binding
const db = createDatabase(env.DB!);
return betterAuth({
// Required secret for encryption
secret: env.BETTER_AUTH_SECRET,
// Base URL for OAuth callbacks
baseURL: env.BETTER_AUTH_URL,
// Database adapter (Drizzle + D1)
// Schema keys must be lowercase to match better-auth's internal model names
database: drizzleAdapter(db, {
provider: 'sqlite',
schema: {
user,
session,
account,
verification,
jwks,
// OIDC Provider tables
oauthClient,
oauthAccessToken,
oauthRefreshToken,
oauthConsent,
// API Key plugin - MUST be lowercase "apikey"
apikey: apiKeyTable,
},
}),
// ═══════════════════════════════════════════════════════════════════
// SOCIAL PROVIDERS (Layer 1 Identity)
// ═══════════════════════════════════════════════════════════════════
socialProviders: {
google: {
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
// Include calendar scope for API access (not just identity)
scope: [
'openid',
'email',
'profile',
'https://www.googleapis.com/auth/calendar',
],
// Request refresh token for long-lived sessions
accessType: 'offline',
prompt: 'consent',
},
microsoft: {
clientId: env.MICROSOFT_CLIENT_ID || '',
clientSecret: env.MICROSOFT_CLIENT_SECRET || '',
tenantId: env.MICROSOFT_TENANT_ID || 'common',
scope: ['openid', 'email', 'profile', 'User.Read'],
},
github: {
clientId: env.GITHUB_CLIENT_ID || '',
clientSecret: env.GITHUB_CLIENT_SECRET || '',
scope: ['user:email', 'read:user'],
},
},
// ═══════════════════════════════════════════════════════════════════
// PLUGINS
// ═══════════════════════════════════════════════════════════════════
plugins: [
// JWT (required for OAuth Provider)
jwt(),
// OIDC Provider for MCP clients (Claude.ai, Claude Code, etc.)
oidcProvider({
// Required for MCP clients to self-register
allowDynamicClientRegistration: true,
// Custom pages
loginPage: '/login',
consentPage: '/consent',
// Token configuration
accessTokenExpiresIn: 3600, // 1 hour
refreshTokenExpiresIn: 604800, // 7 days
}),
// Admin features (user management, ban, impersonate)
admin({
defaultRole: 'user',
adminRole: 'admin',
}),
// API key authentication for programmatic access
apiKeyPlugin({
// Rate limiting per key
rateLimit: {
timeWindow: 60, // seconds
maxRequests: 100, // requests per window
},
}),
],
// ═══════════════════════════════════════════════════════════════════
// SESSION CONFIGURATION
// ═══════════════════════════════════════════════════════════════════
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // Update session every 24 hours
cookieCache: {
enabled: true,
maxAge: 60 * 5, // 5 minutes
},
},
// ═══════════════════════════════════════════════════════════════════
// ADVANCED OPTIONS
// ═══════════════════════════════════════════════════════════════════
advanced: {
useSecureCookies: true,
crossSubDomainCookies: {
enabled: false,
},
},
});
}
// Type export for use in route handlers
export type Auth = ReturnType<typeof createAuth>;