Protect your agents and routes with user authentication. Agentuity provides a first-party auth solution powered by BetterAuth.
This page covers React frontend auth setup. For a platform overview covering API keys, bearer tokens, and session auth configuration, see Authentication Services.
Full-Stack Auth in Seconds
const { user, isAuthenticated } = useAuth();
if (!isAuthenticated) return <LoginForm />;
return <div>Welcome, {user.name}</div>;What You Get
- Email/password authentication out of the box
- Session and API key middleware for routes
- Native
ctx.authsupport in agents - Organizations, teams, and roles via BetterAuth plugins
- JWT tokens for external service integration
Quick Start
Select authentication during project creation:
agentuity project create
# Select "Yes" when prompted for authenticationServer Setup
The Basics
Create an auth instance with just a connection string:
import { createAuth } from '@agentuity/auth';
export const auth = createAuth({
connectionString: process.env.DATABASE_URL,
});That's it! This gives you:
- Email/password authentication
- Session management
- All default plugins (see below)
Default Plugins
Agentuity Auth includes these plugins automatically:
| Plugin | Purpose |
|---|---|
organization | Multi-tenancy with teams, roles, and invitations |
jwt | JWT token generation with JWKS endpoint |
bearer | Bearer token auth via Authorization header |
apiKey | API key authentication for programmatic access |
If you need full control over plugins, use skipDefaultPlugins: true and add only what you need.
Mounting Auth Routes
Mount the auth handler to expose sign-in, sign-up, session, and other endpoints:
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
import { mountAuthRoutes } from '@agentuity/auth';
import { auth } from '../auth';
const router = new Hono<Env>();
// Mount auth routes at /api/auth/*
router.on(['GET', 'POST'], '/api/auth/*', mountAuthRoutes(auth));
export default router;Advanced Configuration
import { createAuth } from '@agentuity/auth';
export const auth = createAuth({
connectionString: process.env.DATABASE_URL,
// Or: database: drizzleAdapter(db, { provider: 'pg', schema: authSchema }),
skipDefaultPlugins: false, // Set true for full control over plugins
apiKey: { enabled: true, defaultPrefix: 'ag_', defaultKeyLength: 64 },
trustedOrigins: ['https://your-domain.com'], // Auto-resolved from env by default
plugins: [], // Add custom BetterAuth plugins
});Middleware
Session Middleware
Protect routes with session-based authentication:
import { createSessionMiddleware } from '@agentuity/auth';
import { auth } from '../auth';
// Required authentication (returns 401 if not authenticated)
const authMiddleware = createSessionMiddleware(auth);
// Optional authentication (continues without auth context if not authenticated)
const optionalAuth = createSessionMiddleware(auth, { optional: true });
// Role-based access (returns 403 if user lacks required role)
const adminOnly = createSessionMiddleware(auth, { hasOrgRole: ['admin', 'owner'] });Usage examples:
// Protect all API routes
router.use('/api/*', authMiddleware);
// Allow both authenticated and anonymous access
router.get('/api/content', optionalAuth, async (c) => {
const user = await c.var.auth.getUser().catch(() => null);
return c.json({ premium: !!user });
});
// Admin-only route
router.get('/api/admin', adminOnly, async (c) => {
return c.json({ message: 'Welcome, admin!' });
});API Key Middleware
For programmatic access via API keys:
import { createApiKeyMiddleware } from '@agentuity/auth';
import { auth } from '../auth';
const apiKeyAuth = createApiKeyMiddleware(auth);
const optionalApiKey = createApiKeyMiddleware(auth, { optional: true });
const writeAccess = createApiKeyMiddleware(auth, { hasPermission: { project: 'write' } });
const fullAccess = createApiKeyMiddleware(auth, { hasPermission: { project: ['read', 'write'], admin: '*' } });API keys are sent via headers: x-agentuity-auth-api-key: your_key or Authorization: ApiKey your_key
The Auth Context
When middleware authenticates a request, c.var.auth provides these methods:
| Method | Returns | Description |
|---|---|---|
getUser() | Promise<AuthUser> | Get the authenticated user |
getOrg() | Promise<AuthOrgContext | null> | Get active organization with full details |
getOrgRole() | Promise<string | null> | Get user's role in active org |
hasOrgRole(...roles) | Promise<boolean> | Check if user has one of the specified roles |
hasPermission(resource, ...actions) | boolean | Check API key permissions |
getToken() | Promise<string | null> | Get the bearer token from request |
authMethod | 'session' | 'api-key' | 'bearer' | How the request was authenticated |
apiKey | AuthApiKeyContext | null | API key details (if authenticated via API key) |
Example:
router.get('/api/profile', authMiddleware, async (c) => {
const user = await c.var.auth.getUser();
const org = await c.var.auth.getOrg();
const isAdmin = await c.var.auth.hasOrgRole('admin', 'owner');
return c.json({
user: { id: user.id, email: user.email, name: user.name },
organization: org ? { id: org.id, name: org.name, role: org.role } : null,
isAdmin,
});
});See the complete type definitions in the auth package types.
Client Setup
Creating the Auth Client
import { createAuthClient } from '@agentuity/auth/react';
export const authClient = createAuthClient();
// Export methods for use in components
export const { signIn, signUp, signOut, useSession, getSession } = authClient;AuthProvider
Wrap your app with AuthProvider. Add AgentuityProvider only if you also use @agentuity/react transport hooks and need AuthProvider to mirror the bearer token into them:
import { useState } from 'react';
import { AgentuityProvider } from '@agentuity/react';
import { AuthProvider, createAuthClient } from '@agentuity/auth/react';
import { App } from './App';
const authClient = createAuthClient();
export function Root() {
const [authHeader, setAuthHeader] = useState<string | null>(null);
return (
<AgentuityProvider authHeader={authHeader}>
<AuthProvider
authClient={authClient}
onAuthHeaderChange={setAuthHeader}
>
<App />
</AuthProvider>
</AgentuityProvider>
);
}useAuth Hook
Access auth state in your components:
import { useAuth } from '@agentuity/auth/react';
function Profile() {
const { user, isAuthenticated, isPending, error } = useAuth();
if (isPending) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!isAuthenticated) return <div>Please sign in</div>;
return (
<div>
<h1>Welcome, {user.name}!</h1>
<p>Email: {user.email}</p>
</div>
);
}Sign In / Sign Up / Sign Out
import { signIn, signUp, signOut } from './auth-client';
// Email/password sign in
await signIn.email({ email, password });
// Email/password sign up
await signUp.email({ email, password, name });
// Sign out
await signOut();Using Auth in Agents
The ctx.auth Interface
Auth is available natively on ctx.auth in agent handlers:
import { createAgent } from '@agentuity/runtime';
import { s } from '@agentuity/schema';
export default createAgent('protected', {
schema: {
input: s.object({ query: s.string() }),
output: s.object({ result: s.string(), userId: s.string() }),
},
handler: async (ctx, { query }) => {
if (!ctx.auth) {
return { result: 'Please sign in', userId: '' };
}
const user = await ctx.auth.getUser();
const org = await ctx.auth.getOrg();
// Check organization roles
if (org && await ctx.auth.hasOrgRole('admin')) {
// Admin-only logic
}
return { result: `Hello ${user.name}`, userId: user.id };
},
});Agent-to-Agent Auth Propagation
When one agent calls another, auth context propagates automatically:
import { createAgent } from '@agentuity/runtime';
import poemAgent from '../poem/agent';
export default createAgent('hello', {
handler: async (ctx, { name }) => {
const user = ctx.auth ? await ctx.auth.getUser() : null;
// Auth context passes through to poem agent automatically
const poem = await poemAgent.run({
userEmail: user?.email,
userName: name,
});
return `Hello ${name}!\n\n${poem}`;
},
});Environment Variables
| Variable | Description | Default |
|---|---|---|
DATABASE_URL | PostgreSQL connection string | Required |
AGENTUITY_AUTH_SECRET | Secret for signing tokens | Falls back to BETTER_AUTH_SECRET |
AGENTUITY_BASE_URL | Base URL for auth callbacks | Falls back to BETTER_AUTH_URL |
Auto-resolved trusted origins:
AGENTUITY_CLOUD_DOMAINS- Platform-set domains (deployment URLs, custom domains)AUTH_TRUSTED_DOMAINS- Developer-set additional trusted domains (comma-separated)
Generate a secure secret:
openssl rand -hex 32If you're using a development secret, generate a new one before deploying to production. Store it securely in your environment variables.
Database Configuration
Connection String (Simplest)
Just provide the connection string and Agentuity handles the rest:
import { createAuth } from '@agentuity/auth';
export const auth = createAuth({
connectionString: process.env.DATABASE_URL,
});Bring Your Own Drizzle
If you have an existing Drizzle setup:
import { drizzle } from 'drizzle-orm/bun-sql';
import { drizzleAdapter } from '@agentuity/drizzle';
import * as authSchema from '@agentuity/auth/schema';
import * as appSchema from './schema';
const schema = { ...authSchema, ...appSchema };
const db = drizzle(process.env.DATABASE_URL!, { schema });
export const auth = createAuth({
database: drizzleAdapter(db, { provider: 'pg', schema: authSchema }),
});The @agentuity/auth/schema export provides all auth-related Drizzle tables (user, session, account, verification, organization, member, invitation, apiKey).
Built-in Features
Organizations & Teams
Create and manage organizations:
// Create an organization
const org = await auth.api.createOrganization({
body: { name: 'My Team', slug: 'my-team' },
headers: c.req.raw.headers,
});
// Get user's role in active org
const role = await c.var.auth.getOrgRole();
// Check role
if (await c.var.auth.hasOrgRole('admin', 'owner')) {
// Admin actions
}See the BetterAuth Organization Plugin docs for the complete API including invitations, member management, and role configuration.
API Keys
Create API keys for programmatic access:
const result = await auth.api.createApiKey({
body: {
name: 'my-integration',
userId: user.id,
expiresIn: 60 * 60 * 24 * 30, // 30 days
permissions: { project: ['read', 'write'] },
},
});
console.log('API Key:', result.key); // Only shown once!See the BetterAuth API Key Plugin docs for listing, revoking, and permission schemas.
JWT & Bearer Tokens
// Get token in route handler
const token = await c.var.auth.getToken();
// JWKS endpoint: GET /api/auth/.well-known/jwks.jsonSee the BetterAuth JWT Plugin docs for token customization and verification.
CLI Commands
Initialize Auth
agentuity project auth init [options]| Option | Description |
|---|---|
--skipMigrations | Skip running database migrations |
--skipInstall | Skip installing dependencies |
If Drizzle or Prisma is detected in your project, init will skip automatic migrations and prompt you to run them with your ORM tools instead.
Generate Schema
Generate SQL schema for auth tables (useful for manual migrations):
agentuity project auth generateThis outputs the SQL needed to create all auth tables, which you can then run with your preferred migration tool.
Next Steps
- Middleware & Routes: Authentication and validation patterns
- Provider Setup:
AgentuityProviderconfiguration for@agentuity/reactapps - React Hooks: Building custom UIs
- Authentication Services: Platform auth overview with OAuth/OIDC