Files
gh-greyhaven-ai-claude-code…/skills/authentication-patterns/SKILL.md
2025-11-29 18:29:28 +08:00

6.9 KiB

name, description
name description
grey-haven-authentication-patterns 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.

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",
});
// 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:

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

  1. tenant_id: Always include in users and sessions tables
  2. Doppler: Use for all auth secrets (never commit!)
  3. Email verification: Required for email/password signup
  4. JWT claims: Include tenant_id in session data
  5. Protected routes: Use beforeLoad for auth checks
  6. Redis sessions: Use Upstash for distributed sessions
  7. OAuth secrets: Store in Doppler (Google, GitHub, etc.)
  8. RLS policies: Create for users and sessions tables
  9. Session expiry: 7 days default, refresh daily
  10. Magic links: 15-minute expiry, single-use tokens