.claude/skills/better-auth/SKILL.md
Authentication and session management with Better Auth in LivestockAI
npx skillsauth add captjay98/gemini-livestockai Better AuthInstall 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.
LivestockAI uses Better Auth for authentication. It provides secure session management with email/password authentication.
Better Auth uses two tables:
| Table | Purpose |
| --------- | --------------------------------------------------- |
| users | User profile data (name, email, role) - NO password |
| account | Authentication credentials (password, providerId) |
Important: Passwords are stored in account, not users.
ALWAYS use the createUserWithAuth helper:
import { createUserWithAuth } from '~/lib/db/seeds/helpers'
const result = await createUserWithAuth(db, {
email: '[email protected]',
password: 'securepassword',
name: 'John Doe',
role: 'user', // or 'admin'
})
This helper:
users tableaccount table with providerId: 'credential'// ❌ WRONG - users table has no password field!
await db
.insertInto('users')
.values({
email: '[email protected]',
password: 'hashedpassword', // This field doesn't exist!
})
.execute()
The auth config is in app/features/auth/config.ts:
import { betterAuth } from 'better-auth'
export const auth = betterAuth({
database: {
type: 'postgres',
url: process.env.DATABASE_URL,
},
emailAndPassword: {
enabled: true,
},
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // 1 day
},
})
Use requireAuth() in server functions:
export const myServerFn = createServerFn({ method: 'GET' }).handler(
async () => {
const { requireAuth } = await import('../auth/server-middleware')
const session = await requireAuth()
// session.user contains:
// - id: string
// - email: string
// - name: string
// - role: 'user' | 'admin'
return { userId: session.user.id }
},
)
// app/features/auth/utils.ts
// Check if user has access to a farm
export async function checkFarmAccess(
userId: string,
farmId: string,
): Promise<boolean>
// Get all farms a user has access to
export async function getUserFarms(userId: string): Promise<string[]>
import { useSession, signIn, signOut } from '~/features/auth/client'
function LoginButton() {
const { data: session, isLoading } = useSession()
if (isLoading) return <Spinner />
if (session) {
return (
<Button onClick={() => signOut()}>
Sign Out ({session.user.email})
</Button>
)
}
return (
<Button onClick={() => signIn('credential', { email, password })}>
Sign In
</Button>
)
}
The _auth.tsx layout protects routes:
// app/routes/_auth.tsx
export const Route = createFileRoute('/_auth')({
beforeLoad: async () => {
const session = await getSession()
if (!session) {
throw redirect({ to: '/login' })
}
return { session }
},
})
LivestockAI supports roles:
| Role | Access |
| ----------------- | -------------------- |
| user | Standard farm access |
| admin | Full system access |
| extension_agent | District-level view |
// Check role in server function
const session = await requireAuth()
if (session.user.role !== 'admin') {
throw new AppError('ACCESS_DENIED')
}
The session object contains:
interface Session {
user: {
id: string
email: string
name: string
role: 'user' | 'admin' | 'extension_agent'
image?: string
}
expires: Date
}
three-layer-architecture - Auth in server layererror-handling - Auth error handlingtanstack-start - Server function patternsdata-ai
Input validation patterns with Zod in LivestockAI server functions
testing
Unit testing patterns with Vitest in LivestockAI
tools
Server → Service → Repository pattern for feature organization
data-ai
Server-side rendering and server functions with TanStack Start in LivestockAI