Policy Decision Point (PDP) - Concepts
Understand AuthZEN 1.1 specification, policy structure, YAML syntax, and the 5-level inheritance hierarchy.
๐Learning Objectives
- Understand what a PDP does and why it matters
- Learn the AuthZEN 1.1 specification basics
- Understand policy structure: applications, resources, actions
- Master YAML policy syntax and JSON policy format (IGA)
- Know the four IGA policy categories: guardrails, SoD, birthright, temporal
- Understand
pdp_applicationrouting and rich decision context - Understand the 5-level inheritance hierarchy
๐ฏWhat is a Policy Decision Point?
The PDP is the brain of the authorization system. It answers the question:
"Can Subject perform Action on Resource?"
Key Concepts
- Subject: Who is making the request (user, service, role)
- Action: What operation they want to perform (read, write, delete)
- Resource: What they want to access (document, API endpoint, feature)
- Context: Additional attributes (time, location, device)
(User)"] A["โก Action
(Read)"] R["๐ Resource
(Document)"] end S --> PDP A --> PDP R --> PDP PDP["๐ง PDP
Policy Evaluation"] PDP --> D{Decision} D -->|"โ "| PERMIT["โ PERMIT
Access Granted"] D -->|"โ"| DENY["โ DENY
Access Denied"] style PDP fill:#7c3aed,color:#fff style PERMIT fill:#059669,color:#fff style DENY fill:#e11d48,color:#fff
๐AuthZEN 1.1 Specification
EmpowerNow PDP implements the AuthZEN 1.1 specification, a standard for authorization APIs:
Standard Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/access/v1/evaluation | POST | Single authorization decision |
/access/v1/evaluations | POST | Batch authorization decisions |
/access/v1/search | POST | Search for allowed resources/actions |
Standard Request Format
{
"subject": {
"type": "user",
"id": "user-uuid-123",
"properties": {
"department": "engineering"
}
},
"action": {
"name": "read"
},
"resource": {
"type": "document",
"id": "doc-456",
"properties": {
"classification": "confidential"
}
},
"context": {
"time": "2024-01-15T10:30:00Z",
"ip_address": "192.168.1.100"
}
}
Standard Response Format
{
"decision": true, // or false
"context": {
"reason_admin": {
"explanation": "User has 'reader' role on document"
}
}
}
Copy curl and run in your terminal to ask the PDP: "Can user Alice read the products catalog?" This is a real AuthZEN evaluation request!
You should see "decision": true because reading products is allowed for all users in the it-shop application.
Copy curl and run in your terminal to try something that should be deniedโdeleting products without admin role:
You should see "decision": false because only users with the product-admin role can delete products.
๐The Authorization Request Journey
Step-by-Step Breakdown
- Request Arrives: User makes an API request with their JWT access token
- Extract Context: API extracts user ID, roles, and request details from the JWT and request
- Build AuthZEN Request: API constructs the subject/action/resource/context JSON
- Cache Check: PDP checks if this exact decision was recently made
- Policy Loading: On cache miss, PDP loads policies for the application and resource type
- Attribute Enrichment: Policy Information Points (PIPs) fetch additional data if needed
- Rule Evaluation: PDP evaluates rules in order, applying inheritance
- Decision & Caching: Result is returned and cached for future requests
- API Response: API enforces the decisionโeither returning data or 403
The cache check happens first because most authorization decisions are repetitive. A user accessing the same resource type multiple times will hit the cache after the first request, keeping latency under 1ms.
๐Policy Structure
Policies are organized in a hierarchical structure:
Application principal policy"] end subgraph IGA["๐ identity-governance/"] PrincipalPolicy["๐ principal-policy.json"] subgraph GuardrailsDir["๐ guardrails/"] GR["๐ contractor-restrictions.json"] end subgraph SoDDir["๐ sod/"] SOD1["๐ payment-vendor-sod.json"] SOD2["๐ hr-payroll-sod.json"] end subgraph BirthrightDir["๐ birthright/"] BR["๐ engineering-birthright.json"] end subgraph TemporalDir["๐ temporal/"] TEMP["๐ contractor-expiration.json"] end end subgraph IdP["๐ idp/"] IdPPolicy["๐ principal-policy.json"] end end subgraph Global["๐ global/"] BaseSec["๐ base-security-policy.json
Global security baseline"] GlobalSec["๐ global-security-baseline.json"] Recert["๐ recertification-auto-policies.json"] end end style ITShopPolicy fill:#f59e0b,color:#000 style BaseSec fill:#7c3aed,color:#fff style PrincipalPolicy fill:#10b981,color:#fff style IdPPolicy fill:#3b82f6,color:#fff
The identity-governance PDP application organizes IGA policies into four categories: guardrails, SoD, birthright, and temporal. Each category has its own subdirectory and evaluates different resource types. Policies use JSON format with structured denyIf/allowIf conditions.
Application Scoping via pdp_application
Each application has its own policy namespace. The PDP determines which policy set to use via the resource.properties.pdp_application field:
{
"resource": {
"type": "group",
"id": "IT-Admins",
"properties": {
"pdp_application": "identity-governance"
}
}
}
- PDP extracts
pdp_applicationfromresource.properties - Resolves the application context from the registry
- Loads policies from the application's configured directories
- Applies the inheritance hierarchy and returns the decision
The pdp_application field lives inside resource.properties, not in context. Missing values default to "global". Invalid format returns HTTP 400; unknown application IDs return HTTP 422.
Example Application IDs
| Application ID | Purpose | Policy Format |
|---|---|---|
it-shop | Shopping features (cart, products, orders) | JSON |
identity-governance | IGA: guardrails, SoD, birthright, temporal | JSON |
experience | Experience app UI access | YAML |
entraid-admin | EntraID administration | YAML |
๐ก๏ธIGA Policy Categories
๐ง Guardrails (GR-xxx)
"Can this provisioning action proceed?"
Resource types: iga.guardrail, group
Example: Contractors blocked from *-Admins groups
โ๏ธ Separation of Duties (SOD-xxx)
"Does this role create a toxic combination?"
Resource types: iga.sod, role
Example: Payment approver + vendor manager = fraud risk
๐ Birthright Provisioning (BR-xxx)
"What should a new hire automatically get?"
Resource types: iga.birthright, target_system
Example: Engineering FTE โ EntraID + AD + OpenLDAP
โฑ๏ธ Temporal Constraints (TEMP-xxx)
"Is this access still valid in time?"
Resource types: iga.temporal
Example: Contractor expired, 4-hour emergency access
IGA Resource Types
| Resource Type | Category | Actions |
|---|---|---|
iga.guardrail | Guardrails | evaluate |
iga.sod | Separation of Duties | evaluate |
iga.birthright | Birthright Provisioning | evaluate |
iga.temporal | Temporal Constraints | evaluate |
group | Guardrails target | add_to_group |
role | SoD target | acquire_role |
target_system | Birthright target | birthright_provision |
๐YAML Policy Syntax
Policies are written in YAML with a specific schema:
Basic Policy Structure
# products.yaml - Policy for products resource
name: products
description: Access control for product catalog
rules:
- id: allow-read-all
description: Anyone can read products
effect: permit
subjects:
- type: any
actions:
- read
- list
- id: allow-admin-write
description: Admins can modify products
effect: permit
subjects:
- type: role
id: product-admin
actions:
- create
- update
- delete
- id: deny-default
description: Deny all other access
effect: deny
subjects:
- type: any
actions:
- any
Conditions
rules:
- id: allow-own-orders
description: Users can only view their own orders
effect: permit
subjects:
- type: user
actions:
- read
conditions:
- attribute: resource.owner_id
operator: equals
value: subject.id
Time-Based Conditions
rules:
- id: business-hours-only
description: Access only during business hours
effect: permit
conditions:
- attribute: context.time
operator: time_between
value:
start: "09:00"
end: "17:00"
timezone: "America/New_York"
๐JSON Policy Format (IGA Policies)
YAML vs JSON: Side by Side
YAML (Traditional โ it-shop)
# products.yaml
rules:
- id: allow-read-all
effect: permit
subjects:
- type: any
actions:
- read
JSON (IGA โ identity-governance)
{
"id": "GR-001-contractor-no-admin",
"effect": "DENY",
"denyIf": {
"operator": "AND",
"conditions": [
{"scope": "subject",
"attribute": "properties.employee_type",
"operator": "equals",
"value": "Contractor"},
{"scope": "resource",
"attribute": "properties.name",
"operator": "matches",
"value": ".*-Admins?$"}
]
}
}
JSON Condition Structure
Each condition in a denyIf or allowIf block has four fields:
| Field | Purpose | Examples |
|---|---|---|
scope | Where to look for the attribute | subject, resource, context |
attribute | Dotted path to the value | properties.employee_type, id |
operator | Comparison to perform | equals, in, matches, contains_any, after_date |
value | Expected value or pattern | "Contractor", ["admin", "editor"], ".*-Admins?$" |
Complete IGA Policy Example (GR-001)
{
"id": "GR-001-contractor-no-admin-groups",
"effect": "DENY",
"denyIf": {
"operator": "AND",
"conditions": [
{"scope": "subject", "attribute": "properties.employee_type",
"operator": "equals", "value": "Contractor"},
{"scope": "resource", "attribute": "properties.name",
"operator": "matches", "value": ".*-Admins?$"}
]
},
// Rich context returned with the DENY decision
"on_deny": {
"context": {
"guardrail_id": "GR-001",
"reason_user": {"en": "Contractors cannot be added to admin groups"},
"reason_admin": {"en": "GR-001: Contractor attempted to join admin group"},
"remediation": "Remove admin group request or convert contractor to FTE"
}
}
}
on_deny.context is returned when the policy blocks access โ it carries the reason, remediation steps, and guardrail/SoD IDs for auditing.
on_permit.context is returned when access is allowed โ for birthright policies it carries what to provision (groups, attributes, licenses).
๐๏ธ5-Level Inheritance Hierarchy
Policies inherit from multiple levels, allowing for flexible and DRY configurations:
All applications"] L2["๐ข Level 2: Application Default
Single app scope"] L3["๐ฆ Level 3: Resource Type
Type of resource"] L4["โก Level 4: Action
Specific action"] L5["๐ฏ Level 5: Instance
Specific resource"] L1 --> L2 --> L3 --> L4 --> L5 style L1 fill:#7c3aed,color:#fff style L2 fill:#3b82f6,color:#fff style L3 fill:#0891b2,color:#fff style L4 fill:#059669,color:#fff style L5 fill:#f59e0b,color:#000
| Level | Scope | Example |
|---|---|---|
| 1. Global | All applications | Deny access to blocked users |
| 2. Application | Single app | IT Shop requires authentication |
| 3. Resource Type | Type of resource | Documents require classification |
| 4. Action | Specific action | Delete requires approval |
| 5. Instance | Specific resource | This document requires manager role |
Inheritance Example
// Level 2: Application default (principal-policy.json)
{
"schema_version": "2.0",
"application": "it-shop",
"rules": [...] // Application-scoped rules
}
# Level 3: Resource type (scoped via resource.type in rules)
name: products
inherit: true # Inherit from application level
rules:
- id: public-read
effect: permit
actions: [read]
subjects:
- type: any
Start with restrictive global defaults (deny), then open up access at lower levels. This follows the principle of least privilege.
๐ฌHands-on Labs
Let's explore the actual policy files that power the EmpowerNow platform.
Navigate to the policy directory: (replace <YourRepoRoot> with your project root, e.g. EmpowerNow)
# Windows (PowerShell)
cd <YourRepoRoot>\ServiceConfigs\Deployment\config\pdp\policies
# macOS/Linux
cd <YourRepoRoot>/ServiceConfigs/Deployment/config/pdp/policies
List the applications with policies:
# Windows
dir applications
# macOS/Linux
ls applications
You should see folders like it-shop, identity-governance, idp, etc.
Read an application-level policy:
# Windows
type applications\it-shop\principal-policy.json
# macOS/Linux
cat applications/it-shop/principal-policy.json
Explore the IGA application policies:
# Windows
dir applications\identity-governance
type applications\identity-governance\principal-policy.json
# macOS/Linux
ls applications/identity-governance
cat applications/identity-governance/principal-policy.json
principal-policy.jsondefines application-level rules using JSON IR schema- IGA subdirectories (
guardrails/,sod/, etc.) contain category-specific policies - Each rule has an
id,effect, conditions via$expr, and optionalon_deny/on_permitcontext
- What is the default effect for the it-shop application?
- Which roles can modify products?
- Are there any conditions defined in the policies?
Let's make authorization requests directly to the PDP using curl.
Check if PDP is healthy:
curl -s https://pdp.self.empowernow.ai/health | jq .
Make a permit request (reading products):
curl -s -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
-H "Content-Type: application/json" \
-d '{
"subject": {"type": "user", "id": "alice"},
"action": {"name": "read"},
"resource": {"type": "products", "id": "catalog"},
"context": {"application": "it-shop"}
}' | jq .
Make a deny request (deleting without permission):
curl -s -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
-H "Content-Type: application/json" \
-d '{
"subject": {"type": "user", "id": "alice"},
"action": {"name": "delete"},
"resource": {"type": "products", "id": "product-123"},
"context": {"application": "it-shop"}
}' | jq .
Try a batch evaluation:
curl -s -X POST https://pdp.self.empowernow.ai/access/v1/evaluations \
-H "Content-Type: application/json" \
-d '{
"evaluations": [
{
"subject": {"type": "user", "id": "alice"},
"action": {"name": "read"},
"resource": {"type": "products"},
"context": {"application": "it-shop"}
},
{
"subject": {"type": "user", "id": "alice"},
"action": {"name": "write"},
"resource": {"type": "orders"},
"context": {"application": "it-shop"}
}
]
}' | jq .
- Read products:
{"decision": true} - Delete products:
{"decision": false} - Batch: Array of decisions for each evaluation
- What happens if you change the application context to one that doesn't exist?
- How would you give Alice permission to delete products?
Let's trace how policies inherit from each level.
Read the global security baseline (replace <YourRepoRoot> with your project root):
# Windows: type <YourRepoRoot>\serviceconfigs\pdp\config\policies\global\base-security-policy.json
# macOS/Linux: cat <YourRepoRoot>/serviceconfigs/pdp/config/policies/global/base-security-policy.json
This is Level 1โapplies to ALL applications.
Read the application default:
# Windows: type <YourRepoRoot>\serviceconfigs\pdp\config\policies\applications\it-shop\principal-policy.json
# macOS/Linux: cat <YourRepoRoot>/serviceconfigs/pdp/config/policies/applications/it-shop/principal-policy.json
This is Level 2โapplies to the it-shop application.
Read an IGA guardrail policy (resource type level):
# Windows: type <YourRepoRoot>\serviceconfigs\pdp\config\policies\applications\identity-governance\guardrails\contractor-restrictions.json
# macOS/Linux: cat <YourRepoRoot>/serviceconfigs/pdp/config/policies/applications/identity-governance/guardrails/contractor-restrictions.json
This is Level 3โapplies to the guardrail resource type within identity-governance.
Trace the inheritance:
- Does the resource policy have
inherit: true? - If a rule matches at Level 3, does Level 1 still apply?
- What happens if Level 1 says deny but Level 3 says permit?
More specific policies (lower levels) override less specific ones (higher levels). A Level 3 permit can override a Level 2 deny, but not a Level 1 explicit deny. This is why "deny at the top" is a safe default.
๐งTroubleshooting Authorization
User gets 403 Forbidden despite having the correct role. They could access this resource yesterday!
- Check if the user's role assignment is still valid (membership service)
- Verify the application context is correct in the request
- Look for recent policy changes that might have added restrictions
- Check if there's a condition (time-based, IP-based) blocking access
# Get detailed decision explanation
curl -s -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
-H "Content-Type: application/json" \
-H "X-Debug: true" \
-d '{"subject": {...}, "action": {...}, "resource": {...}}' | jq '.context.reason_admin'
- Verify role assignment in membership:
GET /users/{id}/roles - Check policy file for new conditions or rule changes
- Clear the PDP cache if policy was recently updated
After editing a policy file, all authorization requests for that application return errors or unexpected denials.
- YAML is whitespace-sensitiveโcheck indentation
- Look for missing colons, quotes, or brackets
- Verify the policy schema is correct (rules, subjects, actions)
# Validate YAML syntax
python -c "import yaml; yaml.safe_load(open('policy.yaml'))"
# Check PDP logs for parsing errors
docker logs pdp-app --tail 50 | grep -i error
- Use a YAML validator or IDE with YAML support
- Keep a backup before editing policies
- Test policy changes in a development environment first
A rule you added at the resource level isn't taking effect. The global or application level seems to be overriding it.
Remember the inheritance rules:
- More specific levels can permit where less specific levels deny
- But explicit denies at higher levels may override lower permits
- Check
inherit: truein your policy file
- Trace inheritance from Level 1 to Level 5 for your request
- Use debug mode to see which rule matched
- Consider if the rule should be at a different level
๐ฆRich Decision Context
on_deny.context, on_permit.context, and obligations provide.
UI message"] RA["reason_admin
debug info"] GID["guardrail_id /
sod_violation"] REM["remediation
steps to fix"] end subgraph Oblig["obligations"] NOTIFY["alert_security"] REVOKE["auto_revoke_access
delay: 4h"] end end
Deny Response (Guardrail GR-001)
{
"decision": false,
"context": {
"guardrail_id": "GR-001",
"guardrail_name": "contractor_no_admin_groups",
"risk_level": "high",
"reason_user": {"en": "Contractors cannot be added to admin groups"},
"reason_admin": {"en": "GR-001: Contractor attempted to join IT-Admins"},
"remediation": "Remove admin group request or convert to FTE"
}
}
Permit Response (Birthright BR-ENG-001)
{
"decision": true,
"context": {
"naming_system": "av_entraid",
"naming_object_type": "account",
"birthright_groups": ["All-Employees", "Engineering-All"],
"birthright_attributes": {"AccountEnabled": true, "UsageLocation": "US"},
"licenses": ["Microsoft 365 E3"]
}
}
Obligations (Automated Actions)
Some policies trigger automated side-effects alongside the decision:
"obligations": [
{"type": "notification", "action": "alert_security",
"parameters": {"priority": "high"}},
{"type": "schedule", "action": "auto_revoke_access",
"parameters": {"delay_hours": 4}}
]
| Obligation Type | Action | Use Case |
|---|---|---|
notification | alert_security | Emergency access triggers SOC alert |
schedule | auto_revoke_access | 4-hour auto-expiry for emergency grants |
audit | enhanced_logging | Extra logging for high-risk decisions |
โKnowledge Check
Authentication answers "Who are you?" It verifies identity through credentials, tokens, or certificates.
Authorization answers "What are you allowed to do?" It determines permissions based on policies, roles, and attributes.
You must authenticate before you can authorizeโthe PDP needs to know who's asking before it can decide if they're allowed.
- Subject: Who is making the request (user, service, role)
- Action: What operation they want to perform (read, write, delete)
- Resource: What they want to access (document, API, feature)
- Context: Additional attributes (time, IP, application)
- Global: Applies to all applications
- Application: Applies to a single application
- Resource Type: Applies to a type of resource (e.g., all products)
- Action: Applies to a specific action (e.g., delete)
- Instance: Applies to a specific resource (e.g., product-123)
More specific levels override less specific ones.
Default deny means: "If no rule explicitly permits this action, deny it."
This follows the principle of least privilegeโusers only get access they've been explicitly granted. It's safer than default permit because:
- New resources are protected by default
- Forgetting to write a policy doesn't create a security hole
- You have to consciously decide to grant access
PIPs are external data sources that the PDP queries to make decisions. When a policy needs information not in the request, it asks a PIP.
Examples:
- Membership service: "What roles does this user have?"
- Resource metadata service: "Who owns this document?"
- Time service: "What's the current time in the user's timezone?"
/access/v1/evaluation (singular): Evaluates a single authorization request. Returns one decision.
/access/v1/evaluations (plural): Evaluates multiple requests in a single call. Returns an array of decisions.
Use batch evaluation when checking multiple permissions at once (e.g., "What can this user do on this page?") to reduce network overhead.
Authorization decisions are evaluated on nearly every API request. Without caching, the PDP would become a bottleneck.
Caching works because:
- Most requests are repetitive (same user, same resource type)
- Policies change infrequently
- Cache can be invalidated when policies update
With caching, repeat decisions return in <1ms instead of 10-50ms.
- Guardrails (GR-xxx): Pre-provisioning checks that block or allow actions (e.g., contractors can't join admin groups)
- Separation of Duties (SOD-xxx): Detect toxic role combinations before assignment (e.g., payment approver + vendor manager)
- Birthright (BR-xxx): Discover what systems/groups to provision based on HR attributes (e.g., Engineering FTE gets EntraID + AD + OpenLDAP)
- Temporal (TEMP-xxx): Time-based constraints (e.g., contractor expired, max access duration)
pdp_application go in an AuthZEN request?
โผ
The pdp_application field goes inside resource.properties:
"resource": {
"type": "group",
"id": "IT-Admins",
"properties": {
"pdp_application": "identity-governance"
}
}
This tells the PDP which application's policy set to load. Missing values default to "global". Invalid format returns HTTP 400; unknown application IDs return HTTP 422.
on_deny.context used for?
โผ
on_deny.context provides rich metadata returned alongside a deny decision. It typically includes:
- guardrail_id / sod_violation: The rule ID that triggered the deny
- reason_user: A user-friendly explanation for the UI
- reason_admin: Technical details for debugging
- remediation: Steps to resolve the denial
Similarly, on_permit.context returns metadata for allowed actions โ for birthright policies this includes which groups and attributes to provision.
๐Day 8 Checkpoint
- Can explain what a PDP does and its role in authorization
- Understand the AuthZEN request/response format
- Know the policy file structure and organization
- Can read and understand YAML policy syntax
- Know the JSON policy format used by IGA policies
- Can name the four IGA policy categories and their resource types
- Understand
pdp_applicationand rich decision context - Understand the 5-level inheritance hierarchy
For hands-on PDP policy authoring, JSON IR, and application registration patterns, use the official EmpowerNow documentation together with your platform administrator.