Skip to main content
CoreSDK
Authorization & Policy

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:

FieldSourceExample
tenant_idSDKConfig / env var"acme"
subjectcaller-supplied (user ID or JWT sub)"usr_2xK9"
actioncaller-supplied"document:delete"
resourcecaller-supplied"doc_4rT1"
resource_ownercaller-supplied"usr_2xK9"
context.rolescaller-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_blocking in Rust. regorus is synchronous — never call policy().evaluate() directly on an async task. The engine pools N instances (up to min(num_cpus, 8)) and dispatches via spawn_blocking.
  • Profile with coresdk policy bench. Run coresdk policy bench ./policies --iterations 10000 to measure per-rule latency before deploying. The CI gate rejects p99 > 2 ms at 500 rps.

Next steps

On this page