API Authentication
VoltAgent Server supports pluggable authentication providers to secure your API endpoints. The authentication system is framework-agnostic and works with any server implementation.
Overview
Authentication in VoltAgent:
- Selective Protection - Only execution endpoints require authentication by default
- Pluggable Providers - Use JWT, Auth0, Supabase, or custom providers
- Automatic Context - User information is injected into agent/workflow context
- Flexible Configuration - Customize which routes require authentication
Default Route Protection
Public Routes (No Auth Required)
These endpoints are public by default:
// Management endpoints
GET /agents // List agents
GET /agents/:id // Get agent details
GET /workflows // List workflows
GET /workflows/:id // Get workflow details
// Documentation
GET / // Landing page
GET /doc // OpenAPI spec
GET /ui // Swagger UI
// Logs & monitoring
GET /api/logs // Get logs (HTTP)
// (Optional) GET /health // Only if you add a health route
Protected Routes (Auth Required)
These execution endpoints require authentication by default:
// Agent execution
POST /agents/:id/text // Generate text
POST /agents/:id/stream // Stream text
POST /agents/:id/object // Generate object
POST /agents/:id/stream-object // Stream object
// Workflow execution
POST /workflows/:id/execute // Execute workflow
POST /workflows/:id/stream // Stream workflow
Note:
POST /workflows/:id/executions/:executionId/suspend
andPOST /workflows/:id/executions/:executionId/resume
are currently NOT included in default protected patterns. If you need auth on these endpoints today, add a custom guard inconfigureApp
or wrap them behind your own routes. Future versions may include them by default.- WebSockets are currently unauthenticated. If you need auth on
/ws/*
, implement a custom provider or a proxy that enforces authentication.
## JWT Authentication
The built-in JWT provider supports standard JSON Web Tokens.
### Basic Setup
```typescript
import { VoltAgent } from "@voltagent/core";
import { honoServer } from "@voltagent/server-hono";
import { jwtAuth } from "@voltagent/server-core";
new VoltAgent({
agents: { myAgent },
server: honoServer({
auth: jwtAuth({
secret: process.env.JWT_SECRET || "your-secret-key",
}),
}),
});
Advanced Configuration
const authProvider = jwtAuth({
// JWT secret for verification
secret: process.env.JWT_SECRET,
// Map JWT payload to user object
mapUser: (payload) => ({
id: payload.sub,
email: payload.email,
name: payload.name,
roles: payload.roles || [],
tier: payload.tier || "free",
}),
// Additional public routes
publicRoutes: ["GET /api/public/*", "POST /api/webhooks/*"],
// JWT verification options
verifyOptions: {
algorithms: ["HS256", "RS256"],
audience: "https://api.example.com",
issuer: "https://auth.example.com",
},
});
new VoltAgent({
agents: { myAgent },
server: honoServer({ auth: authProvider }),
});
Creating JWT Tokens
For testing or simple implementations:
import { createJWT } from "@voltagent/server-core";
const token = createJWT(
{
sub: "user-123",
email: "[email protected]",
name: "John Doe",
roles: ["admin"],
tier: "premium",
},
"your-secret-key",
{
expiresIn: "24h",
audience: "https://api.example.com",
issuer: "https://auth.example.com",
}
);
console.log("Bearer", token);
Using Authentication
Making Authenticated Requests
Include the JWT token in the Authorization header:
# Get token (from your auth system)
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
# Make authenticated request
curl -X POST http://localhost:3141/agents/assistant/text \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"input": "Hello, who am I?"
}'
JavaScript Client
const token = await getAuthToken(); // Your auth logic
const response = await fetch("http://localhost:3141/agents/assistant/text", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
input: "Process my request",
}),
});
if (response.status === 401) {
console.error("Authentication failed");
// Refresh token or redirect to login
}
User Context Injection
When authenticated, user information is automatically injected into the request. The middleware adds the user object to body.context.user
and sets userId
.
Accessing User in Agent Hooks
The user context is available in the OperationContext
Map:
const agent = new Agent({
name: "ContextAwareAgent",
instructions: "You are a helpful assistant",
model: openai("gpt-4"),
hooks: {
onStart: async ({ agent, context }) => {
// User data is in the context Map
const user = context.context.get("user");
console.log("Processing request for:", user?.email);
// userId is directly available
console.log("User ID:", context.userId);
// Customize behavior based on user
if (user?.tier === "premium") {
console.log("Premium user detected");
}
},
onEnd: async ({ agent, context, output, error }) => {
const user = context.context.get("user");
console.log(`Request completed for user: ${user?.id}`);
},
},
});
Dynamic Instructions Based on User
You can use dynamic instructions to customize agent behavior:
const agent = new Agent({
name: "DynamicAgent",
instructions: ({ context }) => {
// Access user from the context Map
const user = context?.get("user");
if (user?.roles?.includes("admin")) {
return "You are an admin assistant with full access to all features.";
}
if (user?.tier === "premium") {
return "You are a premium assistant with advanced capabilities.";
}
return "You are a helpful assistant with standard features.";
},
model: openai("gpt-4"),
});
Workflow Context
Workflows receive user context in a similar way:
const workflow = new Workflow({
name: "UserWorkflow",
run: async (input, { context }) => {
// Context is a Map with user data
const user = context?.get("user");
const userId = context?.get("userId");
if (user?.roles?.includes("admin")) {
console.log("Admin workflow execution");
}
return {
processedBy: userId,
userTier: user?.tier,
};
},
});
Custom Auth Providers
Create your own authentication provider by implementing the AuthProvider
interface:
import type { AuthProvider } from "@voltagent/server-core";
export function customAuth(config: CustomAuthConfig): AuthProvider<Request> {
return {
type: "custom",
// Verify token and return user object
async verifyToken(token: string, request?: Request): Promise<any> {
// Your verification logic
const user = await verifyWithYourService(token);
if (!user) {
throw new Error("Invalid token");
}
return user;
},
// Extract token from request (optional)
extractToken(request: Request): string | undefined {
// Check Authorization header
const authHeader = request.headers.get("Authorization");
if (authHeader?.startsWith("Bearer ")) {
return authHeader.substring(7);
}
// Check cookie
const cookie = request.headers.get("Cookie");
const token = parseCookie(cookie, "auth_token");
if (token) return token;
// Check query parameter
const url = new URL(request.url);
return url.searchParams.get("token") || undefined;
},
// Additional public routes
publicRoutes: ["GET /api/status", "POST /api/login"],
};
}
Error Responses
Authentication failures return consistent error responses:
401 - No Token
{
"success": false,
"error": "Authentication required"
}
401 - Invalid Token
{
"success": false,
"error": "Invalid token: jwt malformed"
}
401 - Expired Token
{
"success": false,
"error": "Token expired"
}
Security Best Practices
1. Secure Token Storage
Never store JWT secrets in code:
// ❌ Bad
const secret = "my-secret-key";
// ✅ Good
const secret = process.env.JWT_SECRET;
if (!secret) {
throw new Error("JWT_SECRET environment variable is required");
}
2. Use HTTPS in Production
Always use HTTPS to prevent token interception:
// Production configuration
if (process.env.NODE_ENV === "production") {
if (!request.url.startsWith("https://")) {
throw new Error("HTTPS required in production");
}
}
3. Token Expiration
Set reasonable expiration times:
createJWT(payload, secret, {
expiresIn: "15m", // Short-lived for sensitive operations
// or
expiresIn: "7d", // Longer for less sensitive apps
});
4. Refresh Tokens
Implement refresh token logic for long sessions:
// Public route for token refresh
publicRoutes: (["POST /auth/refresh"],
// In your refresh handler
app.post("/auth/refresh", async (c) => {
const refreshToken = c.req.header("X-Refresh-Token");
if (validateRefreshToken(refreshToken)) {
const newToken = createJWT(payload, secret, { expiresIn: "15m" });
return c.json({ token: newToken });
}
return c.json({ error: "Invalid refresh token" }, 401);
}));
5. Rate Limiting
Protect auth endpoints from brute force:
import { rateLimiter } from "hono-rate-limiter";
server: honoServer({
configureApp: (app) => {
// Rate limit auth endpoints
app.use(
"/agents/*/text",
rateLimiter({
windowMs: 15 * 60 * 1000, // 15 minutes
limit: 100, // Max 100 requests per window
})
);
},
auth: jwtAuth({ secret }),
});
Testing Authentication
Generate Test Tokens
Create a test token generator:
// test-token.ts
import { createJWT } from "@voltagent/server-core";
const testUsers = {
admin: {
sub: "admin-123",
email: "[email protected]",
roles: ["admin"],
tier: "enterprise",
},
user: {
sub: "user-456",
email: "[email protected]",
roles: ["user"],
tier: "free",
},
};
const token = createJWT(testUsers.admin, process.env.JWT_SECRET || "test-secret", {
expiresIn: "1h",
});
console.log(`Bearer ${token}`);
Test Protected Endpoints
# Without token (should fail)
curl -X POST http://localhost:3141/agents/assistant/text \
-H "Content-Type: application/json" \
-d '{"input": "Hello"}'
# Response: 401 {"success": false, "error": "Authentication required"}
# With token (should succeed)
curl -X POST http://localhost:3141/agents/assistant/text \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"input": "Hello"}'
# Response: 200 {"success": true, "data": {...}}
Next Steps
- Explore Custom Endpoints to add auth-protected routes
- Learn about Streaming with authentication
- Check Agent Endpoints for authenticated requests