Attribute-Based Access Control (ABAC)
Fine-grained access decisions based on user, resource, and tenant attributes.
Attribute-Based Access Control (ABAC)
ABAC extends role-based access by evaluating arbitrary attributes — from JWT claims, tenant context, or resource metadata — at policy evaluation time. CoreSDK passes all attributes to your Rego policy via evaluate_policy, so no code changes are required when you add new rules.
How attributes flow into policies
Every call to evaluate_policy("data.authz.allow", input) passes a PolicyInput struct to the Rego evaluator. The standard input shape is:
| Field | Source | Example |
|---|---|---|
tenant_id | SDKConfig / env var | "acme" |
subject | caller-supplied (user ID or JWT sub) | "usr_2xK9" |
action | caller-supplied | "document:delete" |
resource | caller-supplied | "doc_4rT1" |
resource_owner | caller-supplied | "usr_2xK9" |
context.roles | caller-supplied from JWT claims | ["member"] |
Your Rego policy receives these fields as input.*.
Attribute sources
JWT claims
CoreSDK validates the JWT and the middleware exposes claims on request.state.coresdk_user (FastAPI) or decision.claims (Rust). Pass relevant claims as context when calling evaluate_policy:
package coresdk.authz
# Allow access when the JWT carries a matching department claim
allow {
input.action == "reports:read"
input.context.department == "finance"
input.tenant_id == input.context.tenant_id
}Tenant context
Pass tenant attributes in context at the call site:
package coresdk.authz
# Enterprise tenants can export data; others cannot
allow {
input.action == "data:export"
input.context.tenant_plan == "enterprise"
"member" in input.context.roles
}Resource metadata
Pass resource attributes at the call site — CoreSDK forwards them verbatim to the policy:
package coresdk.authz
default allow = false
# Only the resource owner or an admin can delete
allow {
input.action == "document:delete"
input.resource_owner == input.subject
}
allow {
input.action == "document:delete"
"admin" in input.context.roles
}
# Block access to PII resources for non-compliance-approved users
deny[msg] {
input.context.classification == "pii"
not input.context.compliance_approved
msg := "PII access requires compliance approval"
}Combining RBAC and ABAC
Role checks and attribute checks compose naturally in Rego. A common pattern is to use roles as a coarse first gate, then narrow with attributes:
package coresdk.authz
default allow = false
# Step 1: role gate
has_base_access {
"analyst" in input.context.roles
}
has_base_access {
"admin" in input.context.roles
}
# Step 2: attribute refinement
allow {
has_base_access
input.action == "reports:read"
input.tenant_id == input.context.user_tenant_id
input.context.mfa_verified == true
}
# Admins bypass attribute checks
allow {
"admin" in input.context.roles
}SDK usage
Pass resource attributes at the call site. Anything you include in context is forwarded to the policy.
use coresdk_engine::{Engine, policy::decision::PolicyInput, error::ProblemDetail};
// Policy eval is synchronous in regorus — always use spawn_blocking
let engine = engine.clone();
let allowed = tokio::task::spawn_blocking(move || {
engine.policy().evaluate("data.authz.allow", PolicyInput {
tenant_id: "acme".to_string(),
subject: "usr_2xK9".to_string(),
action: "document:delete".to_string(),
resource: "doc_4rT1".to_string(),
resource_owner: Some("usr_2xK9".to_string()),
context: serde_json::json!({
"roles": ["member"],
"classification": "internal",
"mfa_verified": true,
}),
})
}).await??;
if !allowed {
return Err(ProblemDetail::forbidden("policy denied the request"));
}from coresdk import CoreSDKClient, SDKConfig
from coresdk.errors._rfc9457 import ProblemDetailError
_sdk = CoreSDKClient(SDKConfig(
sidecar_addr="http://127.0.0.1:7233",
tenant_id="acme",
service_name="docs-api",
))
allowed = _sdk.evaluate_policy("data.authz.allow", {
"tenant_id": "acme",
"subject": "usr_2xK9",
"action": "document:delete",
"resource": "doc_4rT1",
"resource_owner": "usr_2xK9",
"context": {
"roles": ["member"],
"classification": "internal",
"mfa_verified": True,
},
})
if not allowed:
raise ProblemDetailError(
status=403,
title="Forbidden",
detail="policy denied the request",
type="https://coresdk.dev/errors/forbidden",
)Go and TypeScript
Phase 2. Go and TypeScript SDKs ship in Phase 2. The API shown is the planned surface.
// Go (Phase 2)
result, err := s.Policy().Evaluate(ctx, "data.authz.allow", sdk.PolicyInput{
TenantID: "acme",
Subject: "usr_2xK9",
Action: "document:delete",
Resource: "doc_4rT1",
ResourceOwner: "usr_2xK9",
Context: map[string]any{
"roles": []string{"member"},
"classification": "internal",
"mfa_verified": true,
},
})// TypeScript (Phase 2)
const allowed = await sdk.policy().evaluate("data.authz.allow", {
tenantId: "acme",
subject: "usr_2xK9",
action: "document:delete",
resource: "doc_4rT1",
resourceOwner: "usr_2xK9",
context: {
roles: ["member"],
classification: "internal",
mfaVerified: true,
},
});Performance tips
- Keep policies flat. Deep rule chains increase evaluation latency. Aim for O(1) lookups over iterating sets.
- Use
spawn_blockingin Rust.regorusis synchronous — never callpolicy().evaluate()directly on an async task. The engine pools N instances (up tomin(num_cpus, 8)) and dispatches viaspawn_blocking. - Profile with
coresdk policy bench. Runcoresdk policy bench ./policies --iterations 10000to measure per-rule latency before deploying. The CI gate rejects p99 > 2 ms at 500 rps.
Next steps
- Testing Policies — unit test ABAC rules with the real evaluator
- Audit Log — every attribute-based decision is logged with full input
- Writing Rego Policies — policy input reference and loading options