| name | grey-haven-authentication-patterns |
| description | Grey Haven's authentication patterns using better-auth - magic links, passkeys, OAuth providers, session management with Redis, JWT claims with tenant_id, and Doppler for auth secrets. Use when implementing authentication features. |
| skills | ["grey-haven-code-style","grey-haven-security-practices","grey-haven-api-design-standards"] |
| allowed-tools | ["Read","Write","MultiEdit","Bash","Grep","Glob","TodoWrite"] |
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(),
email_address: text("email_address").notNull().unique(),
});
export const sessions = pgTable("sessions", {
id: uuid("id").primaryKey().defaultRandom(),
user_id: uuid("user_id").references(() => users.id),
tenant_id: uuid("tenant_id").notNull(),
});
Doppler for Secrets
NEVER commit auth secrets:
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
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
await auth.signUp.email({
email: "user@example.com",
password: "secure-password",
name: "John Doe",
data: {
tenant_id: tenantId,
},
});
await auth.signIn.email({
email: "user@example.com",
password: "secure-password",
});
2. Magic Links
await auth.magicLink.send({
email: "user@example.com",
callbackURL: "/auth/verify",
});
await auth.magicLink.verify({
token: tokenFromEmail,
});
3. OAuth Providers
export const auth = betterAuth({
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
scopes: ["email", "profile"],
},
},
});
await auth.signIn.social({
provider: "google",
callbackURL: "/auth/callback",
});
4. Passkeys (WebAuthn)
export const auth = betterAuth({
passkey: {
enabled: true,
},
});
await auth.passkey.register({
name: "My MacBook",
});
await auth.passkey.authenticate();
Session Management
JWT Claims with tenant_id
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,
user: session.user,
};
}
Session Storage with Redis
export const auth = betterAuth({
session: {
expiresIn: 60 * 60 * 24 * 7,
updateAge: 60 * 60 * 24,
cookieCache: {
enabled: true,
maxAge: 5 * 60,
},
},
});
Protected Routes
TanStack Router beforeLoad
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:
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