Skip to main content

Authorization

The authorization system answers two questions: "What can this entity access?" (GetEntitlements) and "Can this entity access this specific resource?" (GetDecision). For batch checks, use GetDecisionBulk.

Setup

All examples on this page assume you have created a platform client. See Authentication for full details including DPoP key binding.

client, err := sdk.New("http://localhost:8080",
sdk.WithClientCredentials("opentdf", "secret", nil),
)
if err != nil {
log.Fatal(err)
}

// All Go snippets below use `client` and `context.Background()`.
JavaScript examples use client credentials for simplicity

The JavaScript SDK is designed for browser applications. The examples on this page use clientCredentialsTokenProvider because it's self-contained and easy to follow, but it requires a client secret and must not be used in browser code.

In production browser apps, complete an OIDC login flow to obtain a refresh token, then use refreshTokenProvider(). See the Authentication Decision Guide for help choosing the right method.

EntityIdentifier

Every authorization call requires an EntityIdentifier — the entity (user, service, etc.) you're asking about. The entity can be identified by email, username, client ID, JWT token, claims, or registered resource FQN.

Go — use the v2 helper functions:

HelperDescription
authorizationv2.ForEmail(email)Identify by email address
authorizationv2.ForClientID(clientID)Identify by client ID (service account / NPE)
authorizationv2.ForUserName(username)Identify by username
authorizationv2.ForToken(jwt)Resolve entity from a JWT token
authorizationv2.WithRequestToken()Derive entity from the request's Authorization header
import authorizationv2 "github.com/opentdf/platform/protocol/go/authorization/v2"

req := &authorizationv2.GetDecisionRequest{
EntityIdentifier: authorizationv2.ForEmail("alice@example.com"),
// ...
}

Java — build the nested proto structure:

EntityIdentifier.newBuilder()
.setEntityChain(
EntityChain.newBuilder()
.addEntities(
Entity.newBuilder()
.setId("user-1")
.setEmailAddress("alice@example.com") // or .setClientId(), .setUserName(), etc.
)
)
.build()

JavaScript — use the identifier oneof:

{
entityIdentifier: {
identifier: {
case: 'entityChain',
value: {
entities: [
{
ephemeralId: 'user-1',
entityType: {
case: 'emailAddress', // or 'clientId', 'userName', 'token'
value: 'alice@example.com',
},
},
],
},
},
},
}

Supported entity types:

TypeGo helperJava setterJS case
EmailForEmail(email).setEmailAddress(email)'emailAddress'
Client IDForClientID(id).setClientId(id)'clientId'
UsernameForUserName(name).setUserName(name)'userName'
JWT TokenForToken(jwt).setToken(Token.newBuilder().setJwt(jwt))'token'
Claims.setClaims(claims)'claims'
Registered Resource.setRegisteredResourceValueFqn(fqn)'registeredResourceValueFqn'
  • Claims are used by the Entity Resolution Service (ERS) for custom claim-based entity resolution.
  • Registered Resource identifies an entity by a registered resource value FQN stored in platform policy, where the resource acts as a single entity for authorization decisions.

GetEntitlements

Returns all attribute values an entity is entitled to access. Use this for building UIs that show available data, pre-filtering content, or understanding an entity's overall access scope.

Signature

client.AuthorizationV2.GetEntitlements(ctx, &authorizationv2.GetEntitlementsRequest{...})

Parameters

ParameterTypeRequiredDescription
entityIdentifierEntityIdentifierYesThe entity to query. In Go, use helpers like authorizationv2.ForEmail("user@example.com").
withComprehensiveHierarchyboolNoWhen true, returns all entitled values for attributes with hierarchy rules, propagating down from the entitled value.

Example

import authorizationv2 "github.com/opentdf/platform/protocol/go/authorization/v2"

entitlementReq := &authorizationv2.GetEntitlementsRequest{
EntityIdentifier: authorizationv2.ForEmail("bob@OrgA.com"),
}

entitlements, err := client.AuthorizationV2.GetEntitlements(
context.Background(),
entitlementReq,
)
if err != nil {
log.Fatal(err)
}

for _, entitlement := range entitlements.GetEntitlements() {
fmt.Printf("Entity has access to: %v\n",
entitlement.GetActionsPerAttributeValueFqn())
}

To expand hierarchy rules, set WithComprehensiveHierarchy:

import (
authorizationv2 "github.com/opentdf/platform/protocol/go/authorization/v2"
"google.golang.org/protobuf/proto"
)

entitlementReq := &authorizationv2.GetEntitlementsRequest{
EntityIdentifier: authorizationv2.ForEmail("user@company.com"),
WithComprehensiveHierarchy: proto.Bool(true),
}

entitlements, err := client.AuthorizationV2.GetEntitlements(
context.Background(),
entitlementReq,
)
if err != nil {
log.Fatal(err)
}

for _, e := range entitlements.GetEntitlements() {
fmt.Printf("Entitled to: %v\n", e.GetActionsPerAttributeValueFqn())
}
V1 API (Legacy)
import (
"github.com/opentdf/platform/protocol/go/authorization"
"github.com/opentdf/platform/protocol/go/policy"
)

decisionRequests := []*authorization.DecisionRequest{{
Actions: []*policy.Action{{Name: "read"}},
EntityChains: []*authorization.EntityChain{{
Id: "ec1",
Entities: []*authorization.Entity{{
EntityType: &authorization.Entity_EmailAddress{
EmailAddress: "bob@OrgA.com",
},
Category: authorization.Entity_CATEGORY_SUBJECT,
}},
}},
ResourceAttributes: []*authorization.ResourceAttribute{{
AttributeValueFqns: []string{
"https://company.com/attr/clearance/value/public",
"https://company.com/attr/clearance/value/confidential",
},
}},
}}

decisionResponse, err := client.Authorization.GetDecisions(
context.Background(),
&authorization.GetDecisionsRequest{DecisionRequests: decisionRequests},
)
if err != nil {
log.Fatal(err)
}

for _, dr := range decisionResponse.GetDecisionResponses() {
fmt.Printf("Entity chain %s has decision: %v\n",
dr.GetEntityChainId(), dr.GetDecision())
}

Returns

A list of EntityEntitlements objects, each containing a map of attribute value FQNs to the actions the entity can perform on them.


GetDecision

Returns a permit/deny decision for a specific entity + action + resource combination. This is the enforcement point in your application.

Signature

client.AuthorizationV2.GetDecision(ctx, &authorizationv2.GetDecisionRequest{...})

Parameters

ParameterTypeRequiredDescription
entityIdentifierEntityIdentifierYesThe entity requesting access. In Go, use helpers like authorizationv2.ForEmail(...) or authorizationv2.ForToken(jwt).
actionActionYesThe action being performed (e.g., decrypt, read).
resourceResourceYesThe resource being accessed, identified by attribute value FQNs.

Example

import (
authorizationv2 "github.com/opentdf/platform/protocol/go/authorization/v2"
"github.com/opentdf/platform/protocol/go/policy"
)

decisionReq := &authorizationv2.GetDecisionRequest{
EntityIdentifier: authorizationv2.ForEmail("user@company.com"),
Action: &policy.Action{
Name: "decrypt",
},
Resource: &authorizationv2.Resource{
Resource: &authorizationv2.Resource_AttributeValues_{
AttributeValues: &authorizationv2.Resource_AttributeValues{
Fqns: []string{
"https://company.com/attr/clearance/value/confidential",
"https://company.com/attr/department/value/finance",
},
},
},
},
}

decision, err := client.AuthorizationV2.GetDecision(
context.Background(),
decisionReq,
)
if err != nil {
log.Fatal(err)
}

resDecision := decision.GetDecision()
if resDecision.GetDecision() == authorizationv2.Decision_DECISION_PERMIT {
fmt.Println("Access granted")
if len(resDecision.GetRequiredObligations()) > 0 {
fmt.Printf("Required obligations: %v\n", resDecision.GetRequiredObligations())
}
} else {
fmt.Println("Access denied")
}

Using a JWT token instead of email:

import (
authorizationv2 "github.com/opentdf/platform/protocol/go/authorization/v2"
"github.com/opentdf/platform/protocol/go/policy"
)

decisionReq := &authorizationv2.GetDecisionRequest{
EntityIdentifier: authorizationv2.ForToken(jwtToken),
Action: &policy.Action{Name: "decrypt"},
Resource: &authorizationv2.Resource{
Resource: &authorizationv2.Resource_AttributeValues_{
AttributeValues: &authorizationv2.Resource_AttributeValues{
Fqns: []string{"https://company.com/attr/clearance/value/public"},
},
},
},
}

decision, err := client.AuthorizationV2.GetDecision(
context.Background(),
decisionReq,
)
if err != nil {
log.Fatal(err)
}

fmt.Printf("Token-based decision: %v\n", decision.GetDecision().GetDecision())
V1 API (Legacy)
import (
"github.com/opentdf/platform/protocol/go/authorization"
"github.com/opentdf/platform/protocol/go/policy"
)

decisionRequests := []*authorization.DecisionRequest{{
Actions: []*policy.Action{{Name: "decrypt"}},
EntityChains: []*authorization.EntityChain{{
Id: "ec1",
Entities: []*authorization.Entity{{
EntityType: &authorization.Entity_EmailAddress{
EmailAddress: "user@company.com",
},
Category: authorization.Entity_CATEGORY_SUBJECT,
}},
}},
ResourceAttributes: []*authorization.ResourceAttribute{{
AttributeValueFqns: []string{
"https://company.com/attr/clearance/value/confidential",
"https://company.com/attr/department/value/finance",
},
}},
}}

decisionResponse, err := client.Authorization.GetDecisions(
context.Background(),
&authorization.GetDecisionsRequest{DecisionRequests: decisionRequests},
)
if err != nil {
log.Fatal(err)
}

for _, dr := range decisionResponse.GetDecisionResponses() {
if dr.GetDecision() == authorization.DecisionResponse_DECISION_PERMIT {
fmt.Println("Access granted")
if len(dr.GetObligations()) > 0 {
fmt.Printf("Obligations to fulfill: %v\n", dr.GetObligations())
}
} else {
fmt.Println("Access denied")
}
}

Returns

A ResourceDecision with a Decision (permit/deny) and any required obligations the consuming application must enforce.


GetDecisionBulk

Evaluates multiple entity + action + resource combinations in a single call. Each request can include multiple resources, and the response indicates whether all resources were permitted plus per-resource decisions.

Signature

client.AuthorizationV2.GetDecisionBulk(ctx, &authorizationv2.GetDecisionBulkRequest{...})

Parameters

ParameterTypeRequiredDescription
decisionRequests[]GetDecisionMultiResourceRequestYesEach entry contains an entity, action, and one or more resources to evaluate.

Each GetDecisionMultiResourceRequest contains:

FieldTypeRequiredDescription
entityIdentifierEntityIdentifierYesThe entity requesting access.
actionActionYesThe action being performed.
resources[]ResourceYesResources to evaluate, each with an ephemeralId for correlation.

Example

import (
authorizationv2 "github.com/opentdf/platform/protocol/go/authorization/v2"
"github.com/opentdf/platform/protocol/go/policy"
)

bulkReq := &authorizationv2.GetDecisionBulkRequest{
DecisionRequests: []*authorizationv2.GetDecisionMultiResourceRequest{
{
EntityIdentifier: authorizationv2.ForEmail("user@company.com"),
Action: &policy.Action{Name: "decrypt"},
Resources: []*authorizationv2.Resource{
{
EphemeralId: "resource-1",
Resource: &authorizationv2.Resource_AttributeValues_{
AttributeValues: &authorizationv2.Resource_AttributeValues{
Fqns: []string{"https://company.com/attr/class/value/public"},
},
},
},
{
EphemeralId: "resource-2",
Resource: &authorizationv2.Resource_AttributeValues_{
AttributeValues: &authorizationv2.Resource_AttributeValues{
Fqns: []string{"https://company.com/attr/class/value/confidential"},
},
},
},
},
},
},
}

decisions, err := client.AuthorizationV2.GetDecisionBulk(
context.Background(),
bulkReq,
)
if err != nil {
log.Fatal(err)
}

for _, resp := range decisions.GetDecisionResponses() {
allPermitted := resp.GetAllPermitted()
if allPermitted != nil {
fmt.Printf("All resources permitted: %v\n", allPermitted.GetValue())
}
for _, resourceDecision := range resp.GetResourceDecisions() {
fmt.Printf("Resource %s: %v\n",
resourceDecision.GetEphemeralResourceId(),
resourceDecision.GetDecision())
}
}
V1 API (Legacy)
import (
"github.com/opentdf/platform/protocol/go/authorization"
"github.com/opentdf/platform/protocol/go/policy"
)

decisionRequests := []*authorization.DecisionRequest{{
Actions: []*policy.Action{{Name: "decrypt"}},
EntityChains: []*authorization.EntityChain{{
Id: "ec1",
Entities: []*authorization.Entity{{
EntityType: &authorization.Entity_EmailAddress{
EmailAddress: "user@company.com",
},
Category: authorization.Entity_CATEGORY_SUBJECT,
}},
}},
ResourceAttributes: []*authorization.ResourceAttribute{
{AttributeValueFqns: []string{"https://company.com/attr/class/value/public"}},
{AttributeValueFqns: []string{"https://company.com/attr/class/value/confidential"}},
},
}}

decisionResponse, err := client.Authorization.GetDecisions(
context.Background(),
&authorization.GetDecisionsRequest{DecisionRequests: decisionRequests},
)
if err != nil {
log.Fatal(err)
}

for _, dr := range decisionResponse.GetDecisionResponses() {
fmt.Printf("Entity chain %s: %v\n", dr.GetEntityChainId(), dr.GetDecision())
}

Returns

A list of GetDecisionMultiResourceResponse objects, each containing an allPermitted flag and per-resource ResourceDecision entries.


Type Reference

Decision

The authorization result.

ValueDescription
DECISION_PERMITAccess is granted. Check requiredObligations for any controls the PEP must enforce.
DECISION_DENYAccess is denied.

ResourceDecision

The result for a single resource in a decision response.

FieldTypeDescription
ephemeralResourceIdstringCorrelates with the ephemeralId set on the request's Resource.
decisionDecisionPermit or deny.
requiredObligations[]stringObligation value FQNs the PEP must fulfill (e.g., https://example.com/obl/drm/value/watermarking). Empty if none required.

Resource

Identifies the data being accessed. A resource can be specified in two ways:

FieldTypeDescription
ephemeralIdstringAn ID you assign for correlating with the response. Used in bulk requests.
attributeValues.fqns[]stringAttribute value FQNs on the resource (1–20). Use this for TDF payloads or any resource identified by attribute values.
registeredResourceValueFqnstring (URI)A registered resource value FQN stored in platform policy. Alternative to attributeValues.
// Go
&authorizationv2.Resource{
EphemeralId: "resource-1",
Resource: &authorizationv2.Resource_AttributeValues_{
AttributeValues: &authorizationv2.Resource_AttributeValues{
Fqns: []string{"https://example.com/attr/classification/value/secret"},
},
},
}
// JavaScript
{
ephemeralId: 'resource-1',
resource: {
case: 'attributeValues',
value: { fqns: ['https://example.com/attr/classification/value/secret'] },
},
}

EntityEntitlements

Returned by GetEntitlements. One per entity, mapping attribute value FQNs to the actions that entity can perform.

FieldTypeDescription
ephemeralIdstringCorrelates with the entity in the request.
actionsPerAttributeValueFqnmap<string, ActionsList>Keys are attribute value FQNs. Values are lists of actions the entity can perform on data carrying that attribute value.
for _, e := range resp.GetEntitlements() {
for fqn, actions := range e.GetActionsPerAttributeValueFqn() {
fmt.Printf("FQN: %s → actions: %v\n", fqn, actions.GetActions())
}
}
for (const e of resp.entitlements) {
for (const [fqn, actions] of Object.entries(e.actionsPerAttributeValueFqn)) {
console.log(`FQN: ${fqn} → actions:`, actions.actions);
}
}

GetDecisionMultiResourceResponse

Returned by GetDecisionBulk. One per entity+action combination in the request.

FieldTypeDescription
allPermittedboolConvenience flag — true if every resource in this group was permitted.
resourceDecisions[]ResourceDecisionPer-resource results with individual decisions and obligations.

Typical Workflow

  1. During resource discovery: Use GetEntitlements to show users what data they can access — build UIs, pre-filter content, or understand an entity's overall access scope
  2. During resource access: Use GetDecision to enforce access controls when an entity attempts to access a specific resource
  3. For bulk operations: Use GetDecisionBulk for efficient batch authorization when checking multiple resources at once

Best Practices

Performance

  • Batch operations: Use GetDecisionBulk for multiple authorization checks instead of calling GetDecision in a loop
  • Cache entitlements: Cache GetEntitlements results when appropriate (consider TTL)
  • Scope queries: Use withComprehensiveHierarchy only when you need expanded hierarchy values

Security

  • Least privilege: Request only the minimum necessary permissions
  • Token validation: Ensure JWT tokens are properly validated before passing to ForToken
  • Obligation handling: Always process and fulfill returned obligations
  • Deny by default: If an authorization call fails, deny access rather than allowing it

Integration Pattern

import authorizationv2 "github.com/opentdf/platform/protocol/go/authorization/v2"

// Example: Authorization middleware
func authorizationMiddleware(next http.Handler, client *sdk.SDK) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
decision, err := client.AuthorizationV2.GetDecision(
r.Context(),
&authorizationv2.GetDecisionRequest{
EntityIdentifier: authorizationv2.WithRequestToken(),
Action: &policy.Action{Name: "access"},
Resource: resourceFromPath(r.URL.Path),
},
)
if err != nil || decision.GetDecision().GetDecision() != authorizationv2.Decision_DECISION_PERMIT {
http.Error(w, "Access denied", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}

Error Handling

import (
authorizationv2 "github.com/opentdf/platform/protocol/go/authorization/v2"
"github.com/opentdf/platform/protocol/go/policy"
)

decision, err := client.AuthorizationV2.GetDecision(context.Background(), req)

if err != nil {
// Log the error for debugging
log.Printf("Authorization error: %v", err)

// Deny by default (more secure)
handleAccessDenied()
return
}

// Process successful response
handleDecisionResponse(decision)