Skip to main content
Version: Latest (4.0.3)

AuthZEN Search

AuthZEN search answers three questions that a single yes/no evaluation cannot:

QuestionAPI
Who can access this resource?Search subjects
What can this principal access?Search resources
What can this principal do on this resource?Search actions

The Policy Decision Point (PDP) does not enumerate every catalog entity and then run allow on each one. It uses OPA partial evaluation of a filter policy to produce a UCAST filter, converts that filter to GraphQL, queries the Discovery GraphQL URL you registered, and optionally post-evaluates each node with the evaluation policy (data.authzen.allow).

See AuthZEN Fine-Grained Authorization for architecture, and AuthZEN Simulation to run the same searches against the simulation channel.

Production vs sample

The source of truth for subjects, resources, and actions is your infrastructure. Register your GraphQL API as a Discovery data source (same pattern as a PIP).

policy-management-srv also ships a sample entities API and GraphQL endpoint so you can try search without wiring that catalog first. Those APIs are experimental and are not required in production.

During experiments you can point communicationEP at the bundled sample GraphQL API instead of Ext.

Prerequisites

Replace {host} with your tenant hostname and {token} with an access token that includes:

  • cidaas:authzen_evaluate for PDP search and evaluation
  • cidaas:authzen_read / cidaas:authzen_write for policy-management setup

All examples send the token in the access_token header (the same pattern as other cidaas admin APIs).

export DOMAIN="https://{host}"
export TOKEN="{token}"
export AUTH_HDR=(-H "access_token: $TOKEN" -H "Content-Type: application/json")

Discover PDP URLs:

curl -s "$DOMAIN/policy-decision-srv/.well-known/authzen-configuration" \
-H "access_token: $TOKEN"

OpenAPI: AuthZEN configuration.

1. Connect Discovery

Search needs (1) a Discovery data source whose GraphQL URL is your catalog (or the optional sample), and (2) SearchConfig.

Connect your GraphQL Discovery source

Register an external GraphQL API with Create data source. Required for Discovery:

  • type: Discovery
  • searchEntityType: subject, resource, or action (one source per search kind, or more specific matching)
  • communicationEP: your GraphQL endpoint
  • apiAccess: credentials the PDP uses to call that URL
  • matchingCriteria: subjectType / resourceType / actionType (* matches all)
  • graphqlConfig: queryTemplate, resultPath, pagination, optional ucastFieldMappings

Get Discovery templates returns a starting query shape. Adapt field names and mappings to your schema.

curl -s -X POST "$DOMAIN/policy-management-srv/admin/datasources" \
"${AUTH_HDR[@]}" -d '{
"type": "Discovery",
"enabled": true,
"key": "users-from-my-idp",
"searchEntityType": "subject",
"communicationEP": "https://catalog.example.com/graphql",
"protocol": "GraphQL",
"httpMethod": "POST",
"matchingCriteria": {
"subjectType": "user",
"resourceType": "*",
"actionType": "*"
},
"graphqlConfig": {
"queryTemplate": "query ($entityType: String!, $type: String, $filter: JSON, $first: Int, $after: String) { authzenDiscovery(entityType: $entityType, type: $type, filter: $filter, first: $first, after: $after) { nodes { id type properties } pageInfo { endCursor hasNextPage } totalCount } }",
"resultPath": "authzenDiscovery",
"ucastFieldMappings": {
"subject.type": "type",
"subject.properties.department": "properties.department"
},
"pagination": {
"limitVar": "first",
"tokenVar": "after",
"nextTokenPath": "pageInfo.endCursor",
"hasNextPath": "pageInfo.hasNextPage",
"totalPath": "totalCount"
}
},
"apiAccess": {
"type": "API_KEY",
"apiKeyDetails": {
"apiKey": "your-api-key",
"apiKeyPlaceholder": "X-API-Key",
"apiKeyPlacement": "header"
}
}
}'

Repeat for resource and action search (or use more specific matchingCriteria). Set discoveryDataSourceId on SearchConfig to force one source.

SearchConfig and Discovery registrations live in the PDP live cache, which auto-refreshes about every 10 seconds. You do not need to invalidate after creating or updating data sources unless you need the change on the next search.

Optional experiment: sample catalog

Use this only to try search without your own GraphQL API. It is not the production source of truth.

Seed sample entities creates a small demo catalog:

entityTypetypeid (externalId)properties
subjectuseralice{ "department": "engineering" }
subjectuserbob{ "department": "marketing" }
resourceaccount123
resourceaccount456
actionreadread
actionwritewrite
curl -s -X POST "$DOMAIN/policy-management-srv/admin/authzen/entities/seed-samples" \
"${AUTH_HDR[@]}" -d '{}'

Seeded accounts have no owner_id or visibility, and alice/bob have no roles. Post-eval and document demos later in this guide need an admin user and two documents. Create them with Create entity:

entityTypetypeid (externalId)properties
subjectusercarol{ "department": "engineering", "roles": ["admin"] }
resourcedocumentdoc-alice{ "owner_id": "alice", "visibility": "private" }
resourcedocumentdoc-public{ "visibility": "public" }
curl -s -X POST "$DOMAIN/policy-management-srv/admin/authzen/entities" \
"${AUTH_HDR[@]}" -d '{
"entityType": "subject",
"type": "user",
"externalId": "carol",
"properties": { "department": "engineering", "roles": ["admin"] }
}'

curl -s -X POST "$DOMAIN/policy-management-srv/admin/authzen/entities" \
"${AUTH_HDR[@]}" -d '{
"entityType": "resource",
"type": "document",
"externalId": "doc-alice",
"properties": { "owner_id": "alice", "visibility": "private" }
}'

curl -s -X POST "$DOMAIN/policy-management-srv/admin/authzen/entities" \
"${AUTH_HDR[@]}" -d '{
"entityType": "resource",
"type": "document",
"externalId": "doc-public",
"properties": { "visibility": "public" }
}'

Expect 201 for each.

Bootstrap Discovery creates three sample Discovery sources (sample-subject, sample-resource, sample-action) that call the bundled sample GraphQL API. Existing keys are skipped.

curl -s -X POST "$DOMAIN/policy-management-srv/admin/datasources/bootstrap-discovery" \
"${AUTH_HDR[@]}"

Wait about 10 seconds for the live cache to pick up the sample sources, or invalidate if you need them immediately. Worked JSON examples later in this guide assume this sample catalog (seed-samples plus carol and the two documents).

2. Filter policy vs evaluation policy

Filter policyEvaluation policy
PurposeNarrow the candidate set before DiscoveryDecide allow / deny for a fully known tuple
Packageauthzen.filter_subject, authzen.filter_resource, authzen.filter_actionauthzen
Ruleincludeallow
Default OPA querydata.authzen.filter_*.includedata.authzen.allow
MechanismPartial evaluation → UCAST → GraphQLFull evaluation → boolean
Unknown at search timeThe entity being searchedNothing (or fields substituted from Discovery)
When it runsEvery searchPoint eval; search only when postEval.enabled is true

Default filter modules shipped in the PDP are permissive: they only require a non-empty type or action name. A tenant policy in the same package replaces that default.

Post-evaluation is off by default. Turn it on when results must match AuthZEN §8.1 (results SHOULD pass access evaluation), or when the filter cannot express the full allow logic (roles, time windows, PIP data).

3. Sample policies

Create each policy as its own document (language: "rego"). OpenAPI: Create policy.

curl -s -X POST "$DOMAIN/policy-management-srv/admin/policies" \
"${AUTH_HDR[@]}" -d '{
"name": "demo-allow",
"language": "rego",
"script": "package authzen\n\ndefault allow = false\n\nallow if {\n input.subject.properties.roles[_] == \"admin\"\n}\n"
}'

Policy and SearchConfig changes appear after the live cache auto-refresh (about 10 seconds). Invalidate only if you need them on the next request:

curl -s -X POST "$DOMAIN/policy-decision-srv/access/v1/cache/invalidate" \
"${AUTH_HDR[@]}" -d '{}'

OpenAPI: Invalidate cache (optional).

Evaluation policy (package authzen)

Used by Evaluate access and by search post-eval.

package authzen

default allow = false

allow if {
input.subject.id == input.resource.properties.owner_id
}

allow if {
input.subject.properties.roles[_] == "admin"
}

allow if {
input.action.name == "read"
input.resource.properties.visibility == "public"
}

On the sample catalog:

TupleDecision
carol (admin) + any resource + any actionallow
alice + doc-alice + any actionallow (owner)
anyone + doc-public + readallow
alice or bob + account 123deny

Custom subject filter (package authzen.filter_subject)

Replaces the default subject filter. Keep # METADATA / compile.unknowns aligned with SearchConfig unknowns.

# METADATA
# scope: package
# compile:
# unknowns: [input.subject]
package authzen.filter_subject
import rego.v1

include if {
input.subject.type == "user"
input.subject.properties.department == "engineering"
}

Partial evaluation turns unknown input.subject fields into UCAST conditions. Sample Discovery maps subject.properties.department to properties.department, so alice and carol match and bob does not.

Custom resource filter

Known fields (input.subject, input.action) are concrete; input.resource is unknown.

# METADATA
# scope: package
# compile:
# unknowns: [input.resource]
package authzen.filter_resource
import rego.v1

include if {
input.resource.properties.owner_id == input.subject.id
}

include if {
input.action.name == "read"
input.resource.properties.visibility == "public"
}

If Discovery ucastFieldMappings do not include owner_id / visibility, enable post-eval so those rules still apply after candidates are loaded.

Custom action filter

# METADATA
# scope: package
# compile:
# unknowns: [input.action]
package authzen.filter_action
import rego.v1

include if {
input.action.name == "read"
}

include if {
input.subject.properties.roles[_] == "admin"
}

4. Search configuration

SearchConfig is a tenant singleton (id: "search_config"). Empty fields merge with defaults.

OpenAPI: Get search configuration, Upsert search configuration.

curl -s "$DOMAIN/policy-management-srv/admin/search-config" \
-H "access_token: $TOKEN"
FieldPurpose
filterQueryOPA query for partial evaluation
unknownsMust match the filter policy compile metadata
discoveryDataSourceIdOptional; skip matching and force one Discovery source
postEval.enabledRun full eval on each Discovery node (default false)
postEval.evalQueryQuery for post-eval (default data.authzen.allow)

Filter-only (fast)

{
"id": "search_config",
"subject": {
"filterQuery": "data.authzen.filter_subject.include",
"unknowns": ["input.subject"],
"postEval": { "enabled": false, "evalQuery": "data.authzen.allow" }
},
"resource": {
"filterQuery": "data.authzen.filter_resource.include",
"unknowns": ["input.resource"],
"postEval": { "enabled": false, "evalQuery": "data.authzen.allow" }
},
"action": {
"filterQuery": "data.authzen.filter_action.include",
"unknowns": ["input.action"],
"postEval": { "enabled": false, "evalQuery": "data.authzen.allow" }
}
}

Post-eval enabled (strict results)

Enable when search results must match allow. Each result costs an extra evaluation; pagination tokens become opaque.

PUT the SearchConfig. The live cache picks it up within about 10 seconds; invalidate only for an immediate apply.

Search configuration is included in resource export/import as kind cidaas.authzen.searchconfig.

Default page.limit is 50 when omitted.

Subject search — who can do this?

Required: subject.type, resource.id, resource.type, action.name.

curl -s -X POST "$DOMAIN/policy-decision-srv/access/v1/search/subject" \
"${AUTH_HDR[@]}" -d '{
"subject": { "type": "user" },
"resource": { "id": "123", "type": "account" },
"action": { "name": "read" },
"page": { "limit": 10 }
}'

Example 200 (filter-only, seed catalog):

{
"page": { "next_token": "", "count": 2, "total": 2 },
"results": [
{ "type": "user", "id": "alice", "properties": { "department": "engineering" } },
{ "type": "user", "id": "bob", "properties": { "department": "marketing" } }
]
}

With the engineering filter, bob is excluded. With post-eval and the evaluation policy above, only carol remains for account 123 (alice is not admin and the account has no owner/public flags).

Resource search — what can this principal access?

Required: subject.id, subject.type, resource.type, action.name.

curl -s -X POST "$DOMAIN/policy-decision-srv/access/v1/search/resource" \
"${AUTH_HDR[@]}" -d '{
"subject": { "id": "alice", "type": "user" },
"resource": { "type": "account" },
"action": { "name": "read" },
"page": { "limit": 10 }
}'

Filter-only typically returns accounts 123 and 456. With post-eval and no owner/public on those accounts, results are empty.

Action search — what can this principal do here?

Required: subject.id, subject.type, resource.id, resource.type. action.name is omitted.

curl -s -X POST "$DOMAIN/policy-decision-srv/access/v1/search/action" \
"${AUTH_HDR[@]}" -d '{
"subject": { "id": "alice", "type": "user" },
"resource": { "type": "account", "id": "123" },
"page": { "limit": 10 }
}'

Filter-only typically returns read and write. With post-eval, alice is denied on account 123.

Point evaluation as a sanity check

curl -s -X POST "$DOMAIN/policy-decision-srv/access/v1/evaluation" \
"${AUTH_HDR[@]}" -d '{
"subject": {
"id": "carol",
"type": "user",
"properties": { "roles": ["admin"] }
},
"resource": { "id": "123", "type": "account" },
"action": { "name": "read" }
}'

PDP evaluation responses follow AuthZEN (no { success, data } wrapper): { "decision": true, "context": {} }.

Pagination

  • Filter-only: page.next_token is the Discovery GraphQL cursor.
  • Post-eval: next_token is an opaque token. Do not reuse it with a different request body.

Pass the previous next_token as page.token until it is empty.

6. Example use cases

Assume your Discovery GraphQL returns the sample catalog (or you seeded the experimental catalog) and the evaluation policy above unless noted.

Support console — who can read this account?

  • Search: subjects
  • Request: subject.type=user, resource={id:123,type:account}, action.name=read
  • Post-eval off: users that pass the filter (alice, bob, carol)
  • Post-eval on: carol only

User dashboard — which accounts can Alice read?

  • Search: resources
  • Post-eval off: 123, 456
  • Post-eval on: empty (accounts have no owner/public)

Action menu — what may Alice do on account 123?

  • Search: actions
  • Post-eval off: read, write
  • Post-eval on: empty

Department isolation

Use the custom authzen.filter_subject policy. Subject search for account 123 / read returns engineering users only (alice, carol).

Public documents and owners

Resource search with resource.type=document, post-eval on: alice sees doc-alice and doc-public; bob sees doc-public only.

7. Troubleshooting

SymptomLikely causeWhat to do
Search 400No matching Discovery source, unreachable communicationEP, missing graphqlConfig, or invalid JSONConfirm an enabled Discovery source; verify the GraphQL URL from your network; wait for the ~10s live cache refresh or invalidate. For experiments only, bootstrap the sample sources.
Search 400 missing required fieldsAuthZEN validationAdd the fields listed in the error
Empty results after a policy changeStale PDP cache (wait up to ~10s), or post-eval denying everyoneWait for auto-refresh or invalidate; point-eval one tuple; check postEval.enabled
Filter policy ignoredPackage name mismatch or stale bundlePackage must be authzen.filter_*; reload policies
Filter and allow disagreeExpected when post-eval is offEnable post-eval or tighten the filter
Ambiguous Discovery sourceTwo sources tie on specificitySet discoveryDataSourceId or narrow matching criteria
Simulation search equals liveSimulation not pinned, or post-eval offSee AuthZEN Simulation
Pagination skips or repeatsReused next_token with a different bodyKeep the same request; only change page.token
info
Need Support?

Please contact us on our support page or reach out to cidaas support at [email protected].