dist/plugins/api-auth-better-auth-drizzle-hono/skills/api-auth-better-auth-drizzle-hono/SKILL.md
Better Auth patterns, sessions, OAuth
npx skillsauth add agents-inc/skills api-auth-better-auth-drizzle-honoInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Use Better Auth (v1.5+) for type-safe, self-hosted authentication in TypeScript apps. It provides email/password, OAuth, 2FA, sessions, stateless auth, and organization multi-tenancy. Plugin architecture enables progressive complexity. Mount auth handler before session-dependent middleware, configure CORS first for cross-origin deployments, and always run schema generation after adding plugins.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST mount Better Auth handler on the auth route BEFORE any other middleware that depends on session)
(You MUST configure CORS middleware BEFORE auth routes when client and server are on different origins)
(You MUST use environment variables for ALL secrets (clientId, clientSecret, BETTER_AUTH_SECRET) - NEVER hardcode)
(You MUST run npx auth@latest generate then your ORM migration tool after adding plugins)
(You MUST use auth.$Infer.Session types for type-safe session access in middleware)
</critical_requirements>
Auto-detection: Better Auth, betterAuth, createAuthClient, auth.handler, auth.api.getSession, socialProviders, twoFactor plugin, organization plugin, drizzleAdapter, session management, OAuth providers, stateless sessions, cookieCache, genericOAuth, oAuthProvider, passkey, SCIM
When to use:
When NOT to use:
Key patterns covered:
Detailed Resources:
Better Auth follows a TypeScript-first, self-hosted approach to authentication. Your user data stays in your database, with no vendor lock-in. The plugin architecture enables progressive complexity - start simple and add features as needed.
Core principles:
auth.$Infer.SessionCreate the auth instance with database adapter. Single source of truth for all authentication config.
// lib/auth.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/lib/db";
const SESSION_EXPIRES_IN_SECONDS = 60 * 60 * 24 * 7; // 7 days
const SESSION_UPDATE_AGE_SECONDS = 60 * 60 * 24; // Refresh daily
export const auth = betterAuth({
database: drizzleAdapter(db, { provider: "pg" }),
emailAndPassword: {
enabled: true,
minPasswordLength: 8,
maxPasswordLength: 128,
},
session: {
expiresIn: SESSION_EXPIRES_IN_SECONDS,
updateAge: SESSION_UPDATE_AGE_SECONDS,
},
trustedOrigins: [process.env.APP_URL || "http://localhost:3000"],
});
Why good: Named constants make session policy auditable, env vars for URLs, single exported instance
// BAD: Magic numbers, hardcoded secrets, default export
const auth = betterAuth({
database: { url: "postgres://user:pass@localhost/db" },
session: { expiresIn: 604800 },
});
export default auth;
Why bad: Hardcoded credentials leak in source control, magic numbers obscure policy, default export
See examples/core.md for full setup with email verification and Drizzle adapter configuration.
Mount auth handler and create typed middleware for session access in routes.
// CRITICAL: CORS must be configured BEFORE auth routes
app.use("/auth/*", cors({ origin: APP_URL, credentials: true }));
app.on(["POST", "GET"], "/auth/*", (c) => auth.handler(c.req.raw));
// middleware/auth-middleware.ts - Type-safe session access
type AuthVariables = {
user: typeof auth.$Infer.Session.user | null;
session: typeof auth.$Infer.Session.session | null;
};
export const authMiddleware = createMiddleware<{ Variables: AuthVariables }>(
async (c, next) => {
const session = await auth.api.getSession({ headers: c.req.raw.headers });
c.set("user", session?.user ?? null);
c.set("session", session?.session ?? null);
await next();
},
);
Why good: auth.$Infer.Session ensures c.get("user") is correctly typed, CORS before auth prevents preflight failures, c.req.raw provides the Web Standard Request that Better Auth expects
// BAD: No type annotation - c.user is any, bypasses type system
app.use("*", async (c, next) => {
const session = await auth.api.getSession({ headers: c.req.raw.headers });
c.user = session?.user; // any - no autocomplete
await next();
});
Why bad: No AuthVariables type = any access, direct property assignment bypasses typed Variables
See examples/core.md for protected route patterns.
Every plugin adds database tables. Run the CLI after adding or modifying plugins:
# Step 1: Generate Better Auth schema (outputs ORM-specific files)
npx auth@latest generate
# Step 2: Generate migration with your ORM tool
npx drizzle-kit generate
# Step 3: Apply migration
npx drizzle-kit migrate
Always run all 3 steps. The Better Auth migrate command only works with the Kysely adapter - for Drizzle, use generate + Drizzle Kit.
Configure email/password auth with verification and password reset callbacks.
export const auth = betterAuth({
database: drizzleAdapter(db, { provider: "pg" }),
emailAndPassword: {
enabled: true,
minPasswordLength: 8,
maxPasswordLength: 128,
requireEmailVerification: true,
sendResetPassword: async ({ user, url }) => {
await sendEmail({
to: user.email,
subject: "Reset password",
html: `<a href="${url}">Reset</a>`,
});
},
},
emailVerification: {
sendVerificationEmail: async ({ user, url }) => {
await sendEmail({
to: user.email,
subject: "Verify email",
html: `<a href="${url}">Verify</a>`,
});
},
},
});
Why good: Email verification prevents fake signups, password requirements enforced server-side
See examples/core.md for client-side sign up/in hooks with error handling.
Three session approaches with different trade-offs:
| Strategy | DB Required | Revocable | Best For | | ------------------ | ----------- | ----------------- | --------------- | | Database (default) | Yes | Yes | Most apps | | Cookie cache + DB | Yes | Yes (delayed) | Reduce DB load | | Stateless | No | No (version-only) | Edge/serverless |
// Cookie cache: reduces DB hits by caching session in signed cookie
session: {
cookieCache: { enabled: true, maxAge: CACHE_SECONDS, strategy: "compact" },
}
// Stateless: omit database option entirely
const auth = betterAuth({
// No database = fully stateless
session: { cookieCache: { enabled: true, strategy: "jwe" } },
});
Cookie cache strategies: compact (smallest, internal), jwt (standard, third-party verifiable), jwe (encrypted, hides data).
See examples/sessions.md for full configuration and revocation patterns.
</patterns><red_flags>
BETTER_AUTH_SECRET env var - sessions will not workc.user becomes anyauth.migrate() with Drizzle adapter - only works with Kysely, use generate + Drizzle Kitc.req.raw when calling auth.handler() - must pass the raw Web Standard RequestGotchas & Edge Cases:
accessType: "offline" and prompt: "consent"version to invalidate allmaxAge expires on other devicesSameSite=None + Secure for cross-domain deploymentsauthClient.forgotPassword was renamed to authClient.requestPasswordReset in v1.4InferUser/InferSession removed in v1.5 - use generic User and Session types from better-authSee reference.md for anti-patterns with code examples, decision frameworks, and version notes.
</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST mount Better Auth handler on the auth route BEFORE any other middleware that depends on session)
(You MUST configure CORS middleware BEFORE auth routes when client and server are on different origins)
(You MUST use environment variables for ALL secrets (clientId, clientSecret, BETTER_AUTH_SECRET) - NEVER hardcode)
(You MUST run npx auth@latest generate then your ORM migration tool after adding plugins)
(You MUST use auth.$Infer.Session types for type-safe session access in middleware)
Failure to follow these rules will cause authentication failures, security vulnerabilities, or runtime errors.
</critical_reminders>
development
Material Design component library for Vue 3
development
VitePress 1.x — Vue-powered static site generator for documentation sites, built on Vite
tools
Docusaurus 3.x documentation framework — site configuration, docs/blog plugins, sidebars, versioning, MDX, swizzling, and deployment
development
TanStack Form patterns - useForm, form.Field, validators, arrays, linked fields, createFormHook, type safety