Skip to main content

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: "[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

Table of Contents