DAY 09 WEEK 2: CORE SERVICES

Policy Decision Point (PDP) - Hands-on

Make authorization requests to PDP, understand decision explanations, and explore Policy Information Points (PIPs).

Yesterday you learned what the PDP is—the brain that decides who can do what. Today, you become a PDP operator. You'll make authorization requests, decode the responses, and understand why the PDP makes the decisions it does. By the end of this session, you'll be able to troubleshoot any "Access Denied" mystery in your applications.

📋Learning Objectives

  • Make authorization requests to the PDP evaluation endpoint
  • Understand request and response structure
  • Interpret decision explanations
  • Perform batch evaluations for UI permission checks
  • Use search endpoints including /search/resource for birthright discovery
  • Explore Policy Information Points (PIPs)
  • Make IGA requests: guardrail checks, SoD detection, and birthright discovery
  • Simulate a complete Joiner workflow using PDP

🔬Anatomy of an Authorization Decision

Before we dive into making requests, let's understand what happens inside the PDP when it receives an evaluation request. This mental model will help you debug issues and write better policies.
Inside the PDP: Decision Flow
flowchart TB subgraph Input["📥 Request"] S["Subject
(who)"] A["Action
(what)"] R["Resource
(on what)"] C["Context
(when/where)"] end S --> RP A --> RP R --> RP C --> RP subgraph PDP["🧠 PDP Engine"] RP["Request Parser"] RP --> PL["Policy Loader"] PL --> |"Load by app"| PE["Policy Evaluator"] PE --> |"Need attributes?"| PIP["PIPs"] PIP --> |"roles, metadata"| PE PE --> DM["Decision Maker"] end DM --> D{Decision} D -->|"✅"| PERMIT["PERMIT
+ Explanation"] D -->|"❌"| DENY["DENY
+ Explanation"] style PDP fill:#7c3aed,color:#fff style PERMIT fill:#059669,color:#fff style DENY fill:#e11d48,color:#fff

Decision Logic Priority

When multiple rules match, the PDP follows this priority:

  1. Explicit Deny: Any matching deny rule wins (fail-closed)
  2. Explicit Permit: If no deny, check for permit rules
  3. Default Effect: If nothing matches, use the application's default (usually deny)
💡 Security First

The "deny wins" rule is a security feature. Even if 10 rules say permit, a single deny overrides them all. This makes it safer to add restrictions—you don't have to worry about accidentally leaving a backdoor open.

🔧Basic Evaluation Request

Let's start with the simplest possible authorization question: "Can this user read this document?" The request is just JSON describing who, what, and on what.

Make a simple authorization request to the PDP:

# Basic evaluation request
curl -k -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
  -H "Content-Type: application/json" \
  -d '{
    "subject": { "type": "user", "id": "user-123" },
    "action": { "name": "read" },
    "resource": { "type": "document", "id": "doc-456" }
  }'

Expected Response (Permit)

{ "decision": true }

Expected Response (Deny)

{ "decision": false }
💡 PowerShell Version
$body = @{
    subject = @{ type = "user"; id = "user-123" }
    action = @{ name = "read" }
    resource = @{ type = "document"; id = "doc-456" }
} | ConvertTo-Json -Depth 3

Invoke-RestMethod -Uri "https://pdp.self.empowernow.ai/access/v1/evaluation" `
    -Method POST -Body $body -ContentType "application/json" -SkipCertificateCheck

📊Request with Subject Properties

A simple user ID isn't always enough. What if the policy needs to know the user's department, clearance level, or roles? The properties field lets you pass additional attributes that policies can use in their conditions.

Include additional subject and resource attributes for more precise authorization:

🧪 Try It: Rich Authorization Context Interactive

Copy curl and run in your terminal. This request includes roles, department, and clearance level. Policies can use any of these for decisions.

Common Subject Properties

PropertyUse Case
rolesRole-based access control (RBAC)
departmentDepartment-level restrictions
clearance_levelSecurity classification matching
locationGeo-based access control
ℹ️ PIPs vs Properties

You can pass properties in the request, OR let PIPs fetch them dynamically. PIPs are better when you want policies to always use current data (e.g., user's current roles from the database).

💬Exercise 3: Get Decision Explanations

Request detailed explanations for why a decision was made:

curl -k -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
  -H "Content-Type: application/json" \
  -d '{
    "subject": { "type": "user", "id": "user-123" },
    "action": { "name": "delete" },
    "resource": { "type": "document", "id": "doc-456" },
    "context": { "reason_admin": true, "reason_user": true }
  }'

Response with Explanation

{
  "decision": false,
  "context": {
    "reason_admin": {
      "explanation": "Access denied: User lacks 'admin' role required for delete action",
      "matched_rule": "deny-delete-non-admin",
      "policy_file": "documents.yaml"
    },
    "reason_user": { "explanation": "You don't have permission to delete this document" }
  }
}
📝 Explanation Types
  • reason_admin: Technical details for debugging
  • reason_user: User-friendly message for UI

📦Batch Evaluations

Your UI needs to know which buttons to enable. Should "Edit" be clickable? What about "Delete"? Instead of making 10 separate requests, batch evaluation lets you ask all these questions at once—a single round-trip for all your permission checks.

Evaluate multiple authorization requests in a single call:

🧪 Try It: Check Multiple Permissions Interactive

Copy curl and run in your terminal. This batch checks read, write, and delete permissions for a single user on a document.

When to Use Batch

  • Page Load: Check all permissions needed for a page's UI elements
  • List Views: Check permissions for each item in a list
  • Menu Rendering: Determine which menu items to show
💡 Performance Tip

Batch up to 100 evaluations in a single request. The PDP optimizes by sharing policy lookups across evaluations with the same application context.

🔌Policy Information Points (PIPs)

What if the policy needs to know the user's current role, but you don't have it in your request? PIPs are the PDP's phone-a-friend lifeline. During evaluation, the PDP can call out to external systems to fetch the data it needs.
PIPs in Action
sequenceDiagram participant App as 🌐 Application participant PDP as 🧠 PDP participant MemberPIP as 👥 Membership PIP participant ResourcePIP as 📄 Resource PIP participant TimePIP as ⏰ Time PIP App->>PDP: Can user-123 edit doc-456? Note over PDP: Policy needs user roles PDP->>MemberPIP: Get roles for user-123 MemberPIP-->>PDP: ["editor", "reviewer"] Note over PDP: Policy needs doc classification PDP->>ResourcePIP: Get metadata for doc-456 ResourcePIP-->>PDP: {classification: "internal"} Note over PDP: Policy has time condition PDP->>TimePIP: Current time? TimePIP-->>PDP: 14:30 EST PDP-->>App: {"decision": true}

Built-in PIPs

PIPData SourceExample Attributes
MembershipPIPMembership Serviceroles, groups, org hierarchy
ResourcePIPApplication databasesowner, classification, department
TimePIPSystem clockcurrent_time, day_of_week, is_business_hours
GeoIPPIPIP geolocation servicecountry, region, is_vpn

Why PIPs Matter

  • Always Current: Roles checked at decision time, not token-issue time
  • Simpler Requests: Don't need to pass all attributes—PIP fetches them
  • Central Control: Change role assignments, decisions update immediately
💡 Key Insight

PIPs make policies dynamic. A user might have "admin" in their JWT, but if the Membership service says they were demoted 5 minutes ago, the PIP-fetched role wins. This is why EmpowerNow uses PIPs—security shouldn't depend on stale tokens.

🧪Application-Scoped Requests

Different applications have different policies. The IT Shop has rules for carts and products. The identity-governance app has guardrails, SoD, and birthright policies. The pdp_application field in resource.properties tells the PDP which policy namespace to use.

Scope a request to a specific application using resource.properties.pdp_application:

# IT Shop: Can user checkout their cart?
curl -s -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
  -H "Content-Type: application/json" \
  -d '{
    "subject": { "type": "user", "id": "user-123" },
    "action": { "name": "checkout" },
    "resource": {
      "type": "cart", "id": "cart-789",
      "properties": { "pdp_application": "it-shop" }
    }
  }' | jq .
# IGA: Can contractor join admin group?
curl -s -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
  -H "Content-Type: application/json" \
  -d '{
    "subject": { "type": "user", "id": "contractor@acme.com",
                 "properties": { "employee_type": "Contractor" } },
    "action": { "name": "add_to_group" },
    "resource": {
      "type": "group", "id": "IT-Admins",
      "properties": { "name": "IT-Admins", "pdp_application": "identity-governance" }
    }
  }' | jq .
ℹ️ Backward Compatibility

The X-Application header is still accepted, but resource.properties.pdp_application is the canonical approach. All new integrations should use the properties field. Missing values default to "global".

🛡️IGA Authorization Requests

Identity Governance takes PDP from "can user view this page?" to "can this contractor be provisioned into an admin group?" These aren't hypothetical—they're the guardrails that prevent your next compliance audit finding.
🚧 Try It: IGA Guardrail — Contractor Blocked (GR-001) Interactive

Copy curl and run in your terminal. A contractor tries to join an admin group. The guardrail policy should block this and return remediation advice.

✅ Expected Response (BLOCKED)
{
  "decision": false,
  "context": {
    "guardrail_id": "GR-001",
    "reason_user": {"en": "Contractors cannot be added to admin groups"},
    "remediation": "Remove admin group request or convert contractor to FTE"
  }
}
⚖️ Try It: SoD Conflict — Toxic Role Combination (SOD-001) Interactive

Copy curl and run in your terminal. A payment approver requests the vendor_manager role. SoD policy detects a toxic combination (fraud risk).

✅ Expected Response (SOD VIOLATION)
{
  "decision": false,
  "context": {
    "sod_violation": "SOD-001",
    "risk_level": "critical",
    "toxic_combination": true,
    "reason_user": {"en": "SoD conflict: Payment Approval + Vendor Management"},
    "remediation": "Remove payment approval roles before requesting vendor management"
  }
}
🎁 Try It: Birthright Discovery — New Hire Systems Interactive

Copy curl and run in your terminal. An Engineering FTE is joining. The birthright search discovers which target systems to provision.

✅ Expected Response (3 target systems)
{
  "results": [
    { "type": "target_system", "id": "av_entraid",
      "properties": { "birthright_groups": ["All-Employees", "Engineering-All"],
                      "licenses": ["Microsoft 365 E3"] } },
    { "type": "target_system", "id": "av_ad_addomain",
      "properties": { "birthright_groups": ["Engineering-Users", "VPN-Access"] } },
    { "type": "target_system", "id": "av_openldap",
      "properties": { "birthright_groups": ["engineers", "developers"] } }
  ]
}

🔬Hands-on Labs

Time to put it all together. These labs walk you through realistic authorization scenarios you'll encounter in production.
LAB 1 Build a Permission Check Function ⏱️ 10 minutes

Create a reusable script for checking permissions from the command line.

1

Create a check-permission.sh script:

#!/bin/bash
# check-permission.sh - Check if user can perform action on resource

USER_ID=${1:-"user-123"}
ACTION=${2:-"read"}
RESOURCE_TYPE=${3:-"document"}
RESOURCE_ID=${4:-"doc-456"}

curl -s -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
  -H "Content-Type: application/json" \
  -d "{
    \"subject\": {\"type\": \"user\", \"id\": \"$USER_ID\"},
    \"action\": {\"name\": \"$ACTION\"},
    \"resource\": {\"type\": \"$RESOURCE_TYPE\", \"id\": \"$RESOURCE_ID\"},
    \"context\": {\"reason_admin\": true}
  }" | jq .
2

Make it executable and test:

chmod +x check-permission.sh

# Test: Can user-123 read document doc-456?
./check-permission.sh user-123 read document doc-456

# Test: Can user-123 delete document doc-456?
./check-permission.sh user-123 delete document doc-456
✅ Expected Output

You should see JSON responses with decision and context.reason_admin showing why.

LAB 2 Debug a 403 Forbidden ⏱️ 10 minutes

Simulate and debug a real authorization failure.

1

Simulate a denied request:

curl -s -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
  -H "Content-Type: application/json" \
  -H "X-Application: it-shop" \
  -d '{
    "subject": {"type": "user", "id": "guest"},
    "action": {"name": "admin_panel"},
    "resource": {"type": "system", "id": "settings"},
    "context": {"reason_admin": true, "reason_user": true}
  }' | jq .
2

Analyze the explanation:

  • What rule denied the request?
  • What policy file contains the rule?
  • What would the user-facing message say?
3

Test with an admin user:

curl -s -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
  -H "Content-Type: application/json" \
  -H "X-Application: it-shop" \
  -d '{
    "subject": {
      "type": "user", 
      "id": "admin-user",
      "properties": {"roles": ["system-admin"]}
    },
    "action": {"name": "admin_panel"},
    "resource": {"type": "system", "id": "settings"}
  }' | jq .
🤔 Self-Check
  • How does the decision change when you add the admin role?
  • What if you add roles but not system-admin?
LAB 3 UI Permission Pre-flight ⏱️ 10 minutes

Simulate what a frontend would do on page load to determine which UI elements to show.

1

Create a batch request for a product detail page:

curl -s -X POST https://pdp.self.empowernow.ai/access/v1/evaluations \
  -H "Content-Type: application/json" \
  -H "X-Application: it-shop" \
  -d '{
    "evaluations": [
      {"subject": {"type": "user", "id": "user-123"}, "action": {"name": "view"}, "resource": {"type": "product", "id": "prod-1"}},
      {"subject": {"type": "user", "id": "user-123"}, "action": {"name": "edit"}, "resource": {"type": "product", "id": "prod-1"}},
      {"subject": {"type": "user", "id": "user-123"}, "action": {"name": "delete"}, "resource": {"type": "product", "id": "prod-1"}},
      {"subject": {"type": "user", "id": "user-123"}, "action": {"name": "add_to_cart"}, "resource": {"type": "product", "id": "prod-1"}}
    ]
  }' | jq .
2

Map decisions to UI elements:

ActionUI ElementIf False
viewProduct detailsShow "Access Denied" page
editEdit buttonHide or disable button
deleteDelete buttonHide or disable button
add_to_cartAdd to Cart buttonShow "Contact sales"
✅ Key Takeaway

One batch request returns all permissions needed for the page. The frontend uses this to render the appropriate UI without making multiple round-trips.

LAB 4 Explore IGA Policies ⏱️ 10 minutes

Explore the identity-governance PDP application policies to understand guardrails, SoD, and birthright formats.

1

Navigate to the IGA policy directory:

# macOS/Linux
cd <YourRepoRoot>/serviceconfigs/pdp/config/policies/applications/identity-governance
ls -R

# Windows (PowerShell)
cd <YourRepoRoot>\serviceconfigs\pdp\config\policies\applications\identity-governance
Get-ChildItem -Recurse

You should see: principal-policy.json, guardrails/, sod/, birthright/, temporal/

2

Read a guardrail policy:

# macOS/Linux
cat guardrails/contractor-restrictions.json | python3 -m json.tool

# Windows
Get-Content guardrails\contractor-restrictions.json | ConvertFrom-Json | ConvertTo-Json -Depth 10

Find the denyIf block, the conditions array, and the on_deny.context with guardrail_id.

3

Read a birthright policy:

# macOS/Linux
cat birthright/engineering-birthright.json | python3 -m json.tool

Find the allowIf block and the on_permit.context with birthright_groups and naming_system.

🤔 Self-Check
  • What resource type does GR-001 match on? (answer: group)
  • What two role sets create the SOD-001 conflict?
  • What target systems does BR-ENG-001 provision to?
LAB 5 Simulate a Joiner Workflow ⏱️ 15 minutes

Walk through the three-step IGA Joiner workflow: discover birthright systems, check guardrails, check SoD.

Joiner Workflow Steps
sequenceDiagram participant You as 🧑‍💻 You (Terminal) participant PDP as 🧠 PDP You->>PDP: Step 1 — DiscoverBirthright (search/resource) PDP-->>You: 3 target systems loop For each system You->>PDP: Step 2 — CheckGuardrail (evaluation) PDP-->>You: PERMIT or DENY + context end You->>PDP: Step 3 — CheckSoD (evaluation) PDP-->>You: No conflicts / SOD violation
1

Step 1: Discover birthright systems for an Engineering FTE:

curl -s -X POST https://pdp.self.empowernow.ai/access/v1/search/resource \
  -H "Content-Type: application/json" \
  -d '{
    "subject": {"type": "user", "id": "newhire@acme.com",
                "properties": {"employee_type": "Full-Time", "Department": "Engineering"}},
    "action": {"name": "birthright_provision"},
    "resource": {"type": "target_system",
                 "properties": {"pdp_application": "identity-governance"}}
  }' | jq .

Note the systems returned (e.g., av_entraid, av_ad_addomain, av_openldap).

2

Step 2: Check guardrail for provisioning to each system:

curl -s -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
  -H "Content-Type: application/json" \
  -d '{
    "subject": {"type": "user", "id": "newhire@acme.com",
                "properties": {"employee_type": "Full-Time"}},
    "action": {"name": "provision"},
    "resource": {"type": "target_system", "id": "av_entraid",
                 "properties": {"pdp_application": "identity-governance"}}
  }' | jq .
3

Step 3: Check SoD before assigning roles:

curl -s -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
  -H "Content-Type: application/json" \
  -d '{
    "subject": {"type": "user", "id": "newhire@acme.com",
                "properties": {"current_roles": ["Engineering-Users"]}},
    "action": {"name": "acquire_role"},
    "resource": {"type": "role", "id": "Engineering-Lead",
                 "properties": {"pdp_application": "identity-governance"}}
  }' | jq .
✅ Expected Outcome
  • FTE gets 3 systems (all guardrails pass)
  • Contractor would get 2 systems (AD blocked by BR-ENG-005 guardrail)
  • Role assignment proceeds if no SoD conflict exists
🤔 Self-Check
  • What changes if you switch employee_type to "Contractor"?
  • What would happen if the new hire already had payment_approver and requested vendor_manager?

🔧Troubleshooting Authorization

Authorization bugs are sneaky—no stack traces, just a quiet "no." Here are the patterns you'll see over and over, and how to diagnose them.
🤷 The Missing Application Context Case #1
📋 Symptom

Every request returns deny, even for actions that should be permitted. No specific rule is matched in the explanation.

🔍 Investigation
  • Check if X-Application header or context.application is set
  • Without an application, PDP can't find the right policies
# Missing application - hits global default deny
curl -s -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
  -d '{"subject": ..., "action": ..., "resource": ...}'

# With application - finds correct policies
curl -s -X POST https://pdp.self.empowernow.ai/access/v1/evaluation \
  -H "X-Application: it-shop" \
  -d '{"subject": ..., "action": ..., "resource": ...}'
✅ Fix

Always include the application context. Add it to your API gateway or include it in every request body.

The Stale Role Problem Case #2
📋 Symptom

User was just granted a new role, but authorization still fails. The JWT shows the old roles.

🔍 Investigation
  • Are you passing roles in the request body from the JWT?
  • Or is PDP using MembershipPIP to fetch current roles?
  • Check the decision explanation—what roles does it see?
✅ Fix
  • Use MembershipPIP for real-time role checks (recommended)
  • Or have user re-authenticate to get a new token
  • Or shorten token lifetime for faster role updates
⚖️ The Hidden SoD Violation Case #3
📋 Symptom

A role assignment workflow silently fails. The user gets "request denied" with no obvious reason — they have no deny rules in their application policies.

🔍 Investigation
  • Check reason_admin in the decision context — look for sod_violation
  • The user already holds a conflicting role from a previous assignment
  • SoD policies in identity-governance block toxic combinations across roles
# The deny context reveals the SoD conflict
"context": {
  "sod_violation": "SOD-001",
  "risk_level": "critical",
  "reason_admin": {"en": "Payment approver cannot also be vendor manager"}
}
✅ Fix
  • Remove the conflicting role before assigning the new one
  • Or request an SoD exception through the governance workflow
  • Use IGA.CheckSoD before role assignments to catch conflicts early
🔒 The Overly Broad Deny Case #4
📋 Symptom

Admin users are getting denied for actions they should definitely have. The explanation shows a deny rule you didn't expect.

🔍 Investigation
  • Check global policies for broad deny rules
  • Remember: deny wins, even over specific permits
  • Look for subjects: [{type: any}] in deny rules
# This deny rule blocks EVERYONE, including admins!
- id: deny-weekend-access
  effect: deny
  subjects:
    - type: any  # Oops - should exclude admins
  conditions:
    - attribute: context.day_of_week
      operator: in
      value: ["saturday", "sunday"]
✅ Fix

Add exclusions to deny rules, or restructure to use permit rules with specific conditions instead of broad denies.

Knowledge Check

Test your understanding before moving on to tomorrow's Membership service content.
1 What's the difference between /evaluation and /evaluations endpoints?

/evaluation (singular): Evaluates ONE authorization request. Returns a single decision.

/evaluations (plural): Evaluates MULTIPLE requests in a batch. Returns an array of decisions.

Use batch when checking permissions for UI elements or list items to reduce network overhead.

2 What happens when both permit and deny rules match a request?

Deny wins. This is called "deny-override" and is a security feature.

Even if 10 rules say permit, a single matching deny rule will block access. This makes it safe to add restrictions without worrying about conflicting permits.

3 When should you use reason_admin vs reason_user in responses?

reason_admin: For developers and operations. Contains technical details like rule names and policy files. Never expose to end users.

reason_user: For end users. Contains human-readable messages like "You don't have permission to delete this document."

4 What does a PIP do and why is it important?

A Policy Information Point (PIP) fetches additional attributes during policy evaluation.

Example: MembershipPIP queries the current roles for a user from the database.

Why important: Ensures decisions use real-time data, not stale JWT claims. A role revoked 5 minutes ago is immediately effective.

5 How does the X-Application header affect authorization?

It tells the PDP which policy namespace to use. Different applications have different policy sets.

Without an application context, the PDP falls back to global policies only—which typically default to deny.

6 What's the /search endpoint used for?

Instead of asking "can user do X?", search asks "what can user do?"

Use cases:

  • Building dynamic UIs that show only permitted actions
  • Auditing what a user has access to
  • Discovering available actions on a resource type
7 Why would you pass subject properties in the request vs letting PIPs fetch them?

Pass in request when:

  • Data is already available (from JWT or session)
  • PIP would add latency you can't afford
  • Attribute is request-specific (e.g., IP address)

Use PIPs when:

  • Data changes frequently (roles, group membership)
  • Data isn't in the calling application (resource metadata)
  • You want centralized, real-time attribute lookup
8 What is the difference between a guardrail and an SoD policy?

Guardrails block individual provisioning actions based on a single rule and target. Example: "Contractors cannot join admin groups" — one subject attribute, one resource pattern.

SoD (Separation of Duties) detects conflicts between multiple existing/requested roles. Example: "A payment approver cannot also be a vendor manager" — requires checking current_roles against the requested role.

9 How does birthright discovery use the search endpoint differently from standard action search?

Standard search (/access/v1/search) asks "what actions can user do on this resource?"

Birthright discovery uses /access/v1/search/resource to ask "what target systems should this user be provisioned to?" based on HR attributes like employee_type and Department. The response includes on_permit.context with birthright_groups, naming_system, and licenses.

10 What happens when pdp_application is missing from a request?

The PDP defaults to the "global" application, which typically only has baseline deny policies.

If the pdp_application value has an invalid format, the PDP returns HTTP 400. If the application ID is unknown/unregistered, the PDP returns HTTP 422 with a warning log.

📝Day 9 Checkpoint

  • Made basic evaluation requests to PDP
  • Included subject and resource properties in requests
  • Retrieved and interpreted decision explanations
  • Performed batch evaluations
  • Used search endpoint including /search/resource for birthright
  • Understand what PIPs are and how they work
  • Made IGA requests: guardrail check, SoD check, birthright discovery
  • Explored IGA policy files and understand the JSON format
  • Simulated a Joiner workflow end-to-end
💡 Ready to Author Policies?

For the JSON IR schema, IdP DCR policy examples, and how to register new applications, use the official EmpowerNow documentation together with your platform administrator.