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
package main
import (
"context"
"log"
"github.com/opentdf/platform/sdk"
)
func main() {
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 `ctx`.
ctx := context.Background()
}
import io.opentdf.platform.sdk.*;
import java.util.Collections;
SDK sdk = SDKBuilder.newBuilder()
.platformEndpoint("http://localhost:8080")
.clientSecret("opentdf", "secret")
.useInsecurePlaintextConnection(true)
.build();
// All Java snippets below use `sdk`.
// Remember to call sdk.close() when done.
import { authTokenInterceptor, clientCredentialsTokenProvider } from '@opentdf/sdk';
import { PlatformClient } from '@opentdf/sdk/platform';
const platform = 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 `platform`.
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
- Java
- JavaScript
| 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"),
// ...
}
Without helpers (manual proto construction)
import (
"github.com/opentdf/platform/protocol/go/entity"
authorizationv2 "github.com/opentdf/platform/protocol/go/authorization/v2"
)
req := &authorizationv2.GetDecisionRequest{
EntityIdentifier: &authorizationv2.EntityIdentifier{
Identifier: &authorizationv2.EntityIdentifier_EntityChain{
EntityChain: &entity.EntityChain{
Entities: []*entity.Entity{
{
EntityType: &entity.Entity_EmailAddress{EmailAddress: "alice@example.com"},
// or &entity.Entity_ClientId{ClientId: "..."}
// or &entity.Entity_UserName{UserName: "..."}
Category: entity.Entity_CATEGORY_SUBJECT,
},
},
},
},
},
// ...
}
| Helper | Description |
|---|---|
EntityIdentifiers.forEmail(email) | Identify by email address |
EntityIdentifiers.forClientId(clientId) | Identify by client ID (service account / NPE) |
EntityIdentifiers.forUserName(username) | Identify by username |
EntityIdentifiers.forToken(jwt) | Resolve entity from a JWT token |
import io.opentdf.platform.sdk.EntityIdentifiers;
GetDecisionRequest request = GetDecisionRequest.newBuilder()
.setEntityIdentifier(EntityIdentifiers.forEmail("alice@example.com"))
// ...
.build();
Without helpers (manual proto construction)
EntityIdentifier.newBuilder()
.setEntityChain(
EntityChain.newBuilder()
.addEntities(
Entity.newBuilder()
.setId("user-1")
.setEmailAddress("alice@example.com") // or .setClientId(), .setUserName(), etc.
)
)
.build()
| Helper | Description |
|---|---|
EntityIdentifiers.forEmail(email) | Identify by email address |
EntityIdentifiers.forClientId(clientId) | Identify by client ID (service account / NPE) |
EntityIdentifiers.forUserName(username) | Identify by username |
EntityIdentifiers.forToken(jwt) | Resolve entity from a JWT token |
EntityIdentifiers.withRequestToken() | Derive entity from the request's Authorization header |
import { EntityIdentifiers } from '@opentdf/sdk';
const response = await platform.v2.authorization.getDecision({
entityIdentifier: EntityIdentifiers.forEmail('alice@example.com'),
// ...
});
Without helpers (manual object construction)
{
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 | Java | JavaScript |
|---|---|---|---|
ForEmail(email) | EntityIdentifiers.forEmail(email) | EntityIdentifiers.forEmail(email) | |
| Client ID | ForClientID(id) | EntityIdentifiers.forClientId(id) | EntityIdentifiers.forClientId(id) |
| Username | ForUserName(name) | EntityIdentifiers.forUserName(name) | EntityIdentifiers.forUserName(name) |
| JWT Token | ForToken(jwt) | EntityIdentifiers.forToken(jwt) | EntityIdentifiers.forToken(jwt) |
| Request Token | WithRequestToken() | — | EntityIdentifiers.withRequestToken() |
| Claims | — | manual proto construction | manual object construction |
| Registered Resource | — | manual proto construction | manual object construction |
- 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.
Resource
A Resource identifies the data being accessed in GetDecision and GetDecisionBulk calls. It can be specified as a set of attribute value FQNs (most common — e.g. the attributes on a TDF) or as a registered resource value FQN stored in platform policy.
- Go
- Java
- JavaScript
| Helper | Description |
|---|---|
authorizationv2.ForAttributeValues(fqns...) | Resource from attribute value FQNs (e.g. those on a TDF) |
authorizationv2.ForRegisteredResourceValueFqn(fqn) | Resource from a registered resource value FQN in policy |
import authorizationv2 "github.com/opentdf/platform/protocol/go/authorization/v2"
req := &authorizationv2.GetDecisionRequest{
Resource: authorizationv2.ForAttributeValues(
"https://example.com/attr/classification/value/confidential",
"https://example.com/attr/department/value/finance",
),
// ...
}
Without helpers (manual proto construction)
&authorizationv2.Resource{
Resource: &authorizationv2.Resource_AttributeValues_{
AttributeValues: &authorizationv2.Resource_AttributeValues{
Fqns: []string{
"https://example.com/attr/classification/value/confidential",
"https://example.com/attr/department/value/finance",
},
},
},
}
| Helper | Description |
|---|---|
Resources.forAttributeValues(String... fqns) | Resource from attribute value FQNs (e.g. those on a TDF) |
Resources.forRegisteredResourceValueFqn(String fqn) | Resource from a registered resource value FQN in policy |
import io.opentdf.platform.sdk.Resources;
GetDecisionRequest request = GetDecisionRequest.newBuilder()
.setResource(Resources.forAttributeValues(
"https://example.com/attr/classification/value/confidential",
"https://example.com/attr/department/value/finance"))
// ...
.build();
Without helpers (manual proto construction)
Resource.newBuilder()
.setAttributeValues(
Resource.AttributeValues.newBuilder()
.addFqns("https://example.com/attr/classification/value/confidential")
.addFqns("https://example.com/attr/department/value/finance"))
.build()
| Helper | Description |
|---|---|
Resources.forAttributeValues(...fqns) | Resource from attribute value FQNs (e.g. those on a TDF) |
Resources.forRegisteredResourceValueFqn(fqn) | Resource from a registered resource value FQN in policy |
import { Resources } from '@opentdf/sdk';
const response = await platform.v2.authorization.getDecision({
resource: Resources.forAttributeValues(
'https://example.com/attr/classification/value/confidential',
'https://example.com/attr/department/value/finance',
),
// ...
});
Without helpers (manual object construction)
{
resource: {
case: 'attributeValues',
value: {
fqns: [
'https://example.com/attr/classification/value/confidential',
'https://example.com/attr/department/value/finance',
],
},
},
}
Resource variants:
| Variant | Go | Java | JavaScript |
|---|---|---|---|
| Attribute values | authorizationv2.ForAttributeValues(fqns...) | Resources.forAttributeValues(String... fqns) | Resources.forAttributeValues(...fqns) |
| Registered resource | authorizationv2.ForRegisteredResourceValueFqn(fqn) | Resources.forRegisteredResourceValueFqn(fqn) | Resources.forRegisteredResourceValueFqn(fqn) |
The helpers do not set ephemeralId. For GetDecisionBulk where you need to correlate requests with responses, set ephemeralId separately after construction (in Go, assign the field directly; in Java, use .toBuilder().setEphemeralId(...).build()) or use manual construction.
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 platform.v2.authorization.getEntitlements({ ... })
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
entityIdentifier | EntityIdentifier | Yes | The entity to query. Use helpers like ForEmail(...) (Go) or EntityIdentifiers.forEmail(...) (Java/JS). |
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())
}
import io.opentdf.platform.sdk.EntityIdentifiers;
GetEntitlementsRequest request = GetEntitlementsRequest.newBuilder()
.setEntityIdentifier(EntityIdentifiers.forEmail("bob@OrgA.com"))
.build();
GetEntitlementsResponse resp = sdk.getServices()
.authorization()
.getEntitlements(request)
.get();
for (EntityEntitlements entitlement : resp.getEntitlementsList()) {
System.out.println("Entitled to: " +
entitlement.getActionsPerAttributeValueFqnMap());
}
import { EntityIdentifiers } from '@opentdf/sdk';
const response = await platform.v2.authorization.getEntitlements({
entityIdentifier: EntityIdentifiers.forEmail('bob@OrgA.com'),
});
for (const entitlement of response.entitlements) {
console.log('Entitled to:', entitlement.actionsPerAttributeValueFqn);
}
To expand hierarchy rules:
import { EntityIdentifiers } from '@opentdf/sdk';
const response = await platform.v2.authorization.getEntitlements({
entityIdentifier: EntityIdentifiers.forEmail('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 platform.v2.authorization.getDecision({ ... })
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
entityIdentifier | EntityIdentifier | Yes | The entity requesting access. Use helpers like ForEmail(...) (Go) or EntityIdentifiers.forEmail(...) (Java/JS). |
action | Action | Yes | The action being performed (e.g., decrypt, read). |
resource | Resource | Yes | The resource being accessed. Use helpers like ForAttributeValues(...) (Go) or Resources.forAttributeValues(...) (Java/JS). |
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.ForAttributeValues(
"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.ForAttributeValues(
"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")
}
}
import io.opentdf.platform.sdk.EntityIdentifiers;
import io.opentdf.platform.sdk.Resources;
GetDecisionRequest request = GetDecisionRequest.newBuilder()
.setEntityIdentifier(EntityIdentifiers.forEmail("user@company.com"))
.setAction(
Action.newBuilder()
.setName("decrypt")
)
.setResource(Resources.forAttributeValues(
"https://company.com/attr/clearance/value/confidential",
"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 { EntityIdentifiers, Resources } from '@opentdf/sdk';
import { Decision } from '@opentdf/sdk/platform/authorization/v2/authorization_pb.js';
const response = await platform.v2.authorization.getDecision({
entityIdentifier: EntityIdentifiers.forEmail('user@company.com'),
action: { name: 'decrypt' },
resource: Resources.forAttributeValues(
'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 platform.v2.authorization.getDecisionBulk({ ... })
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
decisionRequests | []GetDecisionMultiResourceRequest | Yes | Each entry contains an entity, action, and one or more resources to evaluate. |
GetDecisionMultiResourceRequest
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())
}
import io.opentdf.platform.sdk.EntityIdentifiers;
GetDecisionBulkRequest request = GetDecisionBulkRequest.newBuilder()
.addDecisionRequests(
GetDecisionMultiResourceRequest.newBuilder()
.setEntityIdentifier(EntityIdentifiers.forEmail("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);
}
}
}
import { EntityIdentifiers } from '@opentdf/sdk';
const response = await platform.v2.authorization.getDecisionBulk({
decisionRequests: [
{
entityIdentifier: EntityIdentifiers.forEmail('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. |
Use the Resource helpers for concise construction:
- Go
- Java
- JavaScript
// With helpers
authorizationv2.ForAttributeValues("https://example.com/attr/classification/value/secret")
authorizationv2.ForRegisteredResourceValueFqn("https://example.com/registered/value/my-resource")
// With helpers
Resources.forAttributeValues("https://example.com/attr/classification/value/secret")
Resources.forRegisteredResourceValueFqn("https://example.com/registered/value/my-resource")
// With helpers
Resources.forAttributeValues('https://example.com/attr/classification/value/secret')
Resources.forRegisteredResourceValueFqn('https://example.com/registered/value/my-resource')
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);
}
}
ActionsList
A thin wrapper around a list of Action used as the value type in EntityEntitlements.actionsPerAttributeValueFqn. The wrapper exists because protobuf maps cannot have repeated value types directly.
| Field | Type | Description |
|---|---|---|
actions | []Action | The actions the entity can perform on data carrying the keyed attribute value. |
In Go, reach the underlying slice with actionsList.GetActions(). In Java, actionsList.getActionsList(). In JavaScript, the value is surfaced directly as an Actions object whose .actions field is the array.
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)