Go SDK
Install
go get github.com/vengtoo/vengtoo-go
Source: github.com/vengtoo/vengtoo-go
Quick start
package main
import (
"context"
"fmt"
"log"
vengtoo "github.com/vengtoo/vengtoo-go"
)
func main() {
client := vengtoo.NewClient("azx_...")
decision, err := client.Check(context.Background(),
vengtoo.Subject{ExternalID: "user_2xK9mP", Type: "user"},
"read",
vengtoo.Resource{ExternalID: "doc_7fQr1N", Type: "document"},
)
if err != nil {
log.Fatal(err)
}
fmt.Println("Decision:", decision)
}
ExternalID is whatever ID your system already assigns — a database primary key, a ULID, a slug. ID is also accepted if you have Vengtoo's internal UUID.
Using with the local agent
client := vengtoo.NewClient("", vengtoo.WithBaseURL("http://localhost:8181"))
Same API, no code changes needed.
Full response
resp, err := client.Authorize(ctx, &vengtoo.AuthorizeRequest{
Subject: vengtoo.Subject{ExternalID: "user_2xK9mP", Type: "user"},
Resource: vengtoo.Resource{ExternalID: "doc_7fQr1N", Type: "document"},
Action: vengtoo.Action{Name: "read"},
Context: map[string]interface{}{"ip": "10.0.0.1"},
})
// resp.Decision, resp.Context.Reason, resp.Context.PolicyID, resp.Context.AccessPath
Middleware
The examples below demonstrate type-level authorization — checking whether a subject can perform an action on a resource type, without targeting a specific instance. This is the most common starting point and works for the majority of use cases.
How you implement middleware in practice will vary based on where your subject ID lives (JWT claim, session, header), how your routes are structured, and whether you need instance-level checks. Adapt these to fit your setup.
- Gin
- Chi
- Fiber
func AuthzMiddleware(client *vengtoo.Client, resourceType, action string) gin.HandlerFunc {
return func(c *gin.Context) {
// The subject ExternalID is whatever ID your system uses for this actor —
// a JWT sub claim, a session user ID, or a primary key from your own database.
allowed, err := client.Check(c.Request.Context(),
vengtoo.Subject{ExternalID: c.GetHeader("X-User-ID"), Type: "user"},
action,
vengtoo.Resource{Type: resourceType},
)
if err != nil || !allowed {
c.AbortWithStatusJSON(403, gin.H{"error": "forbidden"})
return
}
c.Next()
}
}
router.GET("/documents/:id", AuthzMiddleware(client, "document", "read"), handler)
router.POST("/documents", AuthzMiddleware(client, "document", "create"), handler)
func AuthzMiddleware(client *vengtoo.Client, resourceType, action string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// The subject ExternalID is whatever ID your system uses for this actor —
// a JWT sub claim, a session user ID, or a primary key from your own database.
allowed, err := client.Check(r.Context(),
vengtoo.Subject{ExternalID: r.Header.Get("X-User-ID"), Type: "user"},
action,
vengtoo.Resource{Type: resourceType},
)
if err != nil || !allowed {
http.Error(w, `{"error":"forbidden"}`, http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
}
r.Get("/documents/{id}", AuthzMiddleware(client, "document", "read")(handler))
r.Post("/documents", AuthzMiddleware(client, "document", "create")(handler))
func AuthzMiddleware(client *vengtoo.Client, resourceType, action string) fiber.Handler {
return func(c *fiber.Ctx) error {
// The subject ExternalID is whatever ID your system uses for this actor —
// a JWT sub claim, a session user ID, or a primary key from your own database.
allowed, err := client.Check(c.Context(),
vengtoo.Subject{ExternalID: c.Get("X-User-ID"), Type: "user"},
action,
vengtoo.Resource{Type: resourceType},
)
if err != nil || !allowed {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "forbidden"})
}
return c.Next()
}
}
app.Get("/documents/:id", AuthzMiddleware(client, "document", "read"), handler)
app.Post("/documents", AuthzMiddleware(client, "document", "create"), handler)
Batch evaluation
Check multiple subject/resource/action combinations in a single request (up to 50):
evals := []vengtoo.BatchEvalItem{
{Subject: &vengtoo.Subject{ExternalID: "user_2xK9mP", Type: "user"}, Action: &vengtoo.Action{Name: "read"}, Resource: &vengtoo.Resource{ExternalID: "doc_7fQr1N", Type: "document"}},
{Subject: &vengtoo.Subject{ExternalID: "user_2xK9mP", Type: "user"}, Action: &vengtoo.Action{Name: "delete"}, Resource: &vengtoo.Resource{ExternalID: "doc_7fQr1N", Type: "document"}},
{Subject: &vengtoo.Subject{ExternalID: "user_9pLx3T", Type: "user"}, Action: &vengtoo.Action{Name: "read"}, Resource: &vengtoo.Resource{ExternalID: "doc_7fQr1N", Type: "document"}},
}
results, err := client.CheckBatch(ctx, &vengtoo.BatchEvaluationRequest{Evaluations: evals})
if err != nil {
log.Fatal(err)
}
for i, allowed := range results {
fmt.Printf("%s %s on %s → %v\n",
evals[i].Subject.ExternalID,
evals[i].Action.Name,
evals[i].Resource.ExternalID,
allowed,
)
}
// user_2xK9mP read on doc_7fQr1N → true
// user_2xK9mP delete on doc_7fQr1N → false
// user_9pLx3T read on doc_7fQr1N → false
Results come back in the same order as the input. Keeping a reference to evals lets you zip decisions back to their inputs by index.
For per-item reason codes and access paths use AuthorizeBatch, which returns *BatchEvaluationResponse.
OAuth2 client credentials
Use a client ID and secret instead of an API key — the SDK exchanges them for a short-lived bearer token automatically and refreshes it before expiry:
client := vengtoo.NewClient("",
vengtoo.WithOAuth("client_abc123", "azx_cs_secret"),
)
Options
vengtoo.NewClient(apiKey,
vengtoo.WithBaseURL("http://localhost:8181"),
vengtoo.WithHTTPClient(customClient),
vengtoo.WithTimeout(5 * time.Second),
vengtoo.WithRetries(3),
)
Error handling
resp, err := client.Authorize(ctx, req)
if err != nil {
if vengtoo.IsAuthError(err) {
// 401 — invalid or expired API key
}
if vengtoo.IsOAuthError(err) {
// OAuth token exchange failed — bad client ID or secret
}
if vengtoo.IsNotFound(err) {
// 404 — subject or resource external_id not found
}
if vengtoo.IsServerError(err) {
// 5xx — server error (already retried)
}
}
Types
| Type | Fields |
|---|---|
Subject | Type (required), ID, ExternalID, Properties |
Resource | Type (required), ID, ExternalID, Properties. If neither ID nor ExternalID is set, "*" is sent automatically for type-level policy matching. |
Action | Name |
AuthorizeRequest | Subject, Resource, Action, Context |
AuthorizeResponse | Decision, Context |
ResponseContext | Reason, PolicyID, AccessPath |
BatchEvalItem | Subject, Action, Resource (all pointers) |
BatchEvaluationRequest | Evaluations []BatchEvalItem (max 50) |
BatchEvaluationResponse | Evaluations []AuthorizeResponse |