Skip to content

Conversation

@rblalock
Copy link
Member

@rblalock rblalock commented Dec 26, 2025

PR: Agentuity Auth - First-Class BetterAuth Integration

What This Is

A first-class auth service for developers building apps on Agentuity. Uses BetterAuth as the foundation with zero-config defaults.

  • Auth for dev users, not Agentuity's identity system
  • Tables live in dev Postgres databases
  • Thin wrapper over BetterAuth - fully customizable

Developer Experience

When adding Agentuity Authentication support (via CLI project create auth init for existing projects or create for new projects), some of the things below will auto get setup, run a sql script to populate your agentuity postgres db, etc.

View migration.ts for the tables that get setup.

1. auth.ts

import { Pool } from 'pg';
import {
	createAgentuityAuth,
	createSessionMiddleware,
	createApiKeyMiddleware,
} from '@agentuity/auth/agentuity';

const pool = new Pool({ connectionString: process.env.DATABASE_URL! });

export const auth = createAgentuityAuth({
	database: pool,
	basePath: '/api/auth',
	secret: process.env.BETTER_AUTH_SECRET!,
	emailAndPassword: {
		enabled: true,
	},
});

// Required auth middleware - returns 401 if not authenticated
export const authMiddleware = createSessionMiddleware(auth);

// API key middleware for programmatic access routes
export const apiKeyMiddleware = createApiKeyMiddleware(auth);

// Type export for end-to-end type safety (maybe we need to do this internally in the package now that i think about it)
export type Auth = typeof auth;

2. Add Routes

// Mount BetterAuth routes (sign-in, sign-up, sign-out, session, token, etc.)
// See mountBetterAuthRoutes ts docs for why this wrapper is required
api.on(['GET', 'POST'], '/auth/*', mountBetterAuthRoutes(auth));

3. Protect API Routes

// Session-based auth (cookies/bearer tokens)
api.get('/me', authMiddleware, async (c) => {
	const user = await c.var.auth.getUser();
	return c.json({ id: user.id, name: user.name });
});

// Org role-based access
api.get('/admin', authMiddleware, async (c) => {
	const hasAdminRole = await c.var.auth.hasOrgRole('owner', 'admin');
	const org = await c.var.auth.getOrg();
	
       return c.json({ role: org?.role });
});

// API key-protected routes
api.post('/projects', apiKeyMiddleware, async (c) => {
	const canWriteProject = c.var.auth.hasPermission('project', 'write');
	return c.json({ success: true });
});

4. Agents can access a user's data too (even through agent to agent comms)

Discussion point: put this in the core agent context instead of needing to wrap it.

import { withSession } from '@agentuity/auth/agentuity';

export default createAgent('my-agent', {
	handler: withSession(async (ctx, { auth, org }, input) => {
		// auth.user, auth.session available
		return { userId: auth.user.id };
	}),
});

Middleware

Middleware Purpose Header
createSessionMiddleware(auth, opts) Session/bearer auth Cookies, Authorization: Bearer
createApiKeyMiddleware(auth, opts) API key only x-agentuity-auth-api-key
app.use('/api/*', createSessionMiddleware(authClient));
app.use('/api/*', createApiKeyMiddleware(auth));

5. React Client

// src/web/auth-client.ts
import { createAuthClient } from 'better-auth/react';

// Auth client has useful hooks, etc.
export const { useSession, signIn, signUp, signOut } = createAuthClient();

// src/web/App.tsx
import { AgentuityBetterAuth } from '@agentuity/auth/agentuity/client';

function App() {
	return (
		<AgentuityProvider>
			<AgentuityBetterAuth authClient={authClient}>
				{/* Auth header automatically injected into API calls */}
			</AgentuityBetterAuth>
		</AgentuityProvider>
	);
}

Auth Helpers on c.var.auth

Org Helpers

  • org - Minimal org data (just { id }) from session
  • getOrg() - Fetches full org (name, slug, role) lazily, caches result
  • getOrgRole() - Returns current user's role in active org
  • hasOrgRole(...roles) - Check if user has one of the specified roles

API Key Helpers

  • authMethod - 'session' | 'api-key' | 'bearer'
  • apiKey - API key context with id, name, permissions
  • hasPermission(resource, ...actions) - Check if API key has required permissions

What's Included

Default Plugins (enabled automatically)

Plugin Purpose
organization Multi-tenancy, teams
bearer Accept Authorization header
jwt Token signing/verification
apiKey Programmatic API access

Auth Methods

Both produce the same c.var.auth context:

  • Session - Cookie-based, default for browsers
  • API Key - Header-based (x-agentuity-auth-api-key), for programmatic access

CLI Integration

screenshot-2025-12-26_11-09-58 screenshot-2025-12-26_11-19-42

New Project

If yes: provisions DB, installs deps, generates auth.ts, runs migrations, prints wiring examples.

Skipped automatically for clerk and auth0 templates.

Existing Project

agentuity project auth init

Walks through: database selection → dependency install → auth.ts generation → migrations → integration examples.


Database

Auth tables created automatically via ensureAuthSchema() (called by createAgentuityAuth):

  • user, session, account, verification (BetterAuth core)
  • organization, member, invitation (org plugin)
  • jwks (JWT plugin)
  • apiKey (API key plugin)

Package Exports

# Server
@agentuity/auth/agentuity 
createAgentuityAuth, createSessionMiddleware, createApiKeyMiddleware, withSession, mountBetterAuthRoutes

# Browser
@agentuity/auth/agentuity/client  AgentuityBetterAuth component, etc

Peer dependency: better-auth (only needed if using Agentuity Auth, not Clerk/Auth0)


New Template

image image image

etc


Test App

See apps/testing/ag-auth-test-app/ for a complete working example.

image

rblalock and others added 18 commits December 24, 2025 21:53
- Rename createHonoMiddleware to createMiddleware for consistency
- Make secondary-storage the default for API key storage
- Remove legacy/deprecated code (withAuth, AgentAuthContext)
- Update sample app to import helpers directly from package
- Add extensibility and API routes vs agent auth documentation
- Fix tests to use new function names

BREAKING CHANGE: createHonoMiddleware renamed to createMiddleware
BREAKING CHANGE: withAuth removed, use withSession instead

Amp-Thread-ID: https://ampcode.com/threads/T-019b5ad0-a8a8-7265-97ec-6c7e53491558
Co-authored-by: Amp <amp@ampcode.com>
Add tests for:
- Public routes (no middleware)
- Protected routes (401 on no auth)
- Optional auth routes (both anonymous and authenticated)
- Scope-protected routes (403 on missing scopes)
- Auth method detection (session vs api-key)
- Token extraction from Authorization header
- Raw session data access
- Full app simulation with multiple route types
- Add ./agentuity/client export path for browser-only code
- Update frontend to import from @agentuity/auth/agentuity/client
- Remove @agentuity/core from Vite optimizeDeps (contains Node.js code)

The @agentuity/core package imports from 'stream/web' which doesn't
exist in browsers. It should only be used server-side.
- Update .gitignore to match other test apps (ignore auto-generated AGENTS.md)
- Remove tracked AGENTS.md files from source directories
- Update generated app.ts with workbench router integration
- Format vite-asset-server.ts

Amp-Thread-ID: https://ampcode.com/threads/T-019b5b31-c6db-724d-8d45-f5b0ee4be355
Co-authored-by: Amp <amp@ampcode.com>
Implements Phase 3 CLI Integration for Agentuity Auth:
- Add 'project auth init' subcommand for existing projects
- Database selection/creation using existing cloud db flows
- Auto-install dependencies with bun
- Optional auth.ts generation
- Run migrations with user confirmation
- Print integration examples (routes, client, agents)

The command is non-invasive - generates only auth.ts and prints
examples for the rest, avoiding template-specific codegen issues.

Amp-Thread-ID: https://ampcode.com/threads/T-019b5b31-c6db-724d-8d45-f5b0ee4be355
Co-authored-by: Amp <amp@ampcode.com>
- shared.test.ts: Tests for AUTH_DEPENDENCIES, SQL schema, generateAuthFileContent
- init.test.ts: Tests for command definition, DATABASE_URL detection, file handling

51 tests covering command structure, schema validation, and core logic.

Amp-Thread-ID: https://ampcode.com/threads/T-019b5b31-c6db-724d-8d45-f5b0ee4be355
Co-authored-by: Amp <amp@ampcode.com>
The dbQuery API only supports single SQL statements. Split
AGENTUITY_AUTH_BASELINE_SQL into individual statements and
execute them sequentially.

Added splitSqlStatements helper with tests.

Amp-Thread-ID: https://ampcode.com/threads/T-019b5b31-c6db-724d-8d45-f5b0ee4be355
Co-authored-by: Amp <amp@ampcode.com>
tui.success() prints to console and returns void, use
tui.tuiColors.success() to colorize inline text instead.
When DATABASE_URL is already configured, show it as the first
option in the picker with a checkmark, instead of a separate
confirm dialog. Cleaner UX with one consistent picker.
- Add 'Enable Agentuity Authentication?' prompt after resource provisioning
- Reuse shared helpers from project auth init (selectOrCreateDatabase,
  ensureAuthDependencies, runAuthMigrations, etc.)
- Fix DATABASE_URL ordering: write after createProjectConfig to avoid overwrite
- Print integration examples at end of create flow when auth is enabled
- Update AGENTUITY_AUTH.md with Phase 3 completion and design decisions
- Update AGENTS.md files to recommend running tests in subagent to avoid context bloat

Phase 3 CLI Integration is now complete:
- agentuity project auth init (existing projects)
- agentuity create with auth option (new projects)

Amp-Thread-ID: https://ampcode.com/threads/T-019b5b6d-9f7e-73de-8b23-516649a24438
Co-authored-by: Amp <amp@ampcode.com>
- Remove old migrations/ folder (now uses ensureAuthSchema at runtime)
- Remove stale dev.db and cookies.txt files
- Update README.md with comprehensive auth documentation
- Update .gitignore to exclude auth test artifacts

The auth test app now demonstrates the canonical pattern for
Agentuity Auth integration with BetterAuth.

Amp-Thread-ID: https://ampcode.com/threads/T-019b5b6d-9f7e-73de-8b23-516649a24438
Co-authored-by: Amp <amp@ampcode.com>
Templates like 'clerk' and 'auth0' already have auth configured,
so offering to set up Agentuity Auth would create a conflict.
…roved docs

- Rename createAuthHandler to mountBetterAuthRoutes for clarity
- Add comprehensive JSDoc explaining why this wrapper is needed for Hono routes
- Document the cookie handling issue when returning raw Response objects
- Add TODO to discuss potential runtime-level fix
- Simplify AuthDemo by removing manual token bridging (cookies handle it)
- Fix sign-out to reload page to clear client-side session cache

Amp-Thread-ID: https://ampcode.com/threads/T-019b5bbf-5047-73ad-90bf-14b1a14563f0
Co-authored-by: Amp <amp@ampcode.com>
@coderabbitai
Copy link

coderabbitai bot commented Dec 26, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch first-class-auth

Comment @coderabbitai help to get the list of available commands and usage tips.

rblalock and others added 11 commits December 26, 2025 13:11
- CLI now imports AGENTUITY_AUTH_BASELINE_SQL from @agentuity/auth
- Added ./agentuity/migrations export to auth package
- Removed duplicate SQL from CLI
- Added @agentuity/auth as CLI dependency
…mples

- API Key: create, list, revoke API keys
- JWT: get token with JWKS URL
- Bearer: documentation route for bearer auth usage
- Organization: create, list, activate, invite, members, whoami

Amp-Thread-ID: https://ampcode.com/threads/T-019b5bbf-5047-73ad-90bf-14b1a14563f0
Co-authored-by: Amp <amp@ampcode.com>
- Fix ensureAuthSchema to check for apikey table (not just user table)
- Fix apikey table name to be lowercase (BetterAuth requirement)
- Change default API key storage from 'secondary-storage' to 'database'
- Add schema auto-creation to test app startup
- Fix TypeScript type errors in test app routes (cast auth.api to any)
- Update migration tests for lowercase table names

Amp-Thread-ID: https://ampcode.com/threads/T-019b5bbf-5047-73ad-90bf-14b1a14563f0
Co-authored-by: Amp <amp@ampcode.com>
- Add OrganizationApiMethods interface for organization plugin
- Add ApiKeyApiMethods interface for apiKey plugin
- Add JwtApiMethods interface for jwt plugin
- Add DefaultPluginApiMethods combined type
- Cast createAgentuityAuth return type to include plugin methods
- Remove 'as any' casts from test app routes
- Export all new types from package

Amp-Thread-ID: https://ampcode.com/threads/T-019b5bbf-5047-73ad-90bf-14b1a14563f0
Co-authored-by: Amp <amp@ampcode.com>
- Simplify ensureAuthSchema to always run SQL (IF NOT EXISTS is idempotent)
- Revert API key default storage to 'secondary-storage' (uses KV)
- Test app explicitly uses 'database' storage since it doesn't configure KV
- Remove unused 'schema' option from EnsureAuthSchemaOptions
- Add comprehensive AuthDemo.tsx with UI for:
  - User Profile display
  - JWT Token generation and copying
  - API Key management (create, list, delete)
  - Organization management (create, list, activate)
- Simplify API Key plugin config to use database storage (defer KV support)
- Remove createAgentuityApiKeyStorage export for now
- Clean up auth.ts in test app to use simplified config

All BetterAuth plugins tested and working:
- API Key plugin with enableSessionForAPIKeys
- JWT plugin with token generation
- Bearer plugin for token auth
- Organization plugin with roles
- Add withSession unified wrapper for agent handlers
- Add requireScopes middleware for Hono routes
- Add createScopeChecker and createRoleScopeChecker helpers
- Update withSession signature to (ctx, session, input) format
- Add poem agent demonstrating agent-to-agent auth propagation
- Add scope-based routes in test app (/admin, /projects, /debug/scopes)
- Export inAgentContext, inHTTPContext, getAgentContext, getHTTPContext from runtime

Note: Scope-based approach to be replaced with native BetterAuth permissions
Remove all generic scope abstractions in favor of BetterAuth's native
permissions approach:

**Removed from @agentuity/auth:**
- requireScopes middleware
- RequireScopesOptions type
- createScopeChecker function
- createRoleScopeChecker function
- extractScopes function
- hasScope from WithSessionContext
- requiredScopes from WithSessionOptions

**Updated:**
- withSession now only provides { auth, org } context
- WithSessionOptions only has optional?: boolean
- Test app uses native BetterAuth org roles and API key permissions
- CLI examples updated to match new withSession signature

**Rationale:**
- Scopes (user-based, from admin plugin) and API key permissions
  (BetterAuth's Record<string, string[]>) are fundamentally different
- BetterAuth already provides API key permissions natively
- The scope abstraction was too complex for framework users
- Future user permissions should come from BetterAuth admin plugin

The test app now demonstrates:
- Org role-based access (/admin - checks role via getFullOrganization)
- API key permissions (/projects - uses verifyApiKey native API)
- Debug endpoint (/debug/permissions) for native permission inspection
1. Fix migrations.test.ts:
   - Update tests to match simplified ensureAuthSchema implementation
   - ensureAuthSchema now always runs idempotent SQL and returns created: true
   - Remove obsolete tests for table existence checks and custom schema

2. Fix AgentuityProvider re-render issue:
   - Memoize setAuthHeader and setAuthLoading with useCallback
   - Memoize context value with useMemo
   - This prevents unnecessary re-renders in AgentuityBetterAuth and
     other components that depend on auth context

The re-rendering issue was caused by:
- setAuthHeader/setAuthLoading being new function references on each render
- Context value object being recreated on each render
- This caused useEffect dependencies to trigger on every parent re-render
- Add createSessionMiddleware and createApiKeyMiddleware
- Add org helpers: getOrg(), getOrgRole(), hasOrgRole()
- Add API key helpers: hasPermission(), authMethod, apiKey context
- Lazy load full org data via getOrg() (fetches on first call, caches)
- c.var.org contains minimal data (just id) from session
- Custom header: x-agentuity-auth-api-key for API key auth
- Full type safety on c.var.* via Hono module augmentation
- Update test app to use new ergonomic helpers

Amp-Thread-ID: https://ampcode.com/threads/T-019b5fe6-13cd-76a9-9984-20fb61996104
Co-authored-by: Amp <amp@ampcode.com>
rblalock and others added 6 commits December 27, 2025 13:46
- OTEL PII filtering: add otelSpans config object with email/orgName
  options (default true, opt-out with { email: false })
- Fix React tokenEndpoint default: /auth/token → /api/auth/token
  to match CLI scaffold basePath
- CLI scaffold: add BETTER_AUTH_SECRET to generated auth.ts with
  post-step warning message
- Update CLI integration examples: correct paths (/api/auth/*),
  updated checklist with BETTER_AUTH_SECRET
- Header forwarding allowlist: mountBetterAuthRoutes now uses
  configurable allowList instead of forwarding all headers
- README: add Agentuity/BetterAuth section with full Quick Start,
  API reference, and updated security best practices
- SQL security annotations: add comments explaining security
  implications for account, jwks, and apikey tables

Amp-Thread-ID: https://ampcode.com/threads/T-019b60b0-3bd7-72c6-bd87-3b3cfdab2e86
Co-authored-by: Amp <amp@ampcode.com>
This is a new unreleased package, so deprecated aliases don't make sense.
Use createSessionMiddleware directly instead.
- Add organizationClient and apiKeyClient plugins to createAgentuityAuthClient
- Bump better-auth from ^1.2.0 to ^1.4.9 (latest)
- Update CLI AUTH_DEPENDENCIES to use ^1.4.9
- Update test app to use ^1.4.9

Amp-Thread-ID: https://ampcode.com/threads/T-019b6190-6a27-756f-9db0-249e3e71bff5
Co-authored-by: Amp <amp@ampcode.com>
- Add resolveBaseURL() to auto-resolve baseURL from env vars
  (BETTER_AUTH_URL, AGENTUITY_DEPLOYMENT_URL)
- Add createDefaultTrustedOrigins() for zero-config CORS handling
  - Trusts resolved baseURL origin
  - Trusts AGENTUITY_DEPLOYMENT_URL origin
  - Trusts same-origin of incoming requests
  - Supports extra origins via AGENTUITY_AUTH_TRUSTED_ORIGINS env
- Update createAgentuityAuth to apply defaults when not provided
- Add explicit TrustedOrigins type to fix downstream type inference
- Add comprehensive tests for origin handling

Amp-Thread-ID: https://ampcode.com/threads/T-019b64fd-83b2-70c4-92a3-d2d764c573a1
Co-authored-by: Amp <amp@ampcode.com>
- Add new agentuity-auth template with BetterAuth integration
  - Email/password authentication out of the box
  - React sign-in/sign-up UI components
  - Session middleware for protected routes
  - API key support for programmatic access
- Register template in templates.json
- Update CLI auth init and template flow for new template
- Minor formatting cleanup in auth package exports and tests

Amp-Thread-ID: https://ampcode.com/threads/T-019b64fd-83b2-70c4-92a3-d2d764c573a1
Co-authored-by: Amp <amp@ampcode.com>
@rblalock rblalock requested review from jhaynie and potofpie December 28, 2025 19:38
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.

2 participants