Authz is a Go authorization library for resources, principals, roles, feature entitlements and entity permissions. Embed it in a service to evaluate policies and return decisions with mandatory entity bounds. Your application supplies trusted identity facts and enforces the result before returning protected data.
- One authorization model for reports, tools, agents, UI resources and data APIs.
- Explicit data boundaries: entity grants constrain access even when a role or another branch of a policy matches.
- Host-owned trust: configure your identity provider, tenant mapping, policy store and administration roles for your application.
- Small core: the root package uses only the Go standard library. OAuth and Datly integration are available when your host needs them.
- Public and protected action policies with subject, role, exposure, entity,
allandanyrules - Mandatory OAuth scope checks and typed entity bounds outside
anybranches - Exact per-entity permissions without implicit inheritance
- Immutable policy revisions and atomic replacement with conflict detection
- Verified JWT/JWKS with injected identity and entity authorities
- Account-bound gates, entitlement checks and an optional durable file registry
- Datly HTTP/MCP components, SQLite/MySQL persistence and a runnable host
| Import path | Purpose | Go module |
|---|---|---|
github.com/viant/authz |
Core types, evaluation, scope intersection, policy services and administration | Core |
github.com/viant/authz/oauth |
Verified JWT/JWKS and generic host bindings | Core |
github.com/viant/authz/gating |
Mandatory gates, versioned decisions, administration and an optional single-process file registry | Core |
github.com/viant/authz/component |
Authz Components, persistence and host | Separate nested module |
oauth and gating are packages in the core module; component has its own
go.mod. Datly and Studio are not dependencies of the core. Studio and
agently-core consume the shared core types directly.
The core module requires Go 1.25.5 or newer; the optional component module requires Go 1.25.8 or newer. To build and test a source checkout:
git clone https://github.com/viant/authz.git
cd authz
go test ./...Import github.com/viant/authz in your Go application. Consumer integration
currently uses local module mappings while releases are prepared; see
local development before integrating
from an independent checkout.
This runnable example shows a role policy constrained to the caller's allowed
projects. Save it as main.go in a temporary directory and run go run with its
absolute file path from the checkout. The facts are a fixture for this example;
a service must obtain them from a trusted Provider after verifying credentials.
package main
import (
"fmt"
"time"
"github.com/viant/authz"
)
func main() {
resource := authz.Resource{
Kind: "report", ID: "usage", Version: "1", Tenant: "team-a",
}
policies := map[string]authz.Policy{
"execute": {
Mode: "protected",
Rule: &authz.Rule{Kind: "role", Value: "analyst"},
EntityType: "project",
},
}
facts := authz.Facts{
Subject: "alice", Tenant: "team-a", Issuer: "https://idp.example.com",
Roles: []string{"analyst"},
EntityGroups: authz.EntityGroups{"project": {"101", "102"}},
ValidUntil: time.Now().Add(time.Minute),
}
decision, err := authz.Evaluate(authz.Request{
Resource: resource, Action: "execute",
}, policies, facts, time.Now())
if err != nil {
panic(err) // Deny the operation on any authorization error.
}
// Example enforcement: release only rows within the returned bounds.
allowed := make(map[authz.Entity]bool)
for _, entity := range decision.Entities {
allowed[entity] = true
}
for _, projectID := range []string{"101", "102", "999"} {
if decision.Bounded && !allowed[authz.Entity{Type: "project", ID: projectID}] {
continue
}
fmt.Println("allowed project:", projectID)
}
}Output:
allowed project: 101
allowed project: 102
For stored policies and request-scoped identity, use Service.Authorize with
Store and Provider. NewStaticStore serves immutable policy documents;
mutable administration needs a writable store. Push entity constraints into the
data query when possible, and reject bounded decisions if your operation cannot
enforce them.
Authz evaluates authorization; the host verifies credentials, selects the
resource and tenant, resolves trusted facts, and enforces decisions. Never copy
roles, entity grants or identity from an operation request into Facts. Treat
any authorization error as a denied operation. Reading a policy or hiding a UI
control does not authorize execution. A successful bounded decision grants only
the returned entities.
The optional OAuth adapters verify host-configured issuers, audiences and keys. Private identity payloads, account-to-tenant mappings, permission vocabulary and business inheritance belong to host-owned providers. Public code has no Viant ID-token, user-info or selected-evaluation wire contract.
A resource is identified by kind, ID, tenant and version. Service.Authorize
checks one action against its stored policy using server-resolved Facts.
Policies support subject, role, exposure, entity, all and any conditions.
Protected policies may also declare requiredScopes, a list of OAuth scope
names checked together with the rule and outside its any branches. Every
required scope must be present in Facts.GrantedScopes; absent scopes deny.
These credential grants are distinct from the entity bounds in Decision.
Decision.Bounded means callers must enforce the returned entity IDs. An empty
or incompatible intersection denies access. Client-supplied filters cannot widen
server-owned entity grants. Reading a policy does not authorize its resource.
Service.AuthorizeWithStatus retains the immutable revision and distinguishes
explicit denial or a missing policy from provider/store outages for gate hosts;
the original Authorize methods retain their existing compatibility behavior.
Facts.EntityGroups is the canonical allowedEntities map. Entity IDs preserve
strings and full-width integers; they are never rounded through float64. Providers
must verify the credential and supply subject, tenant, issuer and an expiry lease.
The library does not trust roles or entity IDs from operation request bodies.
The signed OAuth fact-token provider reads granted scopes from its verified
space-delimited scope claim. A user-info binding must not promote displayed
scopes to credential authority.
oauth.VerifyToken verifies generic JWT claims supplied by a host; it never
interprets private identity fields. oauth.NewIdentity returns registered
subject/issuer/expiry only and grants no tenant, role or entity authority.
oauth.NewStaticAuthorization requires an injected IdentityAuthority and an
explicit policy-tenant validator. Entity checks use an injected
gating.EntityPermissionProvider; capability projections can retain source
leases through NewLeasedCapabilityPermissionResolver. Numeric user/account
claims and provider HTTP DTOs are intentionally outside this module.
Unknown JWKS key IDs cannot force per-request fetches; rotation becomes visible
at the configured refresh lease.
Facts.EntityPermissions is a list of EntityPermission records, with type, ID
and a list of opaque permissions beside each ID. It is not a map or an entity
hierarchy. HasPermission checks exact identity and permission membership; a
permission named admin does not imply a global role, ancestor inheritance or
cross-namespace access. Business logic defines the meaning of permission names.
Signed OAuth facts can carry this list; clients cannot supply verified grants.
An optional oauth.NewEntityRoleResolver projects only explicitly mapped
permissions on the exact selected entity into UI resource-role labels. Global
user roles and unrelated entity permissions are not copied into that list.
[{"type":"advertiser","id":"123","permissions":["read"]},
{"type":"advertiser","id":"456","permissions":["read","edit"]}]Administration.Get currently permits every authenticated caller within its
tenant. Anonymous access is disabled. Policy reads are available to signed-in callers in the same tenant; execution
permissions are evaluated separately.
Administration.Create and Replace require membership in the configured
EditorRoles. No editor roles are enabled by default. The server owns this list;
a policy document or client request cannot change it. Creation cannot overwrite
an existing resource. Replacements compare an expected revision atomically in
storage. A policy's execute/discover rules remain independent of administration.
administration := &authz.Administration{
Store: store,
Provider: verifiedProvider,
EditorRoles: []string{"policy_admin"}, // deployment-defined example
}The components are the SDK endpoints. HTTP and MCP use the same input/output contracts, handlers, trusted capabilities and policy rules. Other Datly projects can import the components into their bootstrap/runtime; exposure and providers remain the host's configuration. See Authz Components.
agently-core's service/policy.AuthzResolver consumes the shared core types and
a host-configured resource mapping. It never treats request context metadata as
identity facts. Its whole-resource consumer rejects bounded decisions because it
cannot enforce a row/entity scope. This adapter does not change existing policy
configuration or automatically enable authorization on an agent.
go test ./...
go test -race ./...
cd component
go test ./...
go build -o bin/authz ./cmd/authzThe extracted reader/writer components have SQLite persistence/transaction tests
and an authorized local MySQL integration test. Native HTTP/MCP tests cover reads,
configured-role creation/modification, forged role input, bounded decisions and
revision conflicts. MySQL integration uses AUTHZ_MYSQL_TEST_DSN and creates a
unique disposable database; do not point it at a production server.
Consumer repositories currently use explicit local module mappings for source
development. Publish/version both Go modules and replace those mappings with
release pins before deployment from an independent checkout. Root go test ./...
does not traverse the nested component module, so run its checks separately.
Apache License 2.0 — see LICENSE and NOTICE.
This product includes software developed at Viant (http://viantinc.com/).
Selector resolves one resource version from a trusted SelectionStore and
then authorizes that exact version through the existing policy service. It is
generic: an MCP server, component, skill or other resource can be selected.
The host owns endpoint routing, publication, runtime loading and session pinning.
Mapping revisions, resource versions and policy revisions remain separate.
family := authz.ResourceFamily{Kind: "mcp", ID: "studio", Tenant: "team"}
mappings, err := authz.NewStaticSelectionStore([]authz.SelectionDocument{{
Resource: family, Revision: 1, DefaultVersion: "v1",
Overrides: []authz.VersionOverride{{
Version: "v2", RequiredExposures: []string{"studio-preview"}, Priority: 10,
}},
}})
if err != nil { return err }
selector := authz.Selector{Mappings: mappings, Service: policyService}
selected, err := selector.Authorize(ctx, authz.SelectionRequest{
Resource: family, Action: "execute",
})
if err != nil { return err }
// Route only to selected.Resource.Version and enforce selected.Decision bounds.Each mapping has exactly one default version. All listed exposures on an override
must match verified provider facts; the highest matching priority wins. Override
priorities are unique, so multiple matching features are deterministic. Without
matching features the default is selected. An alternate's missing or denied
policy never causes fallback to a less restrictive default. Missing mappings deny;
provider/store outages fail closed. A mapping with overrides requires verified,
unexpired identity even when the default is ultimately selected. A mapping with
no overrides and a public * tenant policy supports anonymous access.
The selector resolves identity once per operation, preventing different identity
snapshots for selection and policy evaluation. Authorize returns the selected
resource, mapping revision, policy revision and entity decision. Hosts should use
it consistently for discovery and execution; differing client schemas across
versions may require a host-owned session pin or separate tool names.
selector.FindPolicy(ctx, family) finds the selected version's current policy
and checks unbounded viewAccess against that same document and identity before
returning it. It does not treat execution permission as policy-read permission.
Static mappings are cloned and immutable; reload configuration by constructing
a new store. Dynamic persistence can implement SelectionStore with atomic,
versioned mapping snapshots; this package does not add an endpoint table or SQL
migration for the host.