Documentation Index
Fetch the complete documentation index at: https://docs.builderbox.ai/llms.txt
Use this file to discover all available pages before exploring further.
Server-Side Setup
1. Configure BetterAuth
// apps/server/src/lib/auth.ts
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "postgresql",
}),
emailVerification: {
sendVerificationEmail: async ({ user, url, token }) => {
await sendEmail({
to: user.email,
subject: "Verify your email",
from: "onboarding@resend.dev",
html: getVerifyEmailHtml(
user.name || user.email?.split("@")[0] || "User",
"Builder Box",
process.env.CORS_ORIGIN as string,
token,
),
});
},
sendOnSignUp: true,
autoSignInAfterVerification: true,
expiresIn: 3600,
},
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
sendResetPassword: async ({ user, url, token }) => {
const userName = user?.name || user?.email?.split("@")[0] || "User";
await sendEmail({
to: user.email,
subject: "Reset your password",
from: "onboarding@resend.dev",
html: getResetPasswordEmailHtml(userName, url),
});
},
},
});
2. Database Schema
The authentication system uses the following Prisma schema:
model User {
id String @id @map("_id")
name String
email String @unique
emailVerified Boolean
image String?
createdAt DateTime
updatedAt DateTime
sessions Session[]
accounts Account[]
}
model Session {
id String @id @map("_id")
expiresAt DateTime
token String @unique
createdAt DateTime
updatedAt DateTime
ipAddress String?
userAgent String?
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Account {
id String @id @map("_id")
accountId String
providerId String
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
accessToken String?
refreshToken String?
idToken String?
accessTokenExpiresAt DateTime?
refreshTokenExpiresAt DateTime?
scope String?
password String?
createdAt DateTime
updatedAt DateTime
}
Client-Side Implementation
1. Auth Client Setup
// apps/web/src/lib/auth-client.ts
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_SERVER_URL!,
});
2. Sign Up Form
// apps/web/src/features/auth/signup-form.tsx
"use client";
import { authClient } from "@/lib/auth-client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const signUpSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
password: z.string().min(8),
});
export function SignUpForm() {
const form = useForm({
resolver: zodResolver(signUpSchema),
});
const onSubmit = async (data: z.infer<typeof signUpSchema>) => {
try {
await authClient.signUp(data);
// Handle success
} catch (error) {
// Handle error
}
};
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* Form fields */}
</form>
);
}
3. Login Form
// apps/web/src/features/auth/login-form.tsx
"use client";
import { authClient } from "@/lib/auth-client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const loginSchema = z.object({
email: z.string().email(),
password: z.string(),
});
export function LoginForm() {
const form = useForm({
resolver: zodResolver(loginSchema),
});
const onSubmit = async (data: z.infer<typeof loginSchema>) => {
try {
await authClient.signIn(data);
// Handle success
} catch (error) {
// Handle error
}
};
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* Form fields */}
</form>
);
}
4. Password Reset Flow
// apps/web/src/features/auth/forgot-password-form.tsx
"use client";
import { authClient } from "@/lib/auth-client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const forgotPasswordSchema = z.object({
email: z.string().email(),
});
export function ForgotPasswordForm() {
const form = useForm({
resolver: zodResolver(forgotPasswordSchema),
});
const onSubmit = async (data: z.infer<typeof forgotPasswordSchema>) => {
try {
await authClient.forgotPassword(data.email);
// Handle success
} catch (error) {
// Handle error
}
};
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* Form fields */}
</form>
);
}
Authentication Flows
1. Sign Up Flow
- User enters name, email, and password
- Form validation
- Create user account
- Send verification email
- User verifies email
- Auto sign-in after verification
2. Login Flow
- User enters email and password
- Form validation
- Verify credentials
- Create session
- Redirect to dashboard
3. Password Reset Flow
- User requests password reset
- Send reset email
- User clicks reset link
- Enter new password
- Update password
- Sign in with new password
Security Features
-
Password Security
- Secure password hashing
- Password complexity requirements
- Rate limiting for failed attempts
-
Email Verification
- Secure token generation
- Token expiration
- One-time use tokens
-
Session Security
- Secure session storage
- Session timeout
- IP and user agent tracking
Best Practices
-
Error Handling
- Show user-friendly error messages
- Log authentication errors
- Handle edge cases
-
User Experience
- Clear form validation
- Loading states
- Success/error feedback
-
Security
- HTTPS in production
- Rate limiting
- Input validation
Next Steps