This proof-of-concept demonstrates Microsoft Entra Agent ID (delegated user pattern) authenticating an AI agent to Azure DevOps REST API — without PATs, API keys, or stored secrets for ADO access.
The AI agent (powered by Azure OpenAI with function calling) can:
- ✅ Create work items (Issues)
- ✅ Push file changes to new branches
- ✅ Create pull requests
All authentication to Azure DevOps is handled by the Entra SDK Auth Sidecar using the Agent ID User (delegated) flow.
┌─────────────────────────────────────────────────────────────────────┐
│ Docker Compose (internal network only) │
│ │
│ ┌────────────────────────┐ ┌────────────────────────────┐ │
│ │ Entra SDK Sidecar │◄───────│ AI Agent (Flask + OpenAI) │ │
│ │ (token management) │────────► │ │
│ │ │ │ • Chat UI │ │
│ │ mcr.microsoft.com/ │ │ • Function calling loop │ │
│ │ entra-sdk/auth-sidecar│ │ • ADO REST API tools │ │
│ └────────────────────────┘ └────────────────────────────┘ │
│ │ │ │
│ │ FIC token exchange │ Bearer token │
│ ▼ ▼ │
│ ┌───────────────────────┐ ┌─────────────────────────────┐ │
│ │ Microsoft Entra ID │ │ Azure DevOps REST API │ │
│ │ (login.microsoft.com)│ │ (dev.azure.com) │ │
│ └───────────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
- Sidecar authenticates to Entra ID using Blueprint app credentials
- Agent requests token:
GET /AuthorizationHeaderUnauthenticated/AzureDevOps?AgentIdentity={id}&AgentUsername={upn} - Sidecar performs Federated Identity Credential (FIC) token exchange
- Returns Bearer token scoped to Azure DevOps (
499b84ac-1321-427f-aa17-267ca6975798) - Agent calls ADO REST API as the specified user
Security Note: The
AgentUsernameis configured via environment variable — it is NOT user-supplied. The sidecar is only accessible within the Docker network (no host port exposure).
| Component | Description |
|---|---|
| Blueprint App | App registration that acts as the "factory" for Agent Identity tokens |
| Agent Identity | Service principal (the individual agent) |
| Agent User | A user in your Entra tenant that the agent acts on behalf of |
| FIC Configuration | Federated Identity Credential linking Blueprint → Agent → User |
The Agent Identity (the parent of the Agent User) must hold the following delegated permissions, scoped only to the Agent User:
| Resource | Scope |
|---|---|
| Azure DevOps | user_impersonation |
| Microsoft Graph | openid profile email offline_access |
How to grant — these grants can only be created today via Microsoft Graph by POSTing an oAuth2PermissionGrant (one per resource, two total). Use these exact field values:
| Field | Value |
|---|---|
consentType |
Principal (per-user — not AllPrincipals) |
clientId |
Object ID of the Agent Identity service principal (not the Blueprint, not the User) |
principalId |
Object ID of the Agent User |
resourceId |
Object ID of the resource's Enterprise App service principal in your tenant (Microsoft Graph SP for the Graph grant; Azure DevOps SP for the ADO grant) |
scope |
Space-separated scope value from the table above |
- Organization must be backed by the same Entra tenant
- The Agent User must be a member of the ADO organization
- The Agent User needs:
- Access level: Basic (Stakeholders cannot access Code/Repos)
- Work Items: Read/Write on the target project
- Code: Contribute to the target repository
- Pull Requests: Create on the target repository
- Deployed model with function calling support (GPT-4o recommended)
- API key and endpoint
- Docker Desktop or equivalent container runtime
-
Clone this repository:
git clone <this-repo-url> cd agentid-azuredevops
-
Configure environment:
cp .env.example .env # Edit .env with your values -
Run:
docker compose --env-file .env up --build
-
Open the chat UI: http://localhost:4192
-
Try a prompt:
"Create an issue titled 'Update documentation', then add a file called docs/getting-started.md with a brief guide, and create a pull request for it. Request review from user@contoso.com"
| Prompt | What it does |
|---|---|
| "Create an issue titled 'Fix login timeout'" | Creates a work item assigned to the agent |
| "Push a file called hello.txt with 'Hello World' to a new branch and open a PR" | Full branch + commit + PR flow |
| "Create a PR for branch feature/docs and ask john@contoso.com to review" | PR with reviewer assignment |
| "Check comments on PR #12 and reply to any feedback" | Reads threads and responds |
| "What comments are on PR #5?" | Lists all review comments |
| "Reply to the reviewer on PR #12 thread 3 saying we'll fix it in the next commit" | Targeted reply |
See .env.example for all configuration options.
| Variable | Description |
|---|---|
TENANT_ID |
Your Entra tenant ID |
BLUEPRINT_APP_ID |
Blueprint app registration client ID |
BLUEPRINT_CLIENT_SECRET |
Blueprint app secret (dev only) |
AGENT_APP_ID |
Agent Identity service principal client ID |
AGENT_USERNAME |
UPN of the agent user (e.g. agent@contoso.com) |
AZURE_OPENAI_ENDPOINT |
Azure OpenAI endpoint URL |
AZURE_OPENAI_API_KEY |
Azure OpenAI API key |
AZURE_OPENAI_DEPLOYMENT |
Model deployment name |
ADO_ORG |
Azure DevOps organization name |
ADO_PROJECT |
Target project name |
ADO_REPO |
Target repository name |
User message → Azure OpenAI (with tool definitions)
│
▼ tool_calls
┌───────────────────────┐
│ execute_tool() │
│ 1. Get token from │
│ sidecar │
│ 2. Call ADO REST API │
│ 3. Return result │
└───────────────────────┘
│
▼ tool results
Azure OpenAI → final response → User
| Tool | ADO API | Description |
|---|---|---|
create_work_item |
POST /_apis/wit/workitems/$Issue |
Creates an Issue (auto-assigned to agent) |
push_file_change |
POST /_apis/git/repositories/{repo}/pushes |
Creates branch + pushes file |
create_pull_request |
POST /_apis/git/repositories/{repo}/pullrequests |
Creates a PR with optional reviewer |
get_pr_threads |
GET /_apis/git/repositories/{repo}/pullRequests/{id}/threads |
Gets all comment threads |
reply_to_pr_thread |
POST /.../threads/{id}/comments |
Replies to a review comment |
This is a demo/PoC. For production deployments:
- Replace
ClientSecretwithSignedAssertionFilePath(AKS Workload Identity) orSignedAssertionFromManagedIdentity - Use the sidecar to also authenticate Azure OpenAI (scope:
https://cognitiveservices.azure.com/.default) - Add proper error handling, retries, and circuit breakers
- Implement audit logging for all ADO operations
- Consider rate limiting on the chat endpoint
- Add input validation and sanitization
- Entra Agent ID Overview
- Entra SDK Auth Sidecar — Endpoints
- Entra SDK Auth Sidecar — Configuration
- Azure DevOps REST API Reference
- Azure OpenAI Function Calling
MIT