AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Api Auth Better Auth Drizzle Hono

skill-agents-inc-skills-api-auth-better-auth-drizzle-hono · by agents-inc

Better Auth patterns, sessions, OAuth

No reviews yet
0 installs
36 views
0.0% view→install

Install

$ agentstack add skill-agents-inc-skills-api-auth-better-auth-drizzle-hono

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-agents-inc-skills-api-auth-better-auth-drizzle-hono)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
5mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Api Auth Better Auth Drizzle Hono? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Authentication with Better Auth

> 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: Before Using This Skill

> 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, BETTERAUTHSECRET) - 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)


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:

  • Building self-hosted authentication (no vendor lock-in)
  • Need email/password + OAuth + 2FA in one solution
  • Multi-tenant SaaS with organization/team management
  • Type-safe session management
  • Projects requiring database-stored or stateless sessions

When NOT to use:

  • Need managed authentication with zero maintenance (consider hosted auth solutions)
  • Simple static sites without user accounts
  • Projects where serverless cold starts are critical (though stateless mode helps)

Key patterns covered:

  • Server configuration (auth.ts) with plugins
  • Session middleware and type-safe route protection
  • Email/password authentication flows
  • OAuth providers (GitHub, Google, Generic OAuth)
  • Two-factor authentication (TOTP)
  • Organization and multi-tenancy
  • Session strategies: database, cookie cache, stateless
  • Database adapter integration
  • Client-side useSession hook
  • Performance: experimental joins, cookie caching, stateless sessions

Detailed Resources:

  • [examples/core.md](examples/core.md) - Sign up, sign in, client setup, database adapter
  • [examples/oauth.md](examples/oauth.md) - GitHub, Google, Generic OAuth providers
  • [examples/two-factor.md](examples/two-factor.md) - TOTP setup, enable, verify
  • [examples/organizations.md](examples/organizations.md) - Multi-tenancy, invitations
  • [examples/sessions.md](examples/sessions.md) - Session config, cookie caching, stateless
  • [reference.md](reference.md) - Decision frameworks, anti-patterns, version notes

Philosophy

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:

  1. Type safety throughout - Session types flow from server to client via auth.$Infer.Session
  2. Database as source of truth - Sessions stored in your DB (with optional stateless mode)
  3. Plugin-based extensibility - Add 2FA, organizations, passkeys, SCIM, OAuth provider when needed
  4. Framework-agnostic - Works with any TypeScript web framework
  5. Performance-focused - Experimental joins (2-3x faster), cookie caching, stateless sessions

Core Patterns

Pattern 1: Server Configuration (auth.ts)

Create 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](examples/core.md) for full setup with email verification and Drizzle adapter configuration.


Pattern 2: Session Middleware with Type Safety

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(
  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](examples/core.md) for protected route patterns.


Pattern 3: Schema Generation After Plugins

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.


Pattern 4: Email/Password with Verification

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: `Reset`,
      });
    },
  },
  emailVerification: {
    sendVerificationEmail: async ({ user, url }) => {
      await sendEmail({
        to: user.email,
        subject: "Verify email",
        html: `Verify`,
      });
    },
  },
});

Why good: Email verification prevents fake signups, password requirements enforced server-side

See [examples/core.md](examples/core.md) for client-side sign up/in hooks with error handling.


Pattern 5: Session Strategies

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](examples/sessions.md) for full configuration and revocation patterns.


RED FLAGS

  • Hardcoded secrets (clientId/clientSecret in source) - must use environment variables
  • CORS configured after auth routes - preflight requests will fail
  • Missing BETTER_AUTH_SECRET env var - sessions will not work
  • No schema generation after adding plugins - database errors at runtime
  • Untyped session middleware - loses TypeScript safety, c.user becomes any
  • Using auth.migrate() with Drizzle adapter - only works with Kysely, use generate + Drizzle Kit
  • Missing c.req.raw when calling auth.handler() - must pass the raw Web Standard Request

Gotchas & Edge Cases:

  • Google only issues refresh tokens on first consent - use accessType: "offline" and prompt: "consent"
  • GitHub OAuth apps don't issue refresh tokens (access tokens are long-lived)
  • Stateless sessions cannot be revoked individually - increment version to invalidate all
  • Cookie cache revocation is delayed until maxAge expires on other devices
  • Session cookies need SameSite=None + Secure for cross-domain deployments
  • authClient.forgotPassword was renamed to authClient.requestPasswordReset in v1.4
  • InferUser/InferSession removed in v1.5 - use generic User and Session types from better-auth

See [reference.md](reference.md) for anti-patterns with code examples, decision frameworks, and version notes.


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, BETTERAUTHSECRET) - 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.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.