Skip to main content
API

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 and POST /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 in configureApp 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: "user@example.com",
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: "admin@test.com",
roles: ["admin"],
tier: "enterprise",
},
user: {
sub: "user-456",
email: "user@test.com",
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​

Table of Contents