Skip to content

docs: expand Better Auth migration epic with detailed technical plan#130

Merged
enko merged 2 commits intomainfrom
claude/review-auth-migration-plan-YbHd5
Mar 12, 2026
Merged

docs: expand Better Auth migration epic with detailed technical plan#130
enko merged 2 commits intomainfrom
claude/review-auth-migration-plan-YbHd5

Conversation

@enko
Copy link
Copy Markdown
Member

@enko enko commented Mar 12, 2026

Summary

This PR significantly expands the Better Auth migration epic documentation with comprehensive technical details, implementation strategies, and risk mitigation approaches. The changes transform the epic from a high-level overview into a detailed implementation guide.

Key Changes

  • Architecture clarification: Documented the migration from dual-token (JWT + session token) to unified cookie-based session model, including specific frontend changes needed to remove Bearer token logic
  • Database migration strategy: Added detailed schema mapping, external ID preservation approach (critical for maintaining foreign key relationships), and parallel operation plan with auth_legacy schema
  • Password hashing transition: Introduced dual-algorithm verification strategy to support both bcrypt (legacy) and scrypt (new) passwords during migration, enabling gradual password upgrade without forced resets
  • Custom fields handling: Specified how to preserve selfProfileId and preferences using Better Auth's additionalFields feature
  • Comprehensive file-by-file changes: Listed all backend, frontend, and shared package files requiring modification with specific rationale
  • API endpoint mapping: Created detailed table mapping current endpoints to Better Auth equivalents with notes on behavioral changes
  • Enhanced migration steps: Expanded from 8 to 9 steps with spike/POC phase, data migration validation, and 2-week verification period before cleanup
  • Detailed risk assessment: Added 10 specific risks with impact and mitigation strategies, including external ID mapping, custom fields compatibility, and rate limiting conflicts
  • Dependencies clarification: Specified version pinning strategy and SvelteKit version requirement (2.20.0+) for cookie plugin support

Notable Implementation Details

  • Session expiry aligned to existing SESSION_EXPIRY_DAYS (7 days)
  • Custom verify function for password hashing handles transparent bcrypt→scrypt migration
  • Database schema isolation using PostgreSQL search_path parameter to keep Better Auth tables separate
  • Rollback strategy simplified to code revert + schema rename (no feature flag needed)
  • Onboarding flow preserved with selfProfileId as additional field
  • Image loading explicitly tested to ensure session cookie covers previous auth_token cookie usage

https://claude.ai/code/session_015gXWpvq2YYBXJHNVfZaKGL

Address loopholes and inconsistencies found during codebase review:
- Fix code size estimate (~600 → ~1,000 lines)
- Fix session expiry mismatch (30 days → 7 days to match current config)
- Replace dual JWT+session token architecture with unified Better Auth sessions
- Add bcrypt→scrypt dual-algorithm password verification strategy
- Add database schema strategy: rename auth→auth_legacy, use Better Auth migrations with custom search_path
- Add missing scope: shared package types, frontend API client, onboarding flow, preferences endpoint, layout/page files
- Add external ID mapping strategy (use existing UUIDs as Better Auth primary keys)
- Add custom user fields strategy (additionalFields for selfProfileId, preferences)
- Replace unrealistic feature flag rollback with code+schema revert strategy
- Add spike/PoC as first migration step
- Expand risk table with new identified risks
- Update file paths to be monorepo-aware
- Add references to PostgreSQL custom schema docs and migration guides

https://claude.ai/code/session_015gXWpvq2YYBXJHNVfZaKGL
// (omit hash to use default)
verify: async ({ hash, password }) => {
// Try scrypt first (new passwords)
const scryptResult = await defaultVerify({ hash, password });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Correctness: defaultVerify is undefined — and missing error handling for bcrypt hashes

Two related issues in this code snippet that would cause login failures for all migrated users:

1. defaultVerify is never imported. Better Auth doesn't export a function by that name. At runtime this would throw ReferenceError: defaultVerify is not defined. You likely want to call Better Auth's built-in verifier via the context object (see Better Auth password docs) or import a specific utility.

2. No try-catch around the scrypt call. When a bcrypt-formatted hash ($2b$...) is passed to a scrypt verifier, most implementations throw rather than return false (the hash prefix is unrecognised). Without a guard, the error propagates up and every legacy user fails to log in—defeating the zero-disruption goal.

Suggested fix:

import bcrypt from "bcrypt";
import { verifyPassword } from "better-auth"; // check actual export name

verify: async ({ hash, password }) => {
  // Try scrypt first (new passwords)
  try {
    if (await verifyPassword({ hash, password })) return true;
  } catch {
    // Not a scrypt hash, fall through to bcrypt
  }
  // Fall back to bcrypt (migrated passwords)
  try {
    return await bcrypt.compare(password, hash);
  } catch {
    return false;
  }
},

Worth validating the exact Better Auth API for the default verifier during the spike (Step 1).

2. **Prepare database:**
- Rename `auth` schema to `auth_legacy`
- Create new `auth` schema
- Run Better Auth CLI migration: `npx auth@latest migrate`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 **Suggestion: Use pnpm dlx instead of npx\n\nThe project uses pnpm throughout (all AGENTS.md commands, package.json scripts). For consistency:\n\nbash\npnpm dlx auth@latest migrate\n

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 12, 2026

🔍 Automated Code Review

Commit ecf9c07
Reviewed 2026-03-12T10:29:00Z
Status 💡 1 suggestion

Findings

Severity Count
🚨 Critical 0
⚠️ Important 0
💡 Suggestion 1

What Changed Since Last Review

Previous review (commit f53ce9d) flagged two issues — both have been addressed in this commit:

  • defaultVerify undefined → replaced with verify from better-auth/crypto (with spike validation note)
  • npx usage → updated to pnpm dlx auth@latest migrate

New Finding

One new suggestion on this commit (see inline comment at line 115): scryptAsync and timingSafeEqual are imported from @noble/hashes/scrypt but unused in the code snippet — the actual scrypt path delegates to better-auth/crypto.

AGENTS.md Compliance

  • ✅ Documentation follows project conventions
  • ✅ TypeScript strict mode guidance maintained in examples
  • ✅ pnpm tooling used consistently

Summary

This is a documentation-only PR expanding the Better Auth migration epic into a thorough implementation guide. The architectural decisions are sound: external ID preservation strategy, dual-algorithm password verification, PostgreSQL schema isolation, and the spike-first approach for validating risky assumptions are all well-considered. No critical or important issues found.


🤖 Automated review by Claude Code • View CI Run

Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Found 1 important issue and 1 suggestion to consider. See inline comments for details.

- Fix password verify snippet: replace undefined `defaultVerify` with
  bcrypt prefix detection ($2b$/$2a$) and proper fallback logic with
  try-catch around both algorithms. Add note that exact Better Auth
  crypto import path must be validated during spike.
- Replace `npx` with `pnpm dlx` for consistency with project toolchain.

https://claude.ai/code/session_015gXWpvq2YYBXJHNVfZaKGL

**Dual-algorithm strategy during parallel operation:**
```typescript
import { scryptAsync, timingSafeEqual } from "@noble/hashes/scrypt";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Suggestion: Remove unused imports from password verification snippet

The imports scryptAsync and timingSafeEqual from @noble/hashes/scrypt are never used — scrypt verification is delegated to better-auth/crypto's verify function. Leaving them in would add an unnecessary dependency and would be flagged by Biome.

Suggested fix:

// Remove this line entirely — @noble/hashes is not needed
// import { scryptAsync, timingSafeEqual } from "@noble/hashes/scrypt";
import bcrypt from "bcrypt";

Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Found 1 suggestion to consider. See inline comment for details.

@enko enko merged commit c994663 into main Mar 12, 2026
5 checks passed
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 2.67.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants