Policy Evaluation
When an identity attempts an action, VeraID evaluates all applicable policies and returns a deterministic allow or deny decision. This page explains the evaluation algorithm, conflict resolution rules, and how to test policy evaluation via the API.
Evaluation Order
VeraID evaluates policies using a strict four-step algorithm:
Step 1: Explicit DENY Always Wins
If any active DENY policy matches the request (subject, resource, action, and all conditions pass), access is immediately denied. No further evaluation occurs.
DENY match found → ACCESS DENIED (stop)Step 2: At Least One ALLOW Required
If no DENY policies match, VeraID checks for matching ALLOW policies. At least one active ALLOW policy must match (subject, resource, action, and all conditions pass) for access to be granted.
No DENY match + ALLOW match found → ACCESS GRANTEDStep 3: All Conditions Must Pass
For a policy to “match,” all of its conditions must pass. A policy with three conditions requires all three to be satisfied. If any condition fails, the policy is skipped as if it does not exist.
Policy conditions: [TIME_WINDOW ✓, IP_RANGE ✓, RATE_LIMIT ✗] → Policy does not matchStep 4: Default Implicit Deny
If no policies match at all — no DENY and no ALLOW — access is denied by default. This is the implicit deny principle: anything not explicitly allowed is forbidden.
No matching policies → ACCESS DENIED (implicit deny)Priority-Based Evaluation
Policies are evaluated in priority order, with higher-priority policies evaluated first. Priority is an integer value; the default is 100.
| Priority | Use Case |
|---|---|
| 1000+ | Emergency lockdown policies |
| 500–999 | Security overrides and compliance rules |
| 100–499 | Standard operational policies |
| 1–99 | Low-priority fallback policies |
Example: Priority in action
Consider two policies for the same identity:
// Policy A: Priority 200 (evaluated first){ "name": "allow-read-all", "effect": "ALLOW", "priority": 200, "subjects": ["identity:svc-data-pipeline"], "resources": ["*"], "actions": ["read:*"], "conditions": []}// Policy B: Priority 500 (evaluated first due to higher priority){ "name": "deny-secrets-read", "effect": "DENY", "priority": 500, "subjects": ["identity:svc-data-pipeline"], "resources": ["secrets:*"], "actions": ["read:*"], "conditions": []}For a request to read:secrets/db-password:
- Policy B (DENY, priority 500) matches first and denies access.
- Policy A (ALLOW, priority 200) is never reached.
Even if the priorities were reversed, the result would be the same — DENY always wins regardless of priority.
Policy Evaluation API
Test policy evaluation without making actual access requests using the evaluation endpoint.
Request
curl -X POST \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "identityId": "id_abc123def456", "resource": "prod:database:users", "action": "write:records", "context": { "ipAddress": "10.0.1.50", "timestamp": "2026-03-19T14:30:00Z", "geoLocation": { "country": "US", "region": "us-east-1" } } }' \ https://app.veraid.io/api/v1/policies/evaluate| Field | Type | Required | Description |
|---|---|---|---|
identityId | string | Yes | The identity attempting the action. |
resource | string | Yes | The target resource identifier. |
action | string | Yes | The action being attempted. |
context | object | No | Runtime context for condition evaluation. |
context.ipAddress | string | No | Source IP address for IP range conditions. |
context.timestamp | string | No | ISO 8601 timestamp for time window conditions. Defaults to current time. |
context.geoLocation | object | No | Geographic location for geo-fencing conditions. |
Response
{ "allowed": false, "matchedPolicies": [ { "id": "pol_deny_prod_writes", "name": "deny-prod-writes-after-hours", "effect": "DENY", "priority": 500, "conditionsEvaluated": [ { "type": "TIME_WINDOW", "passed": true, "detail": "Current time 14:30 UTC is within window" } ] }, { "id": "pol_allow_db_access", "name": "allow-db-access", "effect": "ALLOW", "priority": 100, "conditionsEvaluated": [ { "type": "IP_RANGE", "passed": true, "detail": "10.0.1.50 is within 10.0.0.0/8" } ] } ], "reason": "Denied by policy 'deny-prod-writes-after-hours' (explicit DENY)"}| Field | Type | Description |
|---|---|---|
allowed | boolean | Whether the action is permitted. |
matchedPolicies | object[] | All policies that matched the request, regardless of effect. |
matchedPolicies[].conditionsEvaluated | object[] | Result of each condition check for the policy. |
reason | string | Human-readable explanation of the decision. |
Complete Policy Example
The following example demonstrates a production-grade policy that combines multiple condition types to secure a deployment pipeline.
{ "name": "secure-production-deploy", "description": "Allow production deployments only during business hours, from the corporate network, with rate limiting and MFA verification", "priority": 300, "subjects": [ "group:deploy-bots", "identity:svc-github-actions" ], "resources": [ "prod:deployments:*", "prod:releases:*" ], "actions": [ "deploy:create", "deploy:rollback", "release:publish" ], "conditions": [ { "type": "TIME_WINDOW", "timezone": "America/New_York", "windows": [ { "days": ["MON", "TUE", "WED", "THU", "FRI"], "startTime": "09:00", "endTime": "17:00" } ] }, { "type": "IP_RANGE", "allowedRanges": [ "10.0.0.0/8", "172.16.0.0/12" ], "deniedRanges": [ "10.0.99.0/24" ] }, { "type": "RATE_LIMIT", "maxRequests": 20, "windowSeconds": 3600 } ], "effect": "ALLOW", "isActive": true}How this policy is evaluated:
- Subject match — The request must come from an identity in the
deploy-botsgroup or the specificsvc-github-actionsidentity. - Resource match — The target resource must match
prod:deployments:*orprod:releases:*. - Action match — The action must be one of
deploy:create,deploy:rollback, orrelease:publish. - Time window — The request must occur on a weekday between 9 AM and 5 PM Eastern.
- IP range — The request must originate from
10.0.0.0/8or172.16.0.0/12, but not from10.0.99.0/24. - Rate limit — The identity must not have exceeded 20 requests in the last hour.
If all six checks pass, access is granted. If any check fails, this policy does not match and the evaluator continues to the next policy.
Evaluation Flow Diagram
The complete evaluation flow for a single request:
Request received │ ├─ Collect all active policies matching subject + resource + action │ ├─ Sort by priority (highest first) │ ├─ For each matching DENY policy: │ └─ Evaluate all conditions │ ├─ All conditions pass → ACCESS DENIED (stop) │ └─ Any condition fails → Skip this policy │ ├─ For each matching ALLOW policy: │ └─ Evaluate all conditions │ ├─ All conditions pass → ACCESS GRANTED (stop) │ └─ Any condition fails → Skip this policy │ └─ No policies matched → ACCESS DENIED (implicit deny)Debugging Policy Decisions
When access is unexpectedly denied, use these strategies to diagnose the issue:
- Check the evaluation API response — The
matchedPoliciesarray shows which policies matched and how each condition was evaluated. - Verify policy is active — Ensure
isActiveistrueon the relevant policy. - Check subject membership — Confirm the identity belongs to the expected groups.
- Review condition details — The
conditionsEvaluatedarray includes adetailfield explaining why each condition passed or failed. - Look for DENY policies — A DENY policy at any priority level overrides all ALLOW policies.
# Evaluate with full context for debuggingcurl -X POST \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "identityId": "id_abc123def456", "resource": "prod:database:users", "action": "write:records", "context": { "ipAddress": "10.0.1.50", "timestamp": "2026-03-19T14:30:00Z", "geoLocation": { "country": "US", "region": "us-east-1" } } }' \ https://app.veraid.io/api/v1/policies/evaluate | jq .Next Steps
- Policy Overview — Review the full PBAC model and policy structure.
- Policy Conditions — Configure time windows, IP ranges, rate limits, and geo-fencing.
- Alerts — Set up alerts for policy violations and unusual access patterns.