Authentication

Authentication (@repo/auth)

This package handles all authentication concerns for the project, providing both server-side logic and client-side utilities. It leverages the better-auth library, configuring it with project-specific settings.

Why You Need This

Reliable authentication is crucial for any SaaS product. This package offers secure defaults and a plug-in system so you can enable features like organization management or API keys without starting from scratch. For setup instructions see Getting Started.

About better-auth

better-auth is a modern, modular, and type-safe authentication library designed for TypeScript applications. It aims to simplify the implementation of complex authentication flows while adhering to security best practices.

Why We Chose better-auth

The decision to use better-auth was based on several key factors that align with our project's goals and principles:

  1. Modularity and Extensibility: Its plugin-based architecture allows us to easily enable or disable features (like Organization Management, API Keys, Email OTP, Expo support) as needed. This fits perfectly with our modular monorepo structure.
  2. Type Safety: better-auth is built with TypeScript first, offering strong type safety and inference capabilities (e.g., inferAdditionalFields for the client). This aligns with our core principle of using TypeScript rigorously.
  3. Database Agnostic (with Drizzle Adapter): The library supports various database adapters, including a first-party Drizzle adapter (better-auth/adapters/drizzle), which integrates seamlessly with our @repo/database package.
  4. Modern Practices & Security: It handles many underlying complexities of authentication (session management, secure token handling, etc.), helping us avoid common pitfalls associated with implementing custom authentication schemes [[Cite: https://blog.gitguardian.com/authentication-and-authorization/]].
  5. Framework Integration: While the core is flexible, it provides convenient wrappers and utilities specifically for Hono (server-side) and React (client-side), matching our technology stack.
  6. Comprehensive Feature Set: It provides built-in support or plugins for essential features required by modern applications, including email/password, OTP flows, organization/team management, API key handling, and admin capabilities.

By using better-auth, we benefit from a robust, well-structured, and secure foundation for authentication, allowing us to focus more on application-specific features.

Core Concepts

  • Foundation: Built on better-auth.
  • Configuration: Uses environment variables (process.env) and shared configuration from @repo/config.
  • Database Integration: Connects to the database using @repo/database via the drizzleAdapter from better-auth/adapters/drizzle.
  • Email Integration: Sends emails (verification, password reset, invitations) using the @repo/email package.
  • Extensibility: Utilizes better-auth plugins for various features.
  • Server & Client: Provides both server-side initialization (auth.server.ts) and a React client (client.ts).

Key Files

  • src/auth.server.ts: Initializes the main betterAuth instance (auth) with server-side configuration, including database adapter, plugins, and email sending logic.
  • src/client.ts: Creates and exports the authClient instance using createAuthClient from better-auth/react, configured with corresponding client plugins for use in frontend applications.
  • src/index.ts: Re-exports essential types from better-auth and the configured authClient.

Server-Side Setup (auth.server.ts)

This file is the heart of the authentication setup on the backend.

  • Initialization: Calls betterAuth(authConfig) to create the core auth service.
  • Database Adapter: Uses drizzleAdapter(db) to connect better-auth to our Drizzle ORM setup defined in @repo/database.
  • Email Service: Integrates with @repo/email to send various transactional emails (verification, password reset, OTPs, organization invitations) using predefined templates.
  • Configuration (authConfig): Defines core settings like secret, baseURL, basePath, trusted origins, cookie attributes, and enables/disables features based on @repo/config.
  • Plugins: Configures and enables various better-auth plugins:
    • apiKey: For handling API key authentication.
    • organization: Manages multi-tenancy, teams, and invitations.
    • anonymous: Allows anonymous user sessions.
    • admin: Provides utilities for admin actions.
    • openAPI: Integrates with OpenAPI documentation generation.
    • expo: Support for React Native (Expo) environments.
    • emailOTP: Handles one-time password logic for sign-in, verification, and password reset via email.
    • emailAndPassword (conditional): Standard email/password login, enabled via @repo/config.
// Simplified snippet from packages/auth/src/auth.server.ts import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { db } from "@repo/database"; import { EmailService /* ...email templates */ } from "@repo/email"; import { apiKey, organization /* ...other plugins */ } from "better-auth/plugins"; const authConfig: CustomAuthOptions = { secret: process.env.BETTER_AUTH_SECRET, baseURL: process.env.BETTER_AUTH_URL, database: drizzleAdapter(db, { provider: "pg" }), emailVerification: { /* ... email sending logic ... */ }, plugins: [ apiKey(), organization({ /* ... org config ... */ }), /* ... */ ], // ... other configurations ... }; const authService = betterAuth(authConfig); export const auth = authService; // Export the configured service

Client-Side Setup (client.ts)

This file prepares the client-side utilities for use in React applications.

  • Initialization: Uses createAuthClient from better-auth/react.
  • Plugins: Includes client-side counterparts for the enabled server plugins (organizationClient, anonymousClient, apiKeyClient, adminClient, emailOTPClient).
  • Export: Exports the configured authClient.
// Snippet from packages/auth/src/client.ts import { createAuthClient } from "better-auth/react"; import { organizationClient, /* ...other client plugins */ } from "better-auth/client/plugins"; import type { auth } from "./auth.server"; export const authClient = createAuthClient({ basePath: "/api/v1/auth", // Matches server basePath plugins: [ inferAdditionalFields<typeof auth>(), // Important for type inference organizationClient(), // ... other client plugins ], });

Usage

API Server (@repo/api)

  • Middleware: The API server uses authMiddleware (created using auth.api.getSession and DB lookups) to authenticate requests and attach user/session info to the Hono context (c.get('user')).
  • Protected Routes: The requireAuth middleware checks for c.get('user') to protect endpoints.
  • Auth Endpoints: Mounts the auth.handler (via toHonoHandler) at /api/v1/auth/* to handle sign-in, sign-up, etc.
  • Accessing User: Route handlers access the authenticated user via c.get('user').

(See the API Usage Guide for detailed examples)

Web Application (apps/web)

  • Import: Import authClient directly: import { authClient } from "@repo/auth";
  • Hooks: Use React hooks provided by better-auth/react (often re-exported or used via authClient), such as useSession.
  • Actions: Call methods on authClient for actions like sign-in, sign-out, sign-up, password reset, social login, etc. (e.g., authClient.signIn({ email, password })).
  • Protected Routes: Use the useSession hook to check authentication status and redirect unauthenticated users.
  • Server-Side: In server loaders/actions (if applicable), use auth.api.getSession({ headers }) to get the session.

(See the Web App Usage Guide for detailed examples)