AuthZEN Search
AuthZEN search answers three questions that a single yes/no evaluation cannot:
| Question | API |
|---|---|
| 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.
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_evaluatefor PDP search and evaluationcidaas:authzen_read/cidaas:authzen_writefor 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:DiscoverysearchEntityType:subject,resource, oraction(one source per search kind, or more specific matching)communicationEP: your GraphQL endpointapiAccess: credentials the PDP uses to call that URLmatchingCriteria:subjectType/resourceType/actionType(*matches all)graphqlConfig:queryTemplate,resultPath, pagination, optionalucastFieldMappings
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:
| entityType | type | id (externalId) | properties |
|---|---|---|---|
| subject | user | alice | { "department": "engineering" } |
| subject | user | bob | { "department": "marketing" } |
| resource | account | 123 | — |
| resource | account | 456 | — |
| action | read | read | — |
| action | write | write | — |
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:
| entityType | type | id (externalId) | properties |
|---|---|---|---|
| subject | user | carol | { "department": "engineering", "roles": ["admin"] } |
| resource | document | doc-alice | { "owner_id": "alice", "visibility": "private" } |
| resource | document | doc-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 policy | Evaluation policy | |
|---|---|---|
| Purpose | Narrow the candidate set before Discovery | Decide allow / deny for a fully known tuple |
| Package | authzen.filter_subject, authzen.filter_resource, authzen.filter_action | authzen |
| Rule | include | allow |
| Default OPA query | data.authzen.filter_*.include | data.authzen.allow |
| Mechanism | Partial evaluation → UCAST → GraphQL | Full evaluation → boolean |
| Unknown at search time | The entity being searched | Nothing (or fields substituted from Discovery) |
| When it runs | Every search | Point 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:
| Tuple | Decision |
|---|---|
| carol (admin) + any resource + any action | allow |
alice + doc-alice + any action | allow (owner) |
anyone + doc-public + read | allow |
alice or bob + account 123 | deny |
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"
| Field | Purpose |
|---|---|
filterQuery | OPA query for partial evaluation |
unknowns | Must match the filter policy compile metadata |
discoveryDataSourceId | Optional; skip matching and force one Discovery source |
postEval.enabled | Run full eval on each Discovery node (default false) |
postEval.evalQuery | Query 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.
5. Run live search
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_tokenis the Discovery GraphQL cursor. - Post-eval:
next_tokenis 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:
carolonly
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
| Symptom | Likely cause | What to do |
|---|---|---|
Search 400 | No matching Discovery source, unreachable communicationEP, missing graphqlConfig, or invalid JSON | Confirm 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 fields | AuthZEN validation | Add the fields listed in the error |
Empty results after a policy change | Stale PDP cache (wait up to ~10s), or post-eval denying everyone | Wait for auto-refresh or invalidate; point-eval one tuple; check postEval.enabled |
| Filter policy ignored | Package name mismatch or stale bundle | Package must be authzen.filter_*; reload policies |
| Filter and allow disagree | Expected when post-eval is off | Enable post-eval or tighten the filter |
| Ambiguous Discovery source | Two sources tie on specificity | Set discoveryDataSourceId or narrow matching criteria |
| Simulation search equals live | Simulation not pinned, or post-eval off | See AuthZEN Simulation |
| Pagination skips or repeats | Reused next_token with a different body | Keep the same request; only change page.token |
Related documentation
Please contact us on our support page or reach out to cidaas support at [email protected].