Skip to content

Commit 9cdbd89

Browse files
committed
chore(agents.md): update with auth/feature gating instructions
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent 7d81bf0 commit 9cdbd89

2 files changed

Lines changed: 260 additions & 0 deletions

File tree

.agents/api-endpoints-and-auth.md

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# API Endpoints and Authentication
2+
3+
This guide covers how to add new API endpoints and properly integrate them with the auth/permissions system.
4+
5+
## Architecture overview
6+
7+
Authentication and authorization flow through three layers:
8+
9+
1. **Global auth middleware** (`core/http/auth/middleware.go``auth.Middleware`) — applied to every request in `core/http/app.go`. Handles session cookies, Bearer tokens, API keys, and legacy API keys. Populates `auth_user` and `auth_role` in the Echo context.
10+
2. **Feature middleware** (`auth.RequireFeature`) — per-feature access control applied to route groups or individual routes. Checks if the authenticated user has the specific feature enabled.
11+
3. **Admin middleware** (`auth.RequireAdmin`) — restricts endpoints to admin users only.
12+
13+
When auth is disabled (no auth DB, no legacy API keys), all middleware becomes pass-through (`auth.NoopMiddleware`).
14+
15+
## Adding a new API endpoint
16+
17+
### Step 1: Create the handler
18+
19+
Write the endpoint handler in the appropriate package under `core/http/endpoints/`. Follow existing patterns:
20+
21+
```go
22+
// core/http/endpoints/localai/my_feature.go
23+
func MyFeatureEndpoint(app *application.Application) echo.HandlerFunc {
24+
return func(c echo.Context) error {
25+
// Use auth.GetUser(c) to get the authenticated user (may be nil if auth is disabled)
26+
user := auth.GetUser(c)
27+
28+
// Your logic here
29+
return c.JSON(http.StatusOK, result)
30+
}
31+
}
32+
```
33+
34+
### Step 2: Register routes
35+
36+
Add routes in the appropriate file under `core/http/routes/`. The file you use depends on the endpoint category:
37+
38+
| File | Category |
39+
|------|----------|
40+
| `routes/openai.go` | OpenAI-compatible API endpoints (`/v1/...`) |
41+
| `routes/localai.go` | LocalAI-specific endpoints (`/api/...`, `/models/...`, `/backends/...`) |
42+
| `routes/agents.go` | Agent pool endpoints (`/api/agents/...`) |
43+
| `routes/auth.go` | Auth endpoints (`/api/auth/...`) |
44+
| `routes/ui_api.go` | UI backend API endpoints |
45+
46+
### Step 3: Apply the right middleware
47+
48+
Choose the appropriate protection level:
49+
50+
#### No auth required (public)
51+
Exempt paths bypass auth entirely. Add to `isExemptPath()` in `middleware.go` or use the `/api/auth/` prefix (always exempt). Use sparingly — most endpoints should require auth.
52+
53+
#### Standard auth (any authenticated user)
54+
The global middleware already handles this. API paths (`/api/`, `/v1/`, etc.) automatically require authentication when auth is enabled. You don't need to add any extra middleware.
55+
56+
```go
57+
router.GET("/v1/my-endpoint", myHandler) // auth enforced by global middleware
58+
```
59+
60+
#### Admin only
61+
Pass `adminMiddleware` to the route. This is set up in `app.go` and passed to `Register*Routes` functions:
62+
63+
```go
64+
// In the Register function signature, accept the middleware:
65+
func RegisterMyRoutes(router *echo.Echo, app *application.Application, adminMiddleware echo.MiddlewareFunc) {
66+
router.POST("/models/apply", myHandler, adminMiddleware)
67+
}
68+
```
69+
70+
#### Feature-gated
71+
For endpoints that should be toggleable per-user, use feature middleware. There are two approaches:
72+
73+
**Approach A: Route-level middleware** (preferred for groups of related endpoints)
74+
75+
```go
76+
// In app.go, create the feature middleware:
77+
myFeatureMw := auth.RequireFeature(application.AuthDB(), auth.FeatureMyFeature)
78+
79+
// Pass it to the route registration function:
80+
routes.RegisterMyRoutes(e, app, myFeatureMw)
81+
82+
// In the routes file, apply to a group:
83+
g := e.Group("/api/my-feature", myFeatureMw)
84+
g.GET("", listHandler)
85+
g.POST("", createHandler)
86+
```
87+
88+
**Approach B: RouteFeatureRegistry** (preferred for individual OpenAI-compatible endpoints)
89+
90+
Add an entry to `RouteFeatureRegistry` in `core/http/auth/features.go`. The `RequireRouteFeature` global middleware will automatically enforce it:
91+
92+
```go
93+
var RouteFeatureRegistry = []RouteFeature{
94+
// ... existing entries ...
95+
{"POST", "/v1/my-endpoint", FeatureMyFeature},
96+
}
97+
```
98+
99+
## Adding a new feature
100+
101+
When you need a new toggleable feature (not just a new endpoint under an existing feature):
102+
103+
### 1. Define the feature constant
104+
105+
Add to `core/http/auth/permissions.go`:
106+
107+
```go
108+
const (
109+
// Add to the appropriate group:
110+
// Agent features (default OFF for new users)
111+
FeatureMyFeature = "my_feature"
112+
113+
// OR API features (default ON for new users)
114+
FeatureMyFeature = "my_feature"
115+
)
116+
```
117+
118+
Then add it to the appropriate slice:
119+
120+
```go
121+
// Default OFF — user must be explicitly granted access:
122+
var AgentFeatures = []string{..., FeatureMyFeature}
123+
124+
// Default ON — user has access unless explicitly revoked:
125+
var APIFeatures = []string{..., FeatureMyFeature}
126+
```
127+
128+
### 2. Add feature metadata
129+
130+
In `core/http/auth/features.go`, add to the appropriate `FeatureMetas` function so the admin UI can display it:
131+
132+
```go
133+
func AgentFeatureMetas() []FeatureMeta {
134+
return []FeatureMeta{
135+
// ... existing ...
136+
{FeatureMyFeature, "My Feature", false}, // false = default OFF
137+
}
138+
}
139+
```
140+
141+
### 3. Wire up the middleware
142+
143+
In `core/http/app.go`:
144+
145+
```go
146+
myFeatureMw := auth.RequireFeature(application.AuthDB(), auth.FeatureMyFeature)
147+
```
148+
149+
Then pass it to the route registration function.
150+
151+
### 4. Register route-feature mappings (if applicable)
152+
153+
If your feature gates standard API endpoints (like `/v1/...`), add entries to `RouteFeatureRegistry` in `features.go` instead of using per-route middleware.
154+
155+
## Accessing the authenticated user in handlers
156+
157+
```go
158+
import "github.com/mudler/LocalAI/core/http/auth"
159+
160+
func MyHandler(c echo.Context) error {
161+
// Get the user (nil when auth is disabled or unauthenticated)
162+
user := auth.GetUser(c)
163+
if user == nil {
164+
// Handle unauthenticated — or let middleware handle it
165+
}
166+
167+
// Check role
168+
if user.Role == auth.RoleAdmin {
169+
// admin-specific logic
170+
}
171+
172+
// Check feature access programmatically (when you need conditional behavior, not full blocking)
173+
if auth.HasFeatureAccess(db, user, auth.FeatureMyFeature) {
174+
// feature-specific logic
175+
}
176+
177+
// Check model access
178+
if !auth.IsModelAllowed(db, user, modelName) {
179+
return c.JSON(http.StatusForbidden, ...)
180+
}
181+
}
182+
```
183+
184+
## Middleware composition patterns
185+
186+
Middleware can be composed at different levels. Here are the patterns used in the codebase:
187+
188+
### Group-level middleware (agents pattern)
189+
```go
190+
// All routes in the group share the middleware
191+
g := e.Group("/api/agents", poolReadyMw, agentsMw)
192+
g.GET("", listHandler)
193+
g.POST("", createHandler)
194+
```
195+
196+
### Per-route middleware (localai pattern)
197+
```go
198+
// Individual routes get middleware as extra arguments
199+
router.POST("/models/apply", applyHandler, adminMiddleware)
200+
router.GET("/metrics", metricsHandler, adminMiddleware)
201+
```
202+
203+
### Middleware slice (openai pattern)
204+
```go
205+
// Build a middleware chain for a handler
206+
chatMiddleware := []echo.MiddlewareFunc{
207+
usageMiddleware,
208+
traceMiddleware,
209+
modelFilterMiddleware,
210+
}
211+
app.POST("/v1/chat/completions", chatHandler, chatMiddleware...)
212+
```
213+
214+
## Error response format
215+
216+
Always use `schema.ErrorResponse` for auth/permission errors to stay consistent with the OpenAI-compatible API:
217+
218+
```go
219+
return c.JSON(http.StatusForbidden, schema.ErrorResponse{
220+
Error: &schema.APIError{
221+
Message: "feature not enabled for your account",
222+
Code: http.StatusForbidden,
223+
Type: "authorization_error",
224+
},
225+
})
226+
```
227+
228+
Use these HTTP status codes:
229+
- `401 Unauthorized` — no valid credentials provided
230+
- `403 Forbidden` — authenticated but lacking permission
231+
- `429 Too Many Requests` — rate limited (auth endpoints)
232+
233+
## Usage tracking
234+
235+
If your endpoint should be tracked for usage (token counts, request counts), add the `usageMiddleware` to its middleware chain. See `core/http/middleware/usage.go` and how it's applied in `routes/openai.go`.
236+
237+
## Path protection rules
238+
239+
The global auth middleware classifies paths as API paths or non-API paths:
240+
241+
- **API paths** (always require auth when auth is enabled): `/api/`, `/v1/`, `/models/`, `/backends/`, `/backend/`, `/tts`, `/vad`, `/video`, `/stores/`, `/system`, `/ws/`, `/metrics`
242+
- **Exempt paths** (never require auth): `/api/auth/` prefix, anything in `appConfig.PathWithoutAuth`
243+
- **Non-API paths** (UI, static assets): pass through without auth — the React UI handles login redirects client-side
244+
245+
If you add endpoints under a new top-level path prefix, add it to `isAPIPath()` in `middleware.go` to ensure it requires authentication.
246+
247+
## Checklist
248+
249+
When adding a new endpoint:
250+
251+
- [ ] Handler in `core/http/endpoints/`
252+
- [ ] Route registered in appropriate `core/http/routes/` file
253+
- [ ] Auth level chosen: public / standard / admin / feature-gated
254+
- [ ] If feature-gated: constant in `permissions.go`, metadata in `features.go`, middleware in `app.go`
255+
- [ ] If new path prefix: added to `isAPIPath()` in `middleware.go`
256+
- [ ] If OpenAI-compatible: entry in `RouteFeatureRegistry`
257+
- [ ] If token-counting: `usageMiddleware` added to middleware chain
258+
- [ ] Error responses use `schema.ErrorResponse` format
259+
- [ ] Tests cover both authenticated and unauthenticated access

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This file is an index to detailed topic guides in the `.agents/` directory. Read
1111
| [.agents/coding-style.md](.agents/coding-style.md) | Code style, editorconfig, logging, documentation conventions |
1212
| [.agents/llama-cpp-backend.md](.agents/llama-cpp-backend.md) | Working on the llama.cpp backend — architecture, updating, tool call parsing |
1313
| [.agents/testing-mcp-apps.md](.agents/testing-mcp-apps.md) | Testing MCP Apps (interactive tool UIs) in the React UI |
14+
| [.agents/api-endpoints-and-auth.md](.agents/api-endpoints-and-auth.md) | Adding API endpoints, auth middleware, feature permissions, user access control |
1415

1516
## Quick Reference
1617

0 commit comments

Comments
 (0)