Lab 063: Agent Identity β Entra OBO Flow & Least PrivilegeΒΆ
What You'll LearnΒΆ
- How the OAuth 2.0 On-Behalf-Of (OBO) flow passes user identity through an agent chain
- The difference between delegated permissions (act as user) and application permissions (act as app)
- Identify compliance violations in agent permission configurations
- Apply least-privilege principles to agent identity design
- Implement human-in-the-loop gates for high-risk agent actions
- Analyze a 15-scenario dataset across 4 agents for security posture
IntroductionΒΆ
When agents access enterprise resources β reading email, querying databases, modifying SharePoint β they need an identity. How they authenticate determines the security posture of your entire system.
The On-Behalf-Of (OBO) flow ensures agents act with the user's identity and permissions, maintaining the principle of least privilege. The alternative β client_credentials (application permissions) β gives the agent its own identity with potentially broad access, bypassing user-level authorization.
This lab analyzes 15 real-world scenarios to show why OBO is the default choice and when client_credentials creates compliance risks.
The ScenariosΒΆ
You'll examine 15 scenarios across 4 agents, each with different permission configurations:
| Agent | Description | Scenarios |
|---|---|---|
| MailAgent | Reads and sends email on behalf of users | 4 |
| FileAgent | Accesses SharePoint and OneDrive files | 4 |
| CalendarAgent | Manages calendar events and scheduling | 4 |
| AdminAgent | Performs directory and compliance operations | 3 |
PrerequisitesΒΆ
This lab analyzes pre-computed scenario data β no Entra ID tenant, Azure subscription, or application registration required. To implement OBO flows in production, you would need an Entra ID tenant with app registrations.
π¦ Supporting FilesΒΆ
Download these files before starting the lab
Save all files to a lab-063/ folder in your working directory.
| File | Description | Download |
|---|---|---|
broken_identity.py |
Bug-fix exercise (3 bugs + self-tests) | π₯ Download |
identity_scenarios.csv |
Dataset | π₯ Download |
Part 1: Understanding OBO FlowΒΆ
Step 1: OBO vs client_credentialsΒΆ
The two primary authentication flows for agents:
OBO Flow (Delegated β Recommended):
User β [Auth] β Agent β [OBO token exchange] β Resource API
Agent acts AS the user β user's permissions apply
Client Credentials (Application β Use with caution):
Agent β [App secret/cert] β Resource API
Agent acts AS ITSELF β app permissions apply (often broader)
Key concepts:
| Concept | Description |
|---|---|
| OBO (On-Behalf-Of) | Agent exchanges user token for downstream API token, preserving user identity |
| Delegated permissions | Agent acts as the signed-in user β limited to user's own access |
| Application permissions | Agent acts as itself β can access all users' data (e.g., read ALL mailboxes) |
| Least privilege | Grant only the minimum permissions needed for the task |
| Human-in-the-loop | Require explicit user approval for high-risk actions |
Why OBO Matters
With client_credentials, a MailAgent could read every user's email β not just the requesting user's. OBO ensures the agent can only access what the user themselves can access. This is the difference between a controlled tool and a security liability.
Part 2: Load Scenario DataΒΆ
Step 2: Load π₯ identity_scenarios.csvΒΆ
The scenario dataset contains 15 identity configurations across 4 agents:
# identity_analysis.py
import pandas as pd
scenarios = pd.read_csv("lab-063/identity_scenarios.csv")
print(f"Scenarios: {len(scenarios)}")
print(f"Agents: {scenarios['agent'].unique().tolist()}")
print(f"Auth flows: {scenarios['auth_flow'].unique().tolist()}")
print(scenarios[["scenario_id", "agent", "auth_flow", "risk_level", "compliant"]].to_string(index=False))
Expected output:
Scenarios: 15
Agents: ['MailAgent', 'FileAgent', 'CalendarAgent', 'AdminAgent']
Auth flows: ['obo', 'client_credentials']
scenario_id agent auth_flow risk_level compliant
S01 MailAgent obo low True
S02 MailAgent obo low True
S03 MailAgent obo medium True
S04 MailAgent obo medium True
S05 MailAgent client_credentials critical False
S06 FileAgent obo low True
S07 FileAgent client_credentials critical False
S08 FileAgent obo medium True
S09 FileAgent obo low True
S10 CalendarAgent client_credentials critical False
S11 CalendarAgent obo low True
S12 CalendarAgent obo medium True
S13 AdminAgent obo high True
S14 AdminAgent client_credentials high False
S15 AdminAgent obo medium True
Part 3: Compliance AnalysisΒΆ
Step 3: Identify compliance violationsΒΆ
# Compliance violations
violations = scenarios[scenarios["compliant"] == False]
print(f"Compliance violations: {len(violations)}/{len(scenarios)}")
print("\nViolation details:")
print(violations[["scenario_id", "agent", "auth_flow", "risk_level", "description"]].to_string(index=False))
Expected output:
Compliance violations: 4/15
Violation details:
scenario_id agent auth_flow risk_level description
S05 MailAgent client_credentials critical Read all users' mail with app-level permissions
S07 FileAgent client_credentials critical Access all SharePoint sites without user context
S10 CalendarAgent client_credentials critical Modify any user's calendar without delegation
S14 AdminAgent client_credentials high Directory read with app permissions instead of OBO
Critical Finding
All 4 compliance violations use client_credentials β not OBO. Three are critical-risk (S05, S07, S10) because they grant broad access to all users' data. The pattern is clear: client_credentials without scoping creates compliance violations.
# Verify: do all violations use client_credentials?
violation_flows = violations["auth_flow"].unique().tolist()
print(f"\nAuth flows in violations: {violation_flows}")
print(f"All violations use client_credentials: {violation_flows == ['client_credentials']}")
Expected output:
Part 4: Risk Level AnalysisΒΆ
Step 4: Analyze risk distributionΒΆ
# Risk level distribution
print("Risk level distribution:")
for level in ["low", "medium", "high", "critical"]:
count = len(scenarios[scenarios["risk_level"] == level])
if count > 0:
print(f" {level:>8}: {count}")
# Critical-risk scenarios
critical = scenarios[scenarios["risk_level"] == "critical"]
print(f"\nCritical-risk scenarios: {len(critical)}")
print(critical[["scenario_id", "agent", "auth_flow", "description"]].to_string(index=False))
Expected output:
Risk level distribution:
low: 5
medium: 4
high: 3
critical: 3
Critical-risk scenarios: 3
scenario_id agent auth_flow description
S05 MailAgent client_credentials Read all users' mail with app-level permissions
S07 FileAgent client_credentials Access all SharePoint sites without user context
S10 CalendarAgent client_credentials Modify any user's calendar without delegation
Risk Pattern
All 3 critical-risk scenarios involve agents with client_credentials accessing user data (mail, files, calendar) without user context. The AdminAgent's client_credentials scenario (S14) is high-risk but not critical because directory reads are less sensitive than accessing individual users' data.
Part 5: OBO Flow AnalysisΒΆ
Step 5: OBO adoption rateΒΆ
# OBO vs client_credentials
obo_count = len(scenarios[scenarios["auth_flow"] == "obo"])
total = len(scenarios)
obo_pct = obo_count / total * 100
print(f"OBO flow: {obo_count}/{total} = {obo_pct:.1f}%")
print(f"Client credentials: {total - obo_count}/{total} = {(total - obo_count)/total*100:.1f}%")
# OBO by agent
print("\nOBO usage by agent:")
for agent in scenarios["agent"].unique():
agent_data = scenarios[scenarios["agent"] == agent]
agent_obo = len(agent_data[agent_data["auth_flow"] == "obo"])
agent_total = len(agent_data)
print(f" {agent:>15}: {agent_obo}/{agent_total} OBO")
Expected output:
OBO flow: 11/15 = 73.3%
Client credentials: 4/15 = 26.7%
OBO usage by agent:
MailAgent: 4/5 OBO
FileAgent: 3/4 OBO
CalendarAgent: 2/4 OBO
AdminAgent: 2/3 OBO
73.3% of scenarios use OBO β good, but the 26.7% using client_credentials accounts for all compliance violations. Every agent has at least one client_credentials scenario that should be reviewed.
Part 6: Remediation StrategyΒΆ
Step 6: Fix compliance violationsΒΆ
For each violation, the remediation is to switch from client_credentials to OBO:
| Scenario | Current | Fix | Notes |
|---|---|---|---|
| S05 | App reads all mail | OBO β read only requesting user's mail | Eliminates cross-user data access |
| S07 | App accesses all SharePoint | OBO β access only user's authorized sites | Respects site permissions |
| S10 | App modifies any calendar | OBO β modify only user's own calendar | Prevents cross-user modification |
| S14 | App directory read | OBO β directory read as user | Limits scope to user's directory view |
# Compliance improvement after remediation
compliant_count = scenarios["compliant"].sum()
total = len(scenarios)
print(f"Current compliance: {compliant_count}/{total} = {compliant_count/total*100:.1f}%")
print(f"After remediation: {total}/{total} = 100.0%")
print(f"\nAction: Convert {total - compliant_count} client_credentials scenarios to OBO")
Step 7: Human-in-the-loop for high-risk actionsΒΆ
Even with OBO, some actions warrant explicit user approval:
# High-risk + medium scenarios that should have human-in-the-loop
hitl_candidates = scenarios[scenarios["risk_level"].isin(["high", "critical", "medium"])]
print(f"Scenarios needing human-in-the-loop review: {len(hitl_candidates)}")
print(hitl_candidates[["scenario_id", "agent", "risk_level", "description"]].to_string(index=False))
Defense in Depth
OBO + least privilege + human-in-the-loop form three layers of defense. OBO ensures correct identity. Least privilege limits what that identity can do. Human-in-the-loop adds a confirmation step for sensitive actions β even if the agent has permission, the user explicitly approves.
π Bug-Fix ExerciseΒΆ
The file lab-063/broken_identity.py has 3 bugs in the identity analysis functions. Run the self-tests:
You should see 3 failed tests:
| Test | What it checks | Hint |
|---|---|---|
| Test 1 | Compliance violation count | Are you counting compliant == True instead of compliant == False? |
| Test 2 | Critical-risk count | Are you filtering for risk_level == "high" instead of risk_level == "critical"? |
| Test 3 | OBO percentage | Are you filtering for auth_flow == "client_credentials" instead of auth_flow == "obo"? |
Fix all 3 bugs and re-run until you see π All 3 tests passed.
π§ Knowledge CheckΒΆ
Q1 (Multiple Choice): What is the purpose of the OAuth 2.0 On-Behalf-Of (OBO) flow?
- A) To give agents their own independent identity with full admin access
- B) To pass the user's identity through the agent chain so the agent acts as the user
- C) To bypass authentication entirely for faster agent execution
- D) To create a new user account for each agent instance
β Reveal Answer
Correct: B) To pass the user's identity through the agent chain so the agent acts as the user
The OBO flow exchanges the user's token for a downstream API token, preserving the user's identity and permissions. The agent acts as the user β it can only access what the user can access. This is the foundation of least-privilege agent identity: the agent inherits the user's authorization scope, not a broad application-level scope.
Q2 (Multiple Choice): What is the key difference between delegated and application permissions?
- A) Delegated is faster; application is more accurate
- B) Delegated acts as the signed-in user; application acts as the app itself
- C) Delegated requires no authentication; application requires OAuth
- D) There is no practical difference β they are interchangeable
β Reveal Answer
Correct: B) Delegated acts as the signed-in user; application acts as the app itself
With delegated permissions (OBO), the agent acts as the user β it can read the user's own email but not other users' email. With application permissions (client_credentials), the agent acts as itself with app-level access β it could read ALL users' email. This distinction is critical: all 4 compliance violations in the benchmark use application permissions where delegated permissions should have been used.
Q3 (Run the Lab): How many compliance violations exist in the 15 scenarios?
Calculate (scenarios["compliant"] == False).sum().
β Reveal Answer
4 violations (S05, S07, S10, S14)
Four scenarios are non-compliant: S05 (MailAgent reads all users' mail), S07 (FileAgent accesses all SharePoint), S10 (CalendarAgent modifies any calendar), and S14 (AdminAgent directory read with app permissions). All four use client_credentials instead of OBO, granting broader access than necessary.
Q4 (Run the Lab): How many scenarios are classified as critical risk?
Calculate (scenarios["risk_level"] == "critical").sum().
β Reveal Answer
3 scenarios (S05, S07, S10)
Three scenarios are critical-risk: S05 (MailAgent), S07 (FileAgent), and S10 (CalendarAgent). All three involve agents using client_credentials to access user data (mail, files, calendar) without user context. S14 (AdminAgent) is high-risk but not critical because directory reads are less sensitive than accessing individual users' personal data.
Q5 (Run the Lab): What percentage of scenarios use the OBO flow?
Calculate (scenarios["auth_flow"] == "obo").sum() / len(scenarios) * 100.
β Reveal Answer
73.3% (11/15)
11 of 15 scenarios use OBO β a solid majority, but the remaining 4 (26.7%) using client_credentials account for all compliance violations. The remediation path is clear: convert all 4 client_credentials scenarios to OBO, bringing compliance from 73.3% to 100%. Every agent (MailAgent, FileAgent, CalendarAgent, AdminAgent) has at least one scenario that needs conversion.
SummaryΒΆ
| Topic | What You Learned |
|---|---|
| OBO Flow | Passes user identity through agent chain β agent acts as user |
| Delegated vs Application | Delegated = user scope; Application = app-wide scope |
| Compliance | 4/15 violations β all from client_credentials, not OBO |
| Risk Levels | 3 critical-risk scenarios β all client_credentials accessing user data |
| OBO Adoption | 73.3% OBO β the 26.7% client_credentials causes all violations |
| Remediation | Convert client_credentials to OBO; add human-in-the-loop for high risk |