The boilerplate provides a complete email and password authentication system with email verification and password reset capabilities.
// 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), }); }, }, });
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 }
// apps/web/src/lib/auth-client.ts import { createAuthClient } from "better-auth/react"; export const authClient = createAuthClient({ baseURL: process.env.NEXT_PUBLIC_SERVER_URL!, });
// 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> ); }
// 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> ); }
// 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> ); }