Authentication
Better Auth integration with session management, OAuth, 2FA, and email verification
Authentication Package
The @workspace/auth package provides a complete authentication solution for the monorepo, including email/password auth, Google OAuth, two-factor authentication, and session management via Better Auth.
Overview
- Package:
@workspace/auth - Location:
packages/auth - Authentication: Better Auth with TypeScript
- Providers: Email/password and Google OAuth
- Security: 2FA, email verification, rate limiting
- Database: PostgreSQL via Prisma (shared db package)
Architecture
The auth package integrates with:
- @workspace/db - User and session storage via Prisma
- @workspace/email - Email verification and password reset flows
- @workspace/rate-limit - Protection against brute force attacks
Features
Authentication Methods
- Email/Password - Credential-based login with secure password hashing
- Google OAuth - Sign in with Google account
- Social Providers - Extensible for additional OAuth providers
- Email Verification - Verify user email addresses before account activation
- Password Reset - Secure password reset flows via email
Security
- Two-Factor Authentication (2FA) - TOTP-based second factor
- Session Management - Secure session tokens and cookies
- Rate Limiting - Protect against brute force and abuse
Session Management
- Session Tokens - Secure, short-lived session tokens
- Session Rotation - Session tokens are securely rotated and validated server-side
- Session Persistence - Server-side session storage in database
Project Structure
packages/auth/
├── src/
│ ├── client.ts # Client actions (signIn, signOut, etc.)
│ ├── server.ts # Better Auth instance
│ ├── keys.ts # Environment variable keys and validation
│ ├── next-handlers.ts # Next.js API route handlers
│ ├── node-handlers.ts # Express middleware handlers
│ └── index.ts # Package entry point
├── .env.example # Environment variables template
├── package.json # Dependencies and scripts
└── README.md # Package documentationEnvironment Variables
For complete environment variable setup and configuration, see the Environment Variables Guide.
This package requires:
BETTER_AUTH_SECRET- Authentication encryption key (32+ characters). See Environment Variables Guide for generation instructions. Must be identical acrossapps/api/.env.localandapps/web/.env.local.BETTER_AUTH_URL- Auth server URLGOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRET- OAuth credentialsDATABASE_URL- PostgreSQL connection string
Usage
Client Side (React Components)
Sign In
import { signIn } from "@workspace/auth/client";
// Email/password sign in
await signIn.email({
email: "user@example.com",
password: "password123",
});
// Google OAuth sign in
await signIn.social({
provider: "google",
});Get Session in Components
import { useSession } from "@workspace/auth/client";
export default function Dashboard() {
const { data: session, isPending } = useSession();
if (isPending) return <div>Loading...</div>;
if (!session) return <div>Not authenticated</div>;
return <div>Welcome, {session.user.name}!</div>;
}Sign Out
import { signOut } from "@workspace/auth/client";
await signOut();2FA Management
import { twoFactor } from "@workspace/auth/client";
// Enable 2FA
const { secret, qrCode } = await twoFactor.enable();
// Verify 2FA code
await twoFactor.verify({ code: "123456" });
// Disable 2FA
await twoFactor.disable();Server Side (API Routes & Server Components)
Get Session
import { auth } from "@workspace/auth/server";
// In API route
export async function GET(request: Request) {
const session = await auth.api.getSession({
headers: request.headers,
});
if (!session) {
return new Response("Unauthorized", { status: 401 });
}
return Response.json(session);
}Protect Routes
import { auth } from "@workspace/auth/server";
export async function protectedAction(request: Request) {
const session = await auth.api.getSession({
headers: request.headers,
});
if (!session?.user) {
throw new Error("Unauthorized");
}
// User is authenticated
return { user: session.user };
}Access User Data
const session = await auth.api.getSession({ headers });
// Access user information
console.log(session.user.id);
console.log(session.user.email);
console.log(session.user.name);Key Scripts
| Command | Purpose |
|---|---|
pnpm dev | Watch mode for development |
pnpm build | Build package for production |
pnpm type-check | Check TypeScript types |
pnpm lint | Check code quality with ESLint |
Authentication Flows
Email/Password Registration
1. User enters email and password
2. Password validation (strength requirements)
3. Check email uniqueness
4. Hash password
5. Create user in database
6. Send verification email
7. User clicks verification link
8. Account activatedEmail/Password Login
1. User enters email and password
2. Fetch user from database
3. Verify password hash
4. If 2FA enabled: prompt for 2FA code
5. Create session token
6. Set secure session cookie
7. Return session to clientGoogle OAuth Flow
1. User clicks "Sign in with Google"
2. Redirect to Google OAuth consent screen
3. User authorizes application
4. Google redirects back with auth code
5. Exchange code for access token
6. Fetch user info from Google
7. Find or create user in database
8. Create session token
9. Redirect to dashboardPassword Reset
1. User requests password reset
2. Check email exists in database
3. Generate reset token
4. Send reset link via email
5. User clicks link and enters new password
6. Validate token and password
7. Update password hash
8. Invalidate existing sessions (on request)
9. Redirect to loginEmail Verification
After signup, users receive a verification email:
- Verification Link - Expires after 24 hours
- Resend - Users can request new verification email if original expired
Two-Factor Authentication
Enable 2FA
- User requests to enable 2FA
- Server generates TOTP secret
- Server renders QR code for user's authenticator app
- User scans QR code with Google Authenticator, Authy, etc.
- User enters 6-digit code to confirm
- 2FA is enabled and backup codes are generated
Login with 2FA
- User enters email and password
- Session created but marked as unverified
- Server prompts for 2FA code
- User enters code from authenticator app
- Code validated against TOTP secret
- Session marked as verified
- User logged in
Session Management
Session Lifespan
- Session Token - Expires after 7 days by default
- Session Refresh Window - Can refresh session before expiration
- Sliding Expiration - Active sessions are automatically renewed
Session Storage
Sessions are stored in PostgreSQL database via Prisma:
model Session {
id String @id
expiresAt DateTime
token String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ipAddress String?
userAgent String?
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([token])
@@map("session")
}Session Cookies
- Name:
better-auth.session_token - HttpOnly: Yes
- Secure: Yes (HTTPS only in production)
- SameSite: Lax
- Path:
/(available site-wide)
Rate Limiting
The auth package integrates with @workspace/rate-limit to protect against abuse:
# Sensitive endpoints are rate limited:
# - Sign up: 5 requests per minute per IP
# - Sign in: 10 requests per minute per IP
# - Email verification: 5 requests per minute per email
# - Email change: 5 requests per minute per email
# - Password reset: 3 requests per minute per IP
# - 2FA verification: 5 requests per minute per userDevelopment Tips
Testing Authentication
Use the development environment variables to test locally:
# Sign in with test account
Email: test@example.com
Password: TestPassword123!
# Or use Google OAuth with test accountDebugging Sessions
// Log session info in browser console
const session = await useSession();
console.log(session);Database Debugging
# View auth tables in Prisma Studio
pnpm db:studioEnvironment Setup
Ensure these are set for development:
BETTER_AUTH_SECRET=your-secret-key
BETTER_AUTH_URL=http://localhost:3000
DATABASE_URL=your-db-urlTroubleshooting
Session Not Persisting
- Check Cookies - Open browser DevTools → Application → Cookies
- Verify Domain - Cookie domain should match your app domain
- Check Database - Verify sessions table exists and has data
- Clear Cache - Clear browser cache and try again
Google OAuth Not Working
- Verify Credentials - Check
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRET - Check Redirect URI - Must exactly match in Google Cloud Console
- Scope Permissions - Ensure
emailandprofilescopes are requested - Check CORS - Verify your domain is allowed in Google Console
2FA Issues
- Time Sync - Ensure authenticator app and server time are synchronized
- Backup Codes - Check that backup codes were saved
- App Clock - Phone time should be within ±30 seconds of server
Email Not Sending
- Check RESEND_TOKEN - Verify token is valid and not revoked
- Check Domain - Email domain must be verified in Resend
- Check Sender -
RESEND_EMAIL_FROMmust be authorized domain - Logs - Check Resend dashboard for delivery status
Type Errors
# Regenerate types if you modify Better Auth config
pnpm db:generate
pnpm type-checkSecurity Best Practices
For Developers
- Never expose secrets - Keep
BETTER_AUTH_SECRETprivate - Use HTTPS - Always use HTTPS in production
- Validate input - Validate email and password on client and server
- Rate limit - Use
@workspace/rate-limiton sensitive endpoints - Log events - Log authentication events for security auditing
For Users
- Strong passwords - Enforce password strength requirements
- Enable 2FA - Recommend users enable two-factor authentication
- Verify email - Require email verification before account activation
- Session security - Log out inactive sessions after period
- Breach detection - Monitor for compromised credentials
Related Documentation
- Database Package - User and session storage via Prisma
- Email Package - Verification and password reset emails
- Rate Limiting Package - Brute force protection
- Web Application - Frontend authentication UI
- API Application - API authentication middleware
For More details on Better Auth configuration, see the Better Auth documentation.