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.
- Go
- Java
- JavaScript
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()`.
SDK sdk = SDKBuilder.newBuilder()
.platformEndpoint("http://localhost:8080")
.clientSecret("opentdf", "secret")
.useInsecurePlaintextConnection(true)
.build();
// All Java snippets below use `sdk`.
import { authTokenInterceptor, clientCredentialsTokenProvider } from '@opentdf/sdk';
import { PlatformClient } from '@opentdf/sdk/platform';
const platformClient = new PlatformClient({
interceptors: [authTokenInterceptor(clientCredentialsTokenProvider({
clientId: 'opentdf', clientSecret: 'secret',
oidcOrigin: 'http://localhost:8080/auth/realms/opentdf',
}))],
platformUrl: 'http://localhost:8080',
});
// All JavaScript snippets below use `platformClient`.
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:
| Helper | Description |
|---|---|
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:
| Type | Go helper | Java setter | JS case |
|---|---|---|---|
ForEmail(email) | .setEmailAddress(email) | 'emailAddress' | |
| Client ID | ForClientID(id) | .setClientId(id) | 'clientId' |
| Username | ForUserName(name) | .setUserName(name) | 'userName' |
| JWT Token | ForToken(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
- Go
- Java
- JavaScript
client.AuthorizationV2.GetEntitlements(ctx, &authorizationv2.GetEntitlementsRequest{...})
sdk.getServices().authorization().getEntitlements(req).get()
await platformClient.v2.authorization.getEntitlements({ ... })
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
entityIdentifier | EntityIdentifier | Yes | The entity to query. In Go, use helpers like authorizationv2.ForEmail("user@example.com"). |
withComprehensiveHierarchy | bool | No | When true, returns all entitled values for attributes with hierarchy rules, propagating down from the entitled value. |
Example
- Go
- Java
- JavaScript
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())
}
GetEntitlementsRequest request = GetEntitlementsRequest.newBuilder()
.setEntityIdentifier(
EntityIdentifier.newBuilder()
.setEntityChain(
EntityChain.newBuilder()
.addEntities(
Entity.newBuilder()
.setId("user-bob")
.setEmailAddress("bob@OrgA.com")
)
)
)
.build();
GetEntitlementsResponse resp = sdk.getServices()
.authorization()
.getEntitlements(request)
.get();
for (EntityEntitlements entitlement : resp.getEntitlementsList()) {
System.out.println("Entitled to: " +
entitlement.getActionsPerAttributeValueFqnMap());
}
const response = await platformClient.v2.authorization.getEntitlements({
entityIdentifier: {
identifier: {
case: 'entityChain',
value: {
entities: [
{
ephemeralId: 'user-bob',
entityType: {
case: 'emailAddress',
value: 'bob@OrgA.com',
},
},
],
},
},
},
});
for (const entitlement of response.entitlements) {
console.log('Entitled to:', entitlement.actionsPerAttributeValueFqn);
}
To expand hierarchy rules:
const response = await platformClient.v2.authorization.getEntitlements({
entityIdentifier: {
identifier: {
case: 'entityChain',
value: {
entities: [
{
ephemeralId: 'user-123',
entityType: { case: 'emailAddress', value: 'user@company.com' },
},
],
},
},
},
withComprehensiveHierarchy: true,
});
console.log('Scoped entitlements:', response.entitlements);
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
- Go
- Java
- JavaScript
client.AuthorizationV2.GetDecision(ctx, &authorizationv2.GetDecisionRequest{...})
sdk.getServices().authorization().getDecision(req).get()
await platformClient.v2.authorization.getDecision({ ... })
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
entityIdentifier | EntityIdentifier | Yes | The entity requesting access. In Go, use helpers like authorizationv2.ForEmail(...) or authorizationv2.ForToken(jwt). |
action | Action | Yes | The action being performed (e.g., decrypt, read). |
resource | Resource | Yes | The resource being accessed, identified by attribute value FQNs. |
Example
- Go
- Java
- JavaScript
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")
}
}
GetDecisionRequest request = GetDecisionRequest.newBuilder()
.setEntityIdentifier(
EntityIdentifier.newBuilder()
.setEntityChain(
EntityChain.newBuilder()
.addEntities(
Entity.newBuilder()
.setId("user-123")
.setEmailAddress("user@company.com")
)
)
)
.setAction(
Action.newBuilder()
.setName("decrypt")
)
.setResource(
Resource.newBuilder()
.setAttributeValues(
Resource.AttributeValues.newBuilder()
.addFqns("https://company.com/attr/clearance/value/confidential")
.addFqns("https://company.com/attr/department/value/finance")
)
)
.build();
GetDecisionResponse resp = sdk.getServices()
.authorization()
.getDecision(request)
.get();
Decision decision = resp.getDecision();
if (decision.getDecision() == Decision.DECISION_PERMIT) {
System.out.println("Access granted");
if (decision.getObligationsCount() > 0) {
System.out.println("Obligations to fulfill: " + decision.getObligationsList());
}
} else {
System.out.println("Access denied");
}
import { Decision } from '@opentdf/sdk/platform/authorization/v2/authorization_pb.js';
const response = await platformClient.v2.authorization.getDecision({
entityIdentifier: {
identifier: {
case: 'entityChain',
value: {
entities: [
{
ephemeralId: 'user-123',
entityType: { case: 'emailAddress', value: 'user@company.com' },
},
],
},
},
},
action: { name: 'decrypt' },
resource: {
resource: {
case: 'attributeValues',
value: {
fqns: [
'https://company.com/attr/clearance/value/confidential',
'https://company.com/attr/department/value/finance',
],
},
},
},
});
const decision = response.decision;
if (decision?.decision === Decision.PERMIT) {
console.log('Access granted');
if (decision.requiredObligations.length > 0) {
console.log('Required obligations:', decision.requiredObligations);
}
} else {
console.log('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
- Go
- Java
- JavaScript
client.AuthorizationV2.GetDecisionBulk(ctx, &authorizationv2.GetDecisionBulkRequest{...})
sdk.getServices().authorization().getDecisionBulk(req).get()
await platformClient.v2.authorization.getDecisionBulk({ ... })
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
decisionRequests | []GetDecisionMultiResourceRequest | Yes | Each entry contains an entity, action, and one or more resources to evaluate. |
Each GetDecisionMultiResourceRequest contains:
| Field | Type | Required | Description |
|---|---|---|---|
entityIdentifier | EntityIdentifier | Yes | The entity requesting access. |
action | Action | Yes | The action being performed. |
resources | []Resource | Yes | Resources to evaluate, each with an ephemeralId for correlation. |
Example
- Go
- Java
- JavaScript
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())
}
GetDecisionBulkRequest request = GetDecisionBulkRequest.newBuilder()
.addDecisionRequests(
GetDecisionMultiResourceRequest.newBuilder()
.setEntityIdentifier(
EntityIdentifier.newBuilder()
.setEntityChain(
EntityChain.newBuilder()
.addEntities(
Entity.newBuilder()
.setId("user-123")
.setEmailAddress("user@company.com")
)
)
)
.setAction(Action.newBuilder().setName("decrypt"))
.addResources(
Resource.newBuilder()
.setEphemeralId("resource-1")
.setAttributeValues(
Resource.AttributeValues.newBuilder()
.addFqns("https://company.com/attr/class/value/public")
)
)
.addResources(
Resource.newBuilder()
.setEphemeralId("resource-2")
.setAttributeValues(
Resource.AttributeValues.newBuilder()
.addFqns("https://company.com/attr/class/value/confidential")
)
)
)
.build();
GetDecisionBulkResponse resp = sdk.getServices()
.authorization()
.getDecisionBulk(request)
.get();
for (GetDecisionMultiResourceResponse response : resp.getDecisionResponsesList()) {
if (response.hasAllPermitted()) {
System.out.println("All resources permitted: " + response.getAllPermitted().getValue());
}
for (ResourceDecision resourceDecision : response.getResourceDecisionsList()) {
System.out.println("Resource " + resourceDecision.getEphemeralResourceId() +
": " + resourceDecision.getDecision());
}
}
V1 API (Legacy)
package io.opentdf.platform;
import com.connectrpc.ResponseMessageKt;
import io.opentdf.platform.authorization.DecisionRequest;
import io.opentdf.platform.authorization.DecisionResponse;
import io.opentdf.platform.authorization.Entity;
import io.opentdf.platform.authorization.EntityChain;
import io.opentdf.platform.authorization.GetDecisionsRequest;
import io.opentdf.platform.authorization.GetDecisionsResponse;
import io.opentdf.platform.authorization.ResourceAttribute;
import io.opentdf.platform.policy.Action;
import io.opentdf.platform.sdk.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class GetDecisions {
private static final Logger logger = LogManager.getLogger(GetDecisions.class);
public static void main(String[] args) {
String clientId = "opentdf";
String clientSecret = "secret";
String platformEndpoint = "localhost:8080";
String namespaceName = "opentdf.io";
SDKBuilder builder = new SDKBuilder();
try (SDK sdk =
builder
.platformEndpoint(platformEndpoint)
.clientSecret(clientId, clientSecret)
.useInsecurePlaintextConnection(true)
.build()) {
GetDecisionsRequest request =
GetDecisionsRequest.newBuilder()
.addDecisionRequests(
DecisionRequest.newBuilder()
.addEntityChains(
EntityChain.newBuilder()
.setId("ec1")
.addEntities(
Entity.newBuilder().setId("entity-1").setClientId("opentdf")))
.addActions(Action.newBuilder().setName("read"))
.addResourceAttributes(
ResourceAttribute.newBuilder()
.setResourceAttributesId("resource-attribute-1")
.addAttributeValueFqns(
"https://" + namespaceName + "/attr/test/value/test1")))
.build();
GetDecisionsResponse getDecisionsResponse =
ResponseMessageKt.getOrThrow(
sdk.getServices()
.authorization()
.getDecisionsBlocking(request, Collections.emptyMap())
.execute());
List<DecisionResponse> decisions = getDecisionsResponse.getDecisionResponsesList();
logger.info(
"Successfully retrieved decisions: [{}]",
decisions.stream()
.map(DecisionResponse::getDecision)
.map(DecisionResponse.Decision::toString)
.collect(Collectors.joining(", ")));
} catch (Exception e) {
logger.error("Failed to get decisions", e);
}
}
}
const response = await platformClient.v2.authorization.getDecisionBulk({
decisionRequests: [
{
entityIdentifier: {
identifier: {
case: 'entityChain',
value: {
entities: [
{
ephemeralId: 'user-123',
entityType: { case: 'emailAddress', value: 'user@company.com' },
},
],
},
},
},
action: { name: 'decrypt' },
resources: [
{
ephemeralId: 'resource-1',
resource: {
case: 'attributeValues',
value: { fqns: ['https://company.com/attr/class/value/public'] },
},
},
{
ephemeralId: 'resource-2',
resource: {
case: 'attributeValues',
value: { fqns: ['https://company.com/attr/class/value/confidential'] },
},
},
],
},
],
});
for (const resp of response.decisionResponses) {
if (resp.allPermitted !== undefined) {
console.log('All resources permitted:', resp.allPermitted.value);
}
for (const resourceDecision of resp.resourceDecisions) {
console.log(
`Resource ${resourceDecision.ephemeralResourceId}: ${resourceDecision.decision}`
);
}
}
Returns
A list of GetDecisionMultiResourceResponse objects, each containing an allPermitted flag and per-resource ResourceDecision entries.
Type Reference
Decision
The authorization result.
| Value | Description |
|---|---|
DECISION_PERMIT | Access is granted. Check requiredObligations for any controls the PEP must enforce. |
DECISION_DENY | Access is denied. |
ResourceDecision
The result for a single resource in a decision response.
| Field | Type | Description |
|---|---|---|
ephemeralResourceId | string | Correlates with the ephemeralId set on the request's Resource. |
decision | Decision | Permit or deny. |
requiredObligations | []string | Obligation 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:
| Field | Type | Description |
|---|---|---|
ephemeralId | string | An ID you assign for correlating with the response. Used in bulk requests. |
attributeValues.fqns | []string | Attribute value FQNs on the resource (1–20). Use this for TDF payloads or any resource identified by attribute values. |
registeredResourceValueFqn | string (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.
| Field | Type | Description |
|---|---|---|
ephemeralId | string | Correlates with the entity in the request. |
actionsPerAttributeValueFqn | map<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.
| Field | Type | Description |
|---|---|---|
allPermitted | bool | Convenience flag — true if every resource in this group was permitted. |
resourceDecisions | []ResourceDecision | Per-resource results with individual decisions and obligations. |
Typical Workflow
- 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
- During resource access: Use GetDecision to enforce access controls when an entity attempts to access a specific resource
- For bulk operations: Use GetDecisionBulk for efficient batch authorization when checking multiple resources at once
Best Practices
Performance
- Batch operations: Use
GetDecisionBulkfor multiple authorization checks instead of callingGetDecisionin a loop - Cache entitlements: Cache
GetEntitlementsresults when appropriate (consider TTL) - Scope queries: Use
withComprehensiveHierarchyonly 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
- Go
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)