grey-haven-authentication-patterns
About
This skill provides Grey Haven's authentication patterns using better-auth for TanStack Start projects. It implements multi-tenant authentication with magic links, passkeys, OAuth providers, Redis session management, and JWT claims including tenant_id. Use this when implementing authentication features in multi-tenant applications.
Quick Install
Claude Code
Recommended/plugin add https://github.com/greyhaven-ai/claude-code-configgit clone https://github.com/greyhaven-ai/claude-code-config.git ~/.claude/skills/grey-haven-authentication-patternsCopy and paste this command in Claude Code to install this skill
Documentation
Grey Haven Authentication Patterns
Follow Grey Haven Studio's authentication patterns using better-auth for TanStack Start projects with multi-tenant support.
Stack
- better-auth: Authentication library for TanStack Start
- Drizzle ORM: Database adapter for better-auth
- Doppler: Secret management (BETTER_AUTH_SECRET, OAuth keys)
- Redis: Session storage (via Upstash)
- PostgreSQL: User and session data with RLS
Critical Requirements
Multi-Tenant Authentication
ALWAYS include tenant_id in auth tables:
export const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
tenant_id: uuid("tenant_id").notNull(), // CRITICAL!
email_address: text("email_address").notNull().unique(),
// ... other fields
});
export const sessions = pgTable("sessions", {
id: uuid("id").primaryKey().defaultRandom(),
user_id: uuid("user_id").references(() => users.id),
tenant_id: uuid("tenant_id").notNull(), // CRITICAL!
// ... other fields
});
Doppler for Secrets
NEVER commit auth secrets:
# Doppler provides these at runtime
BETTER_AUTH_SECRET=<generated-secret>
BETTER_AUTH_URL=https://app.example.com
GOOGLE_CLIENT_ID=<from-google-console>
GOOGLE_CLIENT_SECRET=<from-google-console>
Basic Configuration
// lib/server/auth.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "@better-auth/drizzle";
import { db } from "~/lib/server/db";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
schema,
}),
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
},
secret: process.env.BETTER_AUTH_SECRET!,
baseURL: process.env.BETTER_AUTH_URL!,
trustedOrigins: [process.env.BETTER_AUTH_URL!],
});
Authentication Methods
1. Email & Password
// Sign up with email verification
await auth.signUp.email({
email: "user@example.com",
password: "secure-password",
name: "John Doe",
data: {
tenant_id: tenantId, // Include tenant context
},
});
// Sign in
await auth.signIn.email({
email: "user@example.com",
password: "secure-password",
});
2. Magic Links
// Send magic link
await auth.magicLink.send({
email: "user@example.com",
callbackURL: "/auth/verify",
});
// Verify magic link token
await auth.magicLink.verify({
token: tokenFromEmail,
});
3. OAuth Providers
// Google OAuth
export const auth = betterAuth({
// ... other config
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
scopes: ["email", "profile"],
},
},
});
// Redirect to Google
await auth.signIn.social({
provider: "google",
callbackURL: "/auth/callback",
});
4. Passkeys (WebAuthn)
// Enable passkeys
export const auth = betterAuth({
// ... other config
passkey: {
enabled: true,
},
});
// Register passkey
await auth.passkey.register({
name: "My MacBook",
});
// Authenticate with passkey
await auth.passkey.authenticate();
Session Management
JWT Claims with tenant_id
// Middleware to extract tenant from JWT
export async function getTenantFromSession() {
const session = await auth.api.getSession();
if (!session) {
throw new Error("Not authenticated");
}
return {
userId: session.user.id,
tenantId: session.user.tenant_id, // From JWT claims
user: session.user,
};
}
Session Storage with Redis
// Use Upstash Redis for sessions
export const auth = betterAuth({
// ... other config
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // Refresh daily
cookieCache: {
enabled: true,
maxAge: 5 * 60, // 5 minutes
},
},
});
Protected Routes
TanStack Router beforeLoad
// routes/_authenticated/_layout.tsx
import { createFileRoute, redirect } from "@tanstack/react-router";
import { getTenantFromSession } from "~/lib/server/auth";
export const Route = createFileRoute("/_authenticated/_layout")({
beforeLoad: async () => {
try {
const { userId, tenantId, user } = await getTenantFromSession();
return { session: { userId, tenantId, user } };
} catch {
throw redirect({
to: "/auth/login",
search: { redirect: location.href },
});
}
},
});
Supporting Documentation
All supporting files are under 500 lines per Anthropic best practices:
-
examples/ - Complete auth examples
- magic-link.md - Magic link implementation
- oauth.md - OAuth provider setup
- passkeys.md - Passkey authentication
- multi-tenant.md - Multi-tenant patterns
- INDEX.md - Examples navigation
-
reference/ - Auth references
- better-auth-config.md - Configuration options
- session-management.md - Session patterns
- doppler-setup.md - Secret management
- INDEX.md - Reference navigation
-
templates/ - Copy-paste ready templates
- auth-config.ts - better-auth configuration
- auth-schema.ts - Drizzle auth schema
- protected-route.tsx - Protected route layout
-
checklists/ - Security checklists
- auth-checklist.md - Authentication security
When to Apply This Skill
Use this skill when:
- Implementing user authentication
- Adding OAuth providers (Google, GitHub)
- Setting up magic link authentication
- Configuring passkey support
- Managing user sessions
- Implementing multi-tenant auth
- Securing API endpoints
- Setting up protected routes
Template Reference
These patterns are from Grey Haven's production templates:
- cvi-template: TanStack Start + better-auth + multi-tenant
Critical Reminders
- tenant_id: Always include in users and sessions tables
- Doppler: Use for all auth secrets (never commit!)
- Email verification: Required for email/password signup
- JWT claims: Include tenant_id in session data
- Protected routes: Use beforeLoad for auth checks
- Redis sessions: Use Upstash for distributed sessions
- OAuth secrets: Store in Doppler (Google, GitHub, etc.)
- RLS policies: Create for users and sessions tables
- Session expiry: 7 days default, refresh daily
- Magic links: 15-minute expiry, single-use tokens
GitHub Repository
Related Skills
evaluating-llms-harness
TestingThis Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.
sglang
MetaSGLang is a high-performance LLM serving framework that specializes in fast, structured generation for JSON, regex, and agentic workflows using its RadixAttention prefix caching. It delivers significantly faster inference, especially for tasks with repeated prefixes, making it ideal for complex, structured outputs and multi-turn conversations. Choose SGLang over alternatives like vLLM when you need constrained decoding or are building applications with extensive prefix sharing.
langchain
MetaLangChain is a framework for building LLM applications using agents, chains, and RAG pipelines. It supports multiple LLM providers, offers 500+ integrations, and includes features like tool calling and memory management. Use it for rapid prototyping and deploying production systems like chatbots, autonomous agents, and question-answering services.
cloudflare-turnstile
MetaThis skill provides comprehensive guidance for implementing Cloudflare Turnstile as a CAPTCHA-alternative bot protection system. It covers integration for forms, login pages, API endpoints, and frameworks like React/Next.js/Hono, while handling invisible challenges that maintain user experience. Use it when migrating from reCAPTCHA, debugging error codes, or implementing token validation and E2E tests.
