Authentication (BetterAuth)
Relevant source files
The following files were used as context for generating this wiki page:
Authentication within the Syllogic personal finance application is primarily managed by BetterAuth, a specialized authentication framework for Next.js and TypeScript. It handles user identity, session management, and secure route protection. The system is designed with a two-service architecture where the frontend handles user-facing authentication, while a shared internal secret mechanism secures communication between the Next.js frontend and the Python backend.
Sources: CLAUDE.md:12-19, README.md:94-113
Architecture Overview
The authentication system utilizes a Drizzle adapter to interface with a PostgreSQL database. BetterAuth is configured to manage four primary tables: users, sessions, authAccounts, and verificationTokens. The system supports traditional email and password authentication and includes an admin plugin for elevated privileges.
Authentication Flow
The following diagram illustrates the interaction between the user, the BetterAuth handler, and the shared database during a session lifecycle.
System Configuration
BetterAuth is initialized in the frontend library and exposed via a Next.js route handler.
Key Components
| Component | File Path | Description |
|---|---|---|
| BetterAuth Instance | frontend/lib/auth.ts | The core configuration defining database adapters, session expiry, and plugins. |
| Route Handler | frontend/app/api/auth/[...all]/route.ts | The Next.js catch-all route that handles all incoming auth requests (login, logout, callback). |
| Internal Auth | frontend/lib/internal-auth.ts | Logic for generating HMAC signatures for frontend-to-backend requests. |
| Migration Scripts | deploy/railway/README.md | Ensures the database schema for BetterAuth is initialized during deployment. |
Sources: frontend/lib/auth.ts:1-5, frontend/app/api/auth/[...all]/route.ts:1-4, frontend/lib/internal-auth.ts:1-10, deploy/railway/README.md:58-63
Session & Security Settings
BetterAuth is configured with strict session durations suitable for financial applications.
- Session Expiry: 24 hours (86,400 seconds).
- Update Age: Sessions are refreshed every 1 hour.
- Cookie Cache: Disabled to ensure immediate consistency with the database.
- Trusted Origins: Derived dynamically from environment variables like
APP_URL,BETTER_AUTH_URL, and platform-specific variables (Railway/Render).
Sources: frontend/lib/auth.ts:46-56, frontend/lib/auth.ts:57-89
Internal Service Authentication
Because the application uses a two-service architecture (Next.js and FastAPI), a secondary authentication mechanism is used to verify that requests to the backend originate from the authorized frontend.
The system uses three specific headers for this internal handshake:
x-syllogic-user-id: The ID of the user making the request.x-syllogic-timestamp: Unix timestamp of the request.x-syllogic-signature: An HMAC SHA256 hex digest of the method, path, userId, and timestamp.
Sources: frontend/lib/internal-auth.ts:3-37, README.md:144-154
Data Schema Mapping
BetterAuth identifies its required entities with specific internal names, which are mapped to the project's Drizzle schema.
| BetterAuth Entity | Project Schema Table | Description |
|---|---|---|
user | schema.users | Stores user profiles and onboarding status. |
session | schema.sessions | Tracks active user sessions. |
account | schema.authAccounts | Links users to authentication providers. |
verification | schema.verificationTokens | Handles email verification or password reset tokens. |
Sources: frontend/lib/auth.ts:34-39, frontend/lib/actions/onboarding.ts:11-30
Implementation Details
User Onboarding and Auth State
The authentication system is tightly integrated with the onboarding flow. Users are categorized by an onboardingStatus which progresses from pending to completed. Protected routes use helper functions to ensure a user is authenticated before proceeding.
// Example of requiring auth in a Server Action
export async function getOnboardingStatus(): Promise<OnboardingStatusResult | null> {
const userId = await requireAuth(); // Enforces authentication
if (!userId) return null;
const user = await db.query.users.findFirst({
where: eq(users.id, userId),
});
// ...
}
Sources: frontend/lib/actions/onboarding.ts:25-45, CLAUDE.md:92-101
Environment Requirements
Successful authentication deployment requires several critical environment variables.
- BETTER_AUTH_SECRET: Used for encryption and signing of session tokens.
- INTERNAL_AUTH_SECRET: Shared secret for signing requests between the frontend and backend.
- APP_URL: The canonical URL used to validate origins.
Sources: deploy/railway/README.md:20-30, README.md:226-235
Conclusion
The Syllogic authentication system provides a robust security layer using BetterAuth for user sessions and HMAC signatures for internal service communication. By mapping BetterAuth entities directly to the Drizzle ORM schema, the application maintains a single source of truth for user data while supporting complex multi-service interactions.