Skip to main content

Overview

Toots uses better-auth for authentication, providing email/password sign-up and login with session management.

Configuration

Authentication is configured in apps/web/lib/auth/auth.ts:
apps/web/lib/auth/auth.ts
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { prisma } from "../db";

export const auth = betterAuth({
    database: prismaAdapter(prisma, {
        provider: "postgresql",
    }),
    emailAndPassword: {
        enabled: true,
    },
});

Required environment variables

Set these variables in apps/web/.env:
BETTER_AUTH_SECRET="your-random-secret-here"
BETTER_AUTH_URL="http://localhost:3000"
  • BETTER_AUTH_SECRET - A random secret string for session signing and encryption
  • BETTER_AUTH_URL - Your application’s base URL
Use a strong random secret for BETTER_AUTH_SECRET. Never commit it to version control.

Database schema

better-auth uses these Prisma models for authentication:

User model

schema.prisma
model User {
  id            String    @id @default(cuid())
  email         String    @unique
  name          String?
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
  emailVerified Boolean   @default(false)
  image         String?
  sessions      Session[]
  accounts      Account[]
  projects      Project[]

  @@map("user")
}

Session model

schema.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])
  @@index([userId])
  @@map("session")
}

Account model

schema.prisma
model Account {
  id                    String    @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  @default(now())
  updatedAt             DateTime  @updatedAt

  @@index([userId])
  @@map("account")
}

Verification model

schema.prisma
model Verification {
  id         String   @id
  identifier String
  value      String
  expiresAt  DateTime
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt

  @@index([identifier])
  @@map("verification")
}

Authentication features

Email and password

Toots currently supports email/password authentication:
  • Sign up - Create a new account with email and password
  • Sign in - Authenticate with existing credentials
  • Session management - Automatic session handling with secure tokens
  • Route protection - Middleware to protect authenticated routes

User management

Users are identified by:
  • Unique email address
  • CUID (Collision-resistant Unique Identifier)
  • Optional name and profile image
  • Email verification status

Route protection

Toots implements route protection to ensure users are authenticated before accessing protected pages:
  • Public routes - Sign in, sign up pages
  • Protected routes - Dashboard, projects, tickets
  • Automatic redirects - Unauthenticated users are redirected to sign in

Session security

Sessions are secured with:
  • Token-based authentication - Unique session tokens
  • Expiration - Sessions expire after a configured time period
  • IP and user agent tracking - Optional security metadata
  • Cascade deletion - Sessions are deleted when users are removed

Password security

better-auth handles password security:
  • Passwords are hashed using secure algorithms
  • Plain text passwords are never stored
  • Hashes are stored in the Account table
better-auth automatically handles password hashing and verification. You don’t need to implement this manually.

Production considerations

HTTPS required

In production, always use HTTPS for authentication:
.env.production
BETTER_AUTH_URL="https://your-domain.com"
Never use HTTP in production. Sessions and passwords must be transmitted over secure connections.

Secure secrets

Generate a strong secret for production:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Environment-specific URLs

Use different URLs for each environment:
# Development
BETTER_AUTH_URL="http://localhost:3000"

# Staging
BETTER_AUTH_URL="https://staging.your-domain.com"

# Production
BETTER_AUTH_URL="https://your-domain.com"

Future authentication features

better-auth supports additional authentication methods that can be added:
  • OAuth providers (Google, GitHub, etc.)
  • Magic link authentication
  • Two-factor authentication
  • Social login
  • Passkeys
These features are not currently implemented in Toots but can be added through better-auth configuration.

Troubleshooting

Invalid session

If you see session errors:
  1. Check that BETTER_AUTH_SECRET is set
  2. Verify BETTER_AUTH_URL matches your application URL
  3. Clear cookies and sign in again

Database connection errors

Authentication requires a working database connection:
  1. Verify DATABASE_URL is correct
  2. Run migrations: pnpm --filter web db:migrate
  3. Check PostgreSQL is running

Next steps

After configuring authentication:
  1. Set up AI integration
  2. Start the development server
  3. Test sign up and sign in flows

Build docs developers (and LLMs) love