AI Patterns
LLM-optimized authentication with BetterAuth
BetterAuth provides first-party LLM compatibility through its official llms.txt. Combined with StarterApp context engineering in llms/AUTH_PATTERNS.md
, AI assistants implement authentication requirements correctly on first attempt.
Layered Context Engineering
The authentication system combines BetterAuth official documentation, codebase-specific patterns in llms/AUTH_PATTERNS.md, working templates in llms/templates/, and security requirements in llms/SECURITY.md. This layered approach enables accurate AI-assisted development.
Context Engineering Strategy
The authentication system uses layered context engineering to enable accurate AI-assisted development:
BetterAuth Official Context
BetterAuth maintains an official llms.txt
at https://www.better-auth.com/llms.txt with:
- Complete API reference
- Provider configuration patterns
- Session management details
- Plugin usage examples
Codebase-Specific Patterns
llms/AUTH_PATTERNS.md
documents implementation patterns specific to this stack:
# AUTH_PATTERNS.md — BetterAuth + Convex Quick Reference
**Stack**: BetterAuth → Convex → Next.js 15 (no HTTP to auth, use `getToken`)
## Core Rules
* **Server data**: `getToken(createAuth) → fetchQuery(...)` (never HTTP fetch)
* **Protected pages**: Export cache flags (see below)
* **Import paths**: `createAuth` from `convex/lib/auth` (never `convex/http.ts`)
* **Identity**: BetterAuth `userId` maps directly (no complex resolution)
* **Convex identity helpers**: Import via `convex/lib/identity` adapter
* `getUserIdOrThrow` throws `AppError('UNAUTHENTICATED')` on failure
* `assertOwnerByUserId(record, userId, { concealExistence })` hides existence with `NOT_FOUND`
Working Templates
Template files in llms/templates/
provide complete, copy-paste patterns:
auth-api-route-template.ts
- Protected API endpoint with CSRFauth-convex-functions-template.ts
- Authenticated Convex functionsauth-page-template.tsx
- Protected page componentauth-server-component-template.tsx
- Server component patterns
Security Requirements
llms/SECURITY.md
enforces security patterns that AI must follow:
- CSRF protection for mutations
- Secure response headers
- Cache configuration for auth routes
- Error handling without information leakage
AI Capabilities
With comprehensive context, AI assistants accurately implement:
OAuth Provider Setup
"Add Google OAuth to the authentication system"
"Configure GitHub provider for sign-in"
"Enable Microsoft Azure AD authentication"
The AI reads convex/lib/auth.ts
to understand the computeSocialProviders
function and adds providers correctly:
export function computeSocialProviders(env: Record<string, string | undefined>) {
const id = env.GOOGLE_CLIENT_ID?.trim();
const secret = env.GOOGLE_CLIENT_SECRET?.trim();
if (id && secret) {
return {
google: {
clientId: id,
clientSecret: secret,
},
};
}
return;
}
Protected Route Implementation
"Protect the dashboard layout with authentication"
"Add authentication to the billing page"
"Require sign-in for all routes under /app"
The AI references apps/dashboard/app/(dashboard)/layout.tsx
and implements the pattern:
export const revalidate = 0;
export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";
export default async function ProtectedLayout({ children }: PropsWithChildren) {
const session = await getCurrentSession();
if (!session) redirect("/sign-in");
// Layout implementation
}
API Route Protection
"Create a protected API endpoint for user settings"
"Add authentication and CSRF protection to POST /api/support"
"Implement authenticated webhook handler"
The AI follows llms/templates/auth-api-route-template.ts
:
export const runtime = "nodejs";
export const revalidate = 0;
export const dynamic = "force-dynamic";
export async function POST(req: NextRequest) {
try {
assertOrigin(req);
assertFetchMetadata(req);
const session = await auth.api.getSession({ headers: await headers() });
if (!session?.user?.id) {
return secureErrorJson(
{ message: "Authentication required", code: "Unauthorized" },
{ status: 401, userScoped: true }
);
}
// Implementation
} catch (error) {
return secureErrorJson(
{ message: "Internal server error", code: "Internal" },
{ status: 500, userScoped: true }
);
}
}
Convex Function Protection
"Protect the support ticket query with user authentication"
"Add ownership validation to the document mutation"
"Create authenticated action for premium exports"
The AI uses patterns from llms/templates/auth-convex-functions-template.ts
:
import { getUserIdOrThrow, assertOwnerByUserId } from "./lib/identity";
export const getPrivateNote = query({
args: { noteId: v.id("notes") },
handler: async (ctx, { noteId }) => {
const userId = await getUserIdOrThrow(ctx);
const note = await ctx.db.get(noteId);
assertOwnerByUserId(note, userId, { concealExistence: true });
return note;
},
});
Context File Details
Implementation Flow
When an AI assistant receives an authentication task, it follows this flow:
Read Context Files
- Parse
llms/AUTH_PATTERNS.md
for patterns - Review relevant template files
- Check
llms/SECURITY.md
for requirements
Identify Pattern
Match the request to the correct pattern:
- Server component →
requireUser()
orgetCurrentSession()
- API route →
auth.api.getSession()
with CSRF checks - Convex function →
getUserIdOrThrow()
with identity helpers - Client component →
useSession()
oruseQuery(api.auth.getCurrentUser)
Apply Template
Copy the appropriate template and adapt to the specific requirement:
- Update types and schemas
- Adjust error messages
- Customize business logic
- Maintain security patterns
Validate Implementation
Ensure the implementation includes:
- Required cache exports for protected routes
- CSRF protection for mutations
- Proper error handling with
secureErrorJson
- Type safety with Zod validation
- Security headers on responses
Testing AI Understanding
Verify AI comprehension with these prompts:
Session Access:
"Get the current user session in a server component"
"Add a sign-out button to the navigation"
"Show user profile in the dashboard sidebar"
Expected: Correct import paths, proper null handling, loading states
OAuth Configuration:
"Add GitHub OAuth provider to authentication"
"Configure Microsoft Azure AD for enterprise sign-in"
"Enable magic link authentication"
Expected: Environment variable setup, computeSocialProviders
modification, BetterAuth configuration
Protected Endpoints:
"Create authenticated API endpoint for support tickets"
"Protect the admin dashboard layout"
"Add ownership validation to document queries"
Expected: CSRF protection, cache exports, identity helpers, secure error responses
Common AI Implementation Patterns
Pattern 1: Protected Server Component
Prompt: "Protect the settings page with authentication"
Expected Implementation:
import { requireUser } from "~/lib/auth/server";
export const revalidate = 0;
export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";
export default async function SettingsPage() {
const user = await requireUser();
return (
<div>
<h1>Settings for {user.name}</h1>
{/* Page content */}
</div>
);
}
Pattern 2: Protected API Route with CSRF
Prompt: "Create POST /api/tickets with authentication and CSRF protection"
Expected Implementation:
import { auth } from "@workspace/auth/server";
import { secureErrorJson, secureUserJson } from "@workspace/security";
import { headers } from "next/headers";
import { z } from "zod";
import { assertOrigin, assertFetchMetadata } from "~/lib/security/csrf";
export const runtime = "nodejs";
export const revalidate = 0;
export const dynamic = "force-dynamic";
const BodySchema = z.object({
title: z.string().trim().min(1).max(200),
description: z.string().trim().min(10).max(5000),
});
export async function POST(req: NextRequest) {
try {
assertOrigin(req);
assertFetchMetadata(req);
const session = await auth.api.getSession({ headers: await headers() });
if (!session?.user?.id) {
return secureErrorJson(
{ message: "Authentication required", code: "Unauthorized" },
{ status: 401, userScoped: true }
);
}
const body = BodySchema.parse(await req.json());
// Create ticket logic
return secureUserJson({ ok: true, ticket }, { status: 201 });
} catch (error) {
if (error instanceof z.ZodError) {
return secureErrorJson(
{ message: "Validation failed", code: "ValidationError" },
{ status: 400, userScoped: true }
);
}
return secureErrorJson(
{ message: "Internal server error", code: "Internal" },
{ status: 500, userScoped: true }
);
}
}
Pattern 3: Protected Convex Query
Prompt: "Create authenticated query for user documents"
Expected Implementation:
import { query } from "./_generated/server";
import { v } from "convex/values";
import { getUserIdOrThrow } from "./lib/identity";
export const listMyDocuments = query({
args: { limit: v.number() },
handler: async (ctx, { limit }) => {
const userId = await getUserIdOrThrow(ctx);
const docs = await ctx.db
.query("documents")
.withIndex("by_user_id", (q) => q.eq("userId", userId))
.take(Math.min(limit, 50));
return docs;
},
});
Context File Locations
Prop
Type