Skip to content

Add Better Auth (TypeScript) and FastAPI Users (Python) to example agents#7

Merged
teallarson merged 12 commits intomainfrom
teallarson/add-auth-fastapi-users
Mar 3, 2026
Merged

Add Better Auth (TypeScript) and FastAPI Users (Python) to example agents#7
teallarson merged 12 commits intomainfrom
teallarson/add-auth-fastapi-users

Conversation

@teallarson
Copy link
Copy Markdown
Collaborator

Summary

Replaces the hand-rolled bcrypt+session auth in all three templates with batteries-included auth libraries, per TOO-466.

TypeScript templates (ai-sdk + mastra):

  • Swap bcrypt for better-auth — email/password auth, Drizzle (SQLite) adapter, httpOnly session cookies
  • New lib/auth.ts: Better Auth server config + backwards-compatible getSession() export (no changes required in existing route files)
  • New lib/auth-client.ts: React client (signIn, signUp, signOut) for client components
  • Updated lib/db/schema.ts: Better Auth tables (user, session, account, verification)
  • New app/api/auth/[...all]/route.ts: Better Auth catch-all handler (replaces old login/register/logout routes)
  • Updated login-form.tsx and dashboard logout to use authClient
  • Added BETTER_AUTH_SECRET + BETTER_AUTH_URL to .env.example

Python template (langchain):

  • Add fastapi-users[sqlalchemy]>=14.0, drop bcrypt
  • New app/auth_manager.py: FastAPI Users setup (UserManager, cookie JWT backend, 7-day sessions)
  • Updated app/models.py: User extends SQLAlchemyBaseUserTableUUID (UUID id, hashed_password, is_active/is_superuser/is_verified; no sessions table — stateless JWT)
  • Updated app/auth.py: get_current_user(request, db) now decodes FastAPI Users JWT — all callers (chat, arcade, plan routes) unchanged
  • Updated app/routes/auth.py: register/login/logout delegate to UserManager
  • Updated Alembic migration: FastAPI Users schema (no sessions table)

All existing Arcade OAuth (MCP Gateway) code is unchanged. Existing route files that call get_current_user(request, db) continue to work without modification.

Test plan

  • TypeScript: scaffold ai-sdk and mastra templates, run bunx drizzle-kit generate && bunx drizzle-kit migrate, start dev server, register account, verify login/logout cycle
  • TypeScript: verify BETTER_AUTH_SECRET env var is required and sessions expire correctly
  • Python: scaffold langchain template, run alembic upgrade head, start server, register/login/logout cycle
  • Python: verify Arcade OAuth connect flow still works after login
  • Smoke test CI passes (scaffolds + builds all templates)

🤖 Generated with Claude Code

teallarson and others added 12 commits March 3, 2026 15:15
…ents

Replaces the hand-rolled bcrypt+session auth in all three templates with
batteries-included auth libraries:

TypeScript (ai-sdk + mastra templates):
- Swap bcrypt for better-auth@^1.2.7; drop @types/bcrypt
- New lib/auth.ts: Better Auth server config with Drizzle (SQLite) adapter
- New lib/auth-client.ts: React client (signIn, signUp, signOut)
- New lib/db/schema.ts: Better Auth core tables (user, session, account, verification)
- New app/api/auth/[...all]/route.ts: Better Auth Next.js catch-all handler
- Remove old login/, register/, logout/ routes (replaced by Better Auth)
- Update login-form.tsx: use authClient.signIn/signUp instead of fetch
- Update dashboard handleLogout: use authClient.signOut()
- Add BETTER_AUTH_SECRET + BETTER_AUTH_URL to .env.example

Python (langchain template):
- Add fastapi-users[sqlalchemy]>=14.0; drop bcrypt
- New app/auth_manager.py: FastAPI Users setup (UserManager, cookie JWT backend)
- Replace app/models.py: User extends SQLAlchemyBaseUserTableUUID, no sessions table
- Replace app/auth.py: get_current_user() reads FastAPI Users JWT cookie
- Replace app/routes/auth.py: register/login/logout backed by UserManager
- Update alembic/versions/001_initial.py: FastAPI Users schema (UUID id,
  hashed_password, is_active/superuser/verified; no sessions table)

All existing Arcade OAuth (MCP) code is unchanged. Route handlers that call
get_current_user(request, db) continue to work without modification.

Closes TOO-466

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Keep Better Auth (delete old login/register routes, keep catch-all handler)
- Use @arcadeai/design-system imports in login-form.tsx alongside authClient
- Use dashboard page references from main with Better Auth descriptions from PR branch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mapToolToSource was returning "other" for any unrecognized tool, causing
all unknown providers to collide on the same key and show a single "Other"
row. Now extracts the actual namespace from the tool name so each provider
gets its own entry (e.g. "notion", "twitter") and displays its real name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Delete ToolStatusBar and its SSE "sources" handler — redundant now
  that the stats-bar filter tiles show source breakdown
- Add GoogleServicesIcon (stacked Gmail + Calendar) for google_calendar
  entries in the source auth gate pre-flight screen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add coerce_database_url validator in config.py so an empty/blank
  DATABASE_URL env var (from the user's shell) falls back to the
  default SQLite URL instead of crashing alembic during scaffolding
- Add "alembic upgrade head" to the printed next-steps for Python
  templates so users know to run migrations after configuring .env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
load_dotenv() with no args uses find_dotenv() which walks UP the
directory tree and can pick up a stray .env from a parent directory
(e.g. /tmp/.env with DATABASE_URL=local.db). Pin it to the project
root with dotenv_path=".env" so it only reads the project's own .env.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LangChain: "LangChain (Python)" → "LangChain (FastAPI + Python)"
  to match the ai-sdk/mastra pattern of embedding the stack in the label
- Clear all hints (were redundant with the label and caused "(Python) (hint)"
  double-paren display when the item was selected in clack)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the validator only guarded against empty strings, so a value
like DATABASE_URL=local.db (no ://) would pass through and cause an
Alembic / SQLAlchemy parse error. Now any value missing :// falls back
to the default sqlite+aiosqlite:///local.db URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Alembic runs during scaffolding before the user sets up .env, so any
DATABASE_URL in the shell environment (even a stale one from a parent
directory) could cause a parse error. By copying .env.example → .env
right before migrations run, we ensure alembic always uses the safe
SQLite default. The copy is skipped if .env already exists.

Also update Python next-steps: since .env already exists, replace the
cp command with a reminder to fill in API keys.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both TS and Python templates now have .env auto-created from .env.example
before migrations run. Updated printSuccess for both branches to say
"fill in .env" instead of showing the cp command. Removed the now-unused
copyCmd variable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _map_tool_to_source: split on [._] so Slack_WhoAmI → slack
  (was split(".") only, causing everything to show as "Other")
- ChatAnthropic: set max_tokens=16384 to avoid hitting the 200k
  combined context limit (langchain-anthropic defaults to 64000)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@teallarson teallarson marked this pull request as ready for review March 3, 2026 22:15
@teallarson teallarson merged commit 50f9d89 into main Mar 3, 2026
@teallarson teallarson deleted the teallarson/add-auth-fastapi-users branch March 3, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant