---
name: identity-managed-identity
description: >-
  Azure service-to-service (workload) identity: managed identity, user-assigned identity, Azure
  RBAC, passwordless Azure SDK connections, AKS workload identity, Azure DevOps Workload Identity
  Federation, GitHub Actions OIDC to Azure, and federated credential troubleshooting. Use when the
  user says "use managed identity", "passwordless Azure auth", "remove connection strings or keys",
  "federated credential", "workload identity federation", "GitHub Actions OIDC to Azure", "AKS
  workload identity", or "DefaultAzureCredential". Do NOT use for human sign-in, MFA, Conditional
  Access, or B2C / External ID consumer login ; use a human-identity (Entra) skill instead.
compatibility: Azure CLI, Bicep or Terraform, Azure Identity SDK, Microsoft Entra workload identity federation
cowork:
  category: automation
metadata:
  last_verified: "2026-08-25"
---

# Identity & Managed Identity Skill

## Use When

| Condition | Detail |
|-----------|--------|
| **Should trigger** | User needs Azure service-to-service authentication with managed identity, workload identity federation, Azure RBAC, or passwordless Azure SDK access. |
| **Should NOT trigger** | User is designing human sign-in, MFA, Conditional Access, delegated Graph permissions, or app-layer authorization without an Azure workload identity problem. |
| **Nearby skill collision** | Use `entra-id-cac`, `entra-external-id`, or `api-security-review` when the main task is human identity, client auth, or API auth design rather than Azure workload identity. |

## Anti-Hallucination Rule (MANDATORY before writing managed identity or federated credential config)

Azure managed identity spans system-assigned vs user-assigned MI, AKS workload identity (federated credential with OIDC issuer), GitHub Actions OIDC, Azure DevOps Workload Identity Federation, `Azure.Identity` credential types per SDK language (.NET `DefaultAzureCredential` vs `WorkloadIdentityCredential` vs `ManagedIdentityCredential`; Python `DefaultAzureCredential` vs `WorkloadIdentityCredential`), and federated credential `subject` string formats (one format for GitHub Actions `repo:org/repo:ref:refs/heads/main` vs AKS `system:serviceaccount:<ns>:<sa>` vs Azure DevOps `sc://<org>/<project>/<service-connection>`). Wrong federated credential subject silently rejects tokens with confusing errors.

### Common LLM hallucination traps (community-verified)

These are patterns that LLMs frequently get wrong when generating identity code.

**LLM trap: Custom token caching**
```python
# WRONG - LLMs create manual token caching
import time
token_cache = {}
def get_token():
    if "token" in token_cache and token_cache["expires"] > time.time():
        return token_cache["token"]
    token = fetch_token_from_api()
    token_cache["token"] = token
    token_cache["expires"] = time.time() + 3600
    return token

# CORRECT - use DefaultAzureCredential (handles caching automatically)
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()
token = credential.get_token("https://management.azure.com/.default")
```

**LLM trap: Connection string auth**
```python
# WRONG - using connection strings instead of managed identity
connection_string = "DefaultEndpointsProtocol=https;AccountName=..."

# CORRECT - use managed identity
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient

client = BlobServiceClient(
    account_url=f"https://{account_name}.blob.core.windows.net",
    credential=DefaultAzureCredential()
)
```

**LLM trap: Custom credential chains**
```python
# WRONG - building custom auth chains
def get_credential():
    if os.getenv("AZURE_CLIENT_ID"):
        return ServicePrincipalCredential(...)
    elif os.getenv("MSI_ENDPOINT"):
        return ManagedIdentityCredential(...)
    else:
        return AzureCliCredential()

# CORRECT - use DefaultAzureCredential (handles all cases)
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()
```

**LLM trap: Federated credential subject format**
```python
# WRONG - using wrong subject format for GitHub Actions
subject = "github-actions"  # Too vague

# CORRECT - use the exact subject format
subject = f"repo:{org}/{repo}:ref:refs/heads/{branch}"
# For AKS: subject = f"system:serviceaccount:{namespace}:{serviceAccount}"
# For Azure DevOps: subject = f"sc://{org}/{project}/{serviceConnection}"
```

Before writing any managed identity assignment, federated credential subject, `Azure.Identity` credential type, or workload identity service account annotation, ground each identifier using one of these methods:

1. **Federated credential subject format**: GitHub Actions `repo:<org>/<repo>:ref:refs/heads/<branch>` or `repo:<org>/<repo>:pull_request` or `repo:<org>/<repo>:environment:<env>`. AKS workload identity: `system:serviceaccount:<namespace>:<service-account-name>`. Azure DevOps: `sc://<org>/<project>/<service-connection-name>`. Verify each format against the issuer's docs.
2. **OIDC issuer**: GitHub `https://token.actions.githubusercontent.com`; Azure DevOps legacy issuer `https://vstoken.dev.azure.com/<organization-id>` (the path segment is the **organization ID, not the tenant ID**), newer service connections instead use the Entra issuer `https://login.microsoftonline.com/<tenant-id>/v2.0` with subject `<entra-prefix>/sc/<org-id>/<service-connection-id>`; AKS cluster `az aks show --query oidcIssuerProfile.issuerUrl`. Issuer URL must match exactly. Read the actual value from the service connection / federated credential rather than assuming the shape.
3. **`Azure.Identity` credential type**: `DefaultAzureCredential` is convenient but probes many sources; use specific credential types in production (`WorkloadIdentityCredential`, `ManagedIdentityCredential` with explicit `clientId`).
4. **RBAC role assignment**: `az role assignment create --assignee <principal-id> --role "<role-name>" --scope <resource-id>` against the live target; verify role display name resolves to a definition.
5. **Propagation delay (two separate clocks)**: a newly created managed identity (its service principal becoming visible in Microsoft Entra) and a newly created **role assignment** becoming effective on the resource data plane are distinct delays. Role assignments are usually effective within ~5 minutes and can take up to ~30 minutes in the worst case. Do not run smoke tests immediately after deploy; gate first access behind bounded retry (see [references/troubleshooting.md](references/troubleshooting.md)).

Forbidden shortcuts:

- Do not write federated credential `subject` from memory; format is issuer-specific and exact-match.
- Do not assume `DefaultAzureCredential` chain order or composition; the chain has expanded over time. The .NET chain now contains ten credentials including a Broker credential (the VS Code credential requires the `Azure.Identity.Broker` package) and `AzureDeveloperCliCredential`; Python `azure-identity >= 1.14.0` no longer halts the chain when a developer credential fails to get a token. Pin `Azure.Identity` / `azure-identity` version before reasoning about chain order, and use `WorkloadIdentityCredential` explicitly in pod-identity scenarios.
- Do not assume the same federated credential works across GitHub branches; subject must match exactly (or use environments / pull_request patterns deliberately).
- Do not write AKS workload identity without enabling the AKS feature (`--enable-workload-identity --enable-oidc-issuer`). It is enabled at cluster creation, or in place on an existing cluster with `az aks update --enable-oidc-issuer --enable-workload-identity` - recreation is not required. Existing pods must be restarted to pick up the injected token after enablement.
- Do not write `client_id` of a user-assigned MI from memory; resolve at deploy time.

Known asymmetry traps (illustrative, verify each):

- Federated credential `audience` is `api://AzureADTokenExchange` (default), but some scenarios require a specific audience.
- AKS workload identity requires service account annotation `azure.workload.identity/client-id: <client-id>` AND pod label `azure.workload.identity/use: "true"` - missing either silently falls back to AKS kubelet identity.
- GitHub Actions `subject` for branch `main` is `repo:org/repo:ref:refs/heads/main` but for tag is `repo:org/repo:ref:refs/tags/v1` - different ref types.
- `Azure.Identity` `DefaultAzureCredential` in production may fall back to Azure CLI credential if managed identity probe fails, silently authenticating as the deployer.
- User-assigned MI vs system-assigned MI: system-assigned dies with the resource; user-assigned outlives it. Cannot convert one to the other.

### Safe degraded output when verification is blocked

If you cannot read AKS OIDC issuer URL, check `Azure.Identity` installed version, or run `az role assignment create`, do not produce production-shaped managed identity / federated credential config from memory. Federated credential subject format mismatch is the most common silent failure.

Instead, return:

1. **A labelled skeleton** with `[VERIFY]` markers on federated credential subject formats, OIDC issuer URLs, `Azure.Identity` credential type, role display names, AKS workload identity annotations/labels.
2. **Verification commands**: GitHub OIDC subject docs URL, `az aks show --query oidcIssuerProfile.issuerUrl`, Azure DevOps subject format docs URL, `Azure.Identity` installed package version.
3. **Identifier guess list** grouped by failure mode (federated credential subject mismatch / wrong OIDC issuer / `DefaultAzureCredential` falls back to deployer identity / AKS workload identity not enabled / system vs user-assigned MI confusion).
4. **A design-level artifact offer**: identity inventory (which workload needs which credential type), federated credential subject inventory per workflow/environment, role assignment matrix, OIDC issuer plan, propagation/test plan.

Do not produce speculative managed identity config under blocked verification. Subject-format mismatches and `DefaultAzureCredential` silent fallback to wrong identity are dominant pains.

## Non-negotiable rule

For new Azure projects, use managed identity or workload identity federation for service-to-service authentication. Do not introduce application secrets, shared keys, SAS tokens, publish profiles, certificate passwords, or connection strings with embedded credentials unless the target service has no supported identity-based alternative and the exception is explicitly documented with an owner, expiry date, and migration path.

## Use this skill when

- Replacing secrets, storage keys, SAS tokens, connection strings, or service principal secrets with Azure managed identity or federated identity.
- Configuring Azure compute to access Azure resources using `ManagedIdentityCredential`, `WorkloadIdentityCredential`, or a constrained credential chain.
- Assigning Azure RBAC or service-native data-plane permissions for application identities.
- Configuring AKS workload identity, Azure DevOps Workload Identity Federation, or GitHub Actions OIDC to Azure.
- Troubleshooting identity, role assignment, token exchange, or federated credential failures.
- Granting the deploying user or service principal access to provisioned resources (AKS cluster admin, PostgreSQL admin, Key Vault access policy) before those resources are needed.
- Capturing the deploying identity in pre-provision hooks for use in Bicep/Terraform role assignments.

## When NOT to Use

- Designing human sign-in, Microsoft Entra External ID/B2C, MFA, Conditional Access, consent, delegated Graph permissions, or UI login flows. For human identity, use `entra-id-cac` or `entra-external-id` instead.
- Designing business/application authorization models such as personas, entitlements, row-level security, or app roles, unless the implementation also requires Azure workload identity. For API authorization design, use `api-security-review` instead.
- Solving non-Azure identity federation unless Azure is the relying party or target resource.

## First response behaviour

Before producing code, role assignments, or remediation steps, determine and state:

1. **Runtime**: App Service, Function App, Container Apps, VM, AKS, Azure DevOps, GitHub Actions, local dev, or other.
2. **Effective identity**: system-assigned managed identity, user-assigned managed identity, workload identity, service connection identity, or app registration.
3. **Target resource and operation**: for example, read secret, send message, receive event, read blob, write blob, connect to SQL, deploy infrastructure.
4. **Permission model**: Azure RBAC, service-native RBAC, SQL contained database user, or a combination.
5. **Local auth posture**: whether shared-key/SAS/local auth can be disabled at creation time.
6. **Verification plan**: commands, tests, logs, and expected success/failure signals.

If any item is unknown, make a safe assumption, label it, and continue with a reversible pattern.

## Default decisions for new projects

| Decision area | Default |
| --- | --- |
| Workload on one Azure resource | System-assigned managed identity |
| Multiple resources share the same access or blue/green slots need stable identity | User-assigned managed identity |
| AKS pod access to Azure resources | Microsoft Entra Workload ID with a user-assigned managed identity |
| Azure Bot Service for a Teams / M365 agent (incl. published Foundry agents) | User-assigned managed identity (single-tenant) instead of an App Registration where supported - avoids creating an app registration when org policy blocks it |
| CI/CD to Azure | Workload Identity Federation/OIDC; no client secrets |
| Local development | Developer tool credential chain only; never production secrets |
| Production Azure SDK credential | Specific credential: `ManagedIdentityCredential` or `WorkloadIdentityCredential` |
| `DefaultAzureCredential` in production | Only when intentionally constrained and documented |
| Permissions | Narrowest data-plane permission at the narrowest practical scope |
| Local authentication | Disabled at resource creation where supported |
| IaC | Bicep/Terraform first; CLI for verification or one-off remediation only |

## Credential selection

Use deterministic credentials in production. Unrestricted `DefaultAzureCredential` is acceptable for local development because it improves developer experience, but it can hide which credential is actually used in production and may fall through to unexpected credential sources.

| Environment | Preferred credential |
| --- | --- |
| Local development | `ChainedTokenCredential` containing only approved developer credentials, or `DefaultAzureCredential` with excludes if needed |
| App Service, Functions, Container Apps, VM, VMSS | `ManagedIdentityCredential` |
| User-assigned managed identity | `ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedClientId(clientId))` or equivalent language option |
| AKS workload identity | `WorkloadIdentityCredential`, or a constrained chain that only allows workload identity |
| Azure DevOps pipeline code using Azure SDK | Service connection + `AzurePipelinesCredential` or task-native Azure login, depending on SDK/task support |
| GitHub Actions to Azure | GitHub OIDC + federated credential + `azure/login`; no `AZURE_CLIENT_SECRET` |
| Multi-language app implementation | Use [references/language-patterns.md](references/language-patterns.md) for SDK-specific examples |

### Choosing between DefaultAzureCredential, ManagedIdentityCredential, and WorkloadIdentityCredential

The three differ by **how the token is obtained**, not just by environment. Pick by mechanism:

| Credential | How it gets a token | Use when | Do not use when |
| --- | --- | --- | --- |
| [`ManagedIdentityCredential`](https://learn.microsoft.com/dotnet/api/azure.identity.managedidentitycredential) | Calls the Azure Instance Metadata Service (IMDS) token endpoint of the Azure-hosted compute | Workload runs on App Service, Functions, Container Apps, VM/VMSS, or other compute with a directly assigned system- or user-assigned managed identity | The token comes from a projected federated OIDC file (AKS), or the workload is not Azure-hosted compute |
| [`WorkloadIdentityCredential`](https://learn.microsoft.com/dotnet/api/azure.identity.workloadidentitycredential) | Exchanges a projected OIDC token (`AZURE_FEDERATED_TOKEN_FILE`) for a Microsoft Entra token via a federated credential | AKS pods with Microsoft Entra Workload ID, or any federated / projected-token scenario | Plain Azure-hosted compute with a directly assigned managed identity (use `ManagedIdentityCredential` instead) |
| `DefaultAzureCredential` | Probes an ordered chain of credentials until one returns a token (env, managed identity, workload identity, developer tools, …) | Local development convenience, where the same code should authenticate via whatever the developer is signed in with | Production - it can silently fall through to the deployer's developer identity (see the Quality Gate). Use a specific credential, or a constrained, documented chain |

Rule of thumb: in production, use the **specific** credential for the runtime (`ManagedIdentityCredential` for managed-identity compute, `WorkloadIdentityCredential` for federated/AKS). Reserve `DefaultAzureCredential` for local development or an intentionally narrowed, documented chain. For a self-selecting chain that works in both AKS and local dev without an environment check, see [references/language-patterns.md](references/language-patterns.md).

### Runtime binding rules

- If `AZURE_CLIENT_ID` is set, treat the workload as intentionally bound to a user-assigned managed identity or federated workload identity.
- If no user-assigned identity is configured, treat the workload as system-assigned.
- If two or more user-assigned identities (or a user-assigned plus a system-assigned identity) are attached and `AZURE_CLIENT_ID` is **not** set, the identity is ambiguous - IMDS cannot disambiguate and may fail or return a token for the wrong identity. Treat this as a configuration error: bind explicitly with the client ID, do not assume it falls back to system-assigned.
- Assign roles to the effective runtime principal, not to a similarly named app registration or another identity.
- Reuse credential instances. Register one credential instance in dependency injection or as a long-lived singleton; do not instantiate credentials per request.
- For direct token use outside Azure SDK clients, handle token lifetime and refresh. Prefer SDK clients that refresh tokens automatically.

```csharp
using Azure.Core;
using Azure.Identity;
using Azure.Storage.Blobs;

// App Service / Container Apps / VM — system- or user-assigned managed identity.
TokenCredential credential = builder.Environment.IsDevelopment()
    ? new ChainedTokenCredential(
        new AzureDeveloperCliCredential(),  // azd auth login
        new VisualStudioCredential(),
        new AzureCliCredential(),
        new AzurePowerShellCredential())
    : new ManagedIdentityCredential();

builder.Services.AddSingleton(credential);
builder.Services.AddSingleton(_ => new BlobServiceClient(
    new Uri($"https://{storageAccountName}.blob.core.windows.net"),
    credential));
```

For AKS workload identity, use a self-selecting chain that does not require an environment check - see [references/language-patterns.md](references/language-patterns.md) for the full pattern.

## Permission model selection

| Target | Permission model | Reference |
| --- | --- | --- |
| Storage Blob | Azure RBAC data-plane roles | [standards/rbac-patterns.md](standards/rbac-patterns.md) |
| Key Vault | Azure RBAC permission model + data-plane roles | [standards/rbac-patterns.md](standards/rbac-patterns.md) |
| Service Bus | Azure RBAC data-plane roles + local auth disabled | [references/messaging.md](references/messaging.md) |
| Event Hubs | Azure RBAC data-plane roles + local auth disabled | [references/messaging.md](references/messaging.md) |
| Cosmos DB for NoSQL | Cosmos DB native data-plane RBAC; local auth disabled | [references/cosmos-db.md](references/cosmos-db.md) |
| Azure SQL | Microsoft Entra authentication + contained database user + database roles | [references/azure-sql.md](references/azure-sql.md) |
| AKS pods | Microsoft Entra Workload ID | [references/aks-workload-identity.md](references/aks-workload-identity.md) |
| Azure DevOps pipelines | Azure Resource Manager service connection with Workload Identity Federation | [references/azure-devops-wif.md](references/azure-devops-wif.md) |
| GitHub Actions | GitHub OIDC federated credential + `azure/login` | [references/github-actions-oidc.md](references/github-actions-oidc.md) |
| Federated credential errors | Issuer, subject, audience, tenant, and identity checks | [references/troubleshooting.md](references/troubleshooting.md) |
| Terraform implementation | Identities, RBAC, and federated credentials | [references/terraform-patterns.md](references/terraform-patterns.md) |
| Landing-zone guardrails | Azure Policy and Resource Graph checks | [references/policy-guardrails.md](references/policy-guardrails.md) |

## IaC-first patterns

### System-assigned identity plus RBAC

```bicep
param location string = resourceGroup().location
param app_name string
param key_vault_name string

var key_vault_secrets_user = '4633458b-17de-408a-b874-0445c86b69e6'

resource app 'Microsoft.Web/sites@2024-04-01' = {
  name: app_name
  location: location
  identity: {
    type: 'SystemAssigned'
  }
}

resource key_vault 'Microsoft.KeyVault/vaults@2026-02-01' existing = {
  name: key_vault_name
}

resource key_vault_role_assignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(key_vault.id, app.identity.principalId, key_vault_secrets_user)
  scope: key_vault
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', key_vault_secrets_user)
    principalId: app.identity.principalId
    principalType: 'ServicePrincipal'
  }
}
```

### User-assigned identity plus RBAC

```bicep
param location string = resourceGroup().location
param service_name string
param storage_account_name string

var storage_blob_data_contributor = 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'

resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
  name: 'id-${service_name}'
  location: location
}

resource storage 'Microsoft.Storage/storageAccounts@2024-01-01' existing = {
  name: storage_account_name
}

resource blob_role_assignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(storage.id, identity.properties.principalId, storage_blob_data_contributor)
  scope: storage
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', storage_blob_data_contributor)
    principalId: identity.properties.principalId
    principalType: 'ServicePrincipal'
  }
}
```

## Deploying identity pattern

When infrastructure-as-code needs to grant the person or service principal running `azd up` (or `terraform apply`) access to a provisioned resource, capture the deploying identity before infrastructure creates the resource.

### Pre-provision hook (powershell)

```powershell
$principalId = az ad signed-in-user show --query id --output tsv
$accountType = az account show --query user.type --output tsv
$principalType = if ($accountType -eq 'servicePrincipal') { 'ServicePrincipal' } else { 'User' }
azd env set DEPLOYING_PRINCIPAL_ID $principalId
azd env set DEPLOYING_PRINCIPAL_TYPE $principalType
```

### Bicep consumption

The captured values flow through `main.parameters.json` using `${AZURE_DEPLOYING_PRINCIPAL_ID}` and `${AZURE_DEPLOYING_PRINCIPAL_TYPE}` environment-var substitution, then into a role assignment:

```bicep
param deployingPrincipalId string
param deployingPrincipalType string

var aks_cluster_admin_role = 'b1ff04bb-8a4e-4dc4-8eb5-8693973ce19b'

resource aks_admin_role_assignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(aksCluster.id, deployingPrincipalId, aks_cluster_admin_role)
  scope: aksCluster
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', aks_cluster_admin_role)
    principalId: deployingPrincipalId
    principalType: deployingPrincipalType
  }
}
```

### Common resources that need this pattern

| Resource | Typical role | Notes |
|---|---|---|
| AKS cluster | `Azure Kubernetes Service RBAC Cluster Admin` | Required when `disableLocalAccounts: true` |
| PostgreSQL Flexible Server | `Azure Database for PostgreSQL Flexible Server Administrator` | Used for Entra admin bootstrap |
| Key Vault | `Key Vault Secrets User` + `Key Vault Certificate User` | For initial secret/cert management |

## Output requirements

When this skill is used, produce outputs in this order:

1. **Decision summary**: chosen identity type, credential, permission model, and scope.
2. **Implementation**: Bicep/Terraform first, then app code, then CLI verification commands.
3. **Validation**: how to prove the identity is used, role assignments exist, local auth is disabled, and secret paths fail.
4. **Rollback**: safe rollback that does not reintroduce secrets unless approved as a temporary exception.
5. **Risks and assumptions**: RBAC propagation delay, tenant restrictions, unsupported SDKs, or local-auth limitations.

## Guardrails

- Never recommend `Owner`, `Contributor`, or management-plane roles for application data access.
- Never assign data-plane roles at subscription scope for application workloads.
- Never grant `User Access Administrator`, `Role Based Access Control Administrator`, or role-assignment permissions to application identities unless that is the application’s explicit purpose and a security review exists.
- Never use `listKeys`, `regenerateKey`, connection-string outputs, SAS generation, or publish profiles in app paths for new projects.
- Prefer separate identities per workload and environment. Do not share one deployment identity across unrelated apps or tenants.
- Pin federated credential subjects tightly to the expected Azure DevOps service connection, GitHub repository/environment/branch, or Kubernetes service account.
- For GitHub Actions, use environments and environment protection rules for production deployments.
- For Azure DevOps, do not grant service connections to all pipelines by default; authorize only the pipelines that need the connection.
- Confirm before disabling local authentication on an existing resource: verify no active consumer still depends on the key/SAS path, because the change takes effect immediately.

### Ownership, approval, and blast radius

- Name an owner for every role assignment and scope. Granting roles is itself a privileged action (`User Access Administrator` / `Role Based Access Control Administrator`) and needs an approver, least privilege must not quietly become "whoever ran the pipeline".
- Document every temporary secret exception with owner, **named approver**, expiry, compensating controls, a review cadence at expiry, and a migration issue. A "temporary" secret with no approver and no review becomes permanent.
- Treat blast radius explicitly: a shared, or subscription/resource-group-scoped, data-plane identity means every workload using it is compromised together. Rollback must be able to revoke one workload without breaking others.
- Revoking a role does not invalidate already-issued access tokens. Expect up to ~24 hours (or until the app restarts and re-authenticates) before access actually stops, plan incident response and rollback around that lag.
- For greenfield platforms, add Azure Policy or equivalent guardrails so insecure local-auth defaults cannot be recreated silently.

## Validation helpers

Use these scripts when reviewing repositories or generated IaC:

- `scripts/scan-secrets.sh` - scans files for obvious Azure secret and connection string patterns.
- `scripts/validate-identity-config.sh` - checks common Azure resources for local-auth and managed-identity posture.
- `scripts/list-identity-rbac.sh` - lists Azure RBAC assignments for a principal.

## Common failure diagnostics

| Symptom | First checks |
| --- | --- |
| `403 Forbidden` from target resource | Correct principal, correct scope, correct data-plane role, RBAC propagation delay |
| Azure SQL `Login failed for user '<token-identified principal>'` | Missing contained database user or database role |
| Cosmos DB `Forbidden` with Entra auth | Missing Cosmos native data-plane role assignment; Azure RBAC alone is insufficient for data access |
| Federated credential not found | Issuer, subject, audience, tenant, identity client ID, renamed repo/project/service connection |
| Wrong identity used | `AZURE_CLIENT_ID`, app settings, pod annotations, service connection, or credential chain fallback |
| Works locally but not in Azure | Developer identity has permissions that workload identity lacks |
| Secret path still works | Local auth/shared key not disabled or legacy secret still present |

## Resource Directories

Load these directories only when the selected workflow needs deeper examples, prompts, or reference material:

- [references/](references/) - service- and platform-specific implementation patterns (SQL, Cosmos, messaging, AKS, GitHub Actions, Azure DevOps, Terraform, language SDKs, policy, troubleshooting).
- [standards/](standards/) - identity selection, RBAC patterns, and the review checklist.
- [actions/](actions/) - the step-by-step "configure identity" runbook.

## Final Output Contract

Every engagement produces:
- A clear identity decision summary covering runtime, identity type, credential choice, and permission model.
- Concrete implementation guidance in the right order: IaC, app/runtime configuration, and verification commands.
- A permission and scope plan showing which principal gets which access and why.
- Validation, rollback, and risk notes that avoid reintroducing secrets as the default escape hatch.

## Quality Gate

Do not mark this engagement complete until:
- [ ] The chosen identity, target resource, and permission model are all explicit.
- [ ] The guidance includes both implementation steps and verification steps.
- [ ] Any issuer URLs, subject formats, role names, or SDK credential assumptions are verified or marked `[VERIFY]`.
- [ ] Production code uses a deterministic credential (`ManagedIdentityCredential` / `WorkloadIdentityCredential`), not an unconstrained `DefaultAzureCredential`. A startup check logs the resolved credential type and fails fast if it is not the expected managed identity (prevents silent fallback to the deployer's developer identity).

## Review checklist

Use [standards/checklist.md](standards/checklist.md) before considering the change complete.

## References

- [references/azure-sql.md](references/azure-sql.md)
- [references/cosmos-db.md](references/cosmos-db.md)
- [references/github-actions-oidc.md](references/github-actions-oidc.md)
- [references/azure-devops-wif.md](references/azure-devops-wif.md)
- [references/aks-workload-identity.md](references/aks-workload-identity.md)
- [references/messaging.md](references/messaging.md)
- [standards/rbac-patterns.md](standards/rbac-patterns.md)
- [references/troubleshooting.md](references/troubleshooting.md)
- [references/language-patterns.md](references/language-patterns.md)
- [references/terraform-patterns.md](references/terraform-patterns.md)
- [references/policy-guardrails.md](references/policy-guardrails.md)
- [actions/configure-identity.md](actions/configure-identity.md)

## Hidden API Surface Properties

The `Microsoft.ManagedIdentity/userAssignedIdentities` resource schema exposes properties absent from overview docs:

| Resource | Schema Property | Impact |
|---|---|---|
| `userAssignedIdentities/federatedIdentityCredentials` | `properties.claimsMatchingExpression` | Flexible federated credential with wildcard subject matching (`languageVersion` + `value`) — available from 2025-01-31-preview — alternatives to fixed `subject` |
| `userAssignedIdentities/federatedIdentityCredentials` | `properties.audiences` | Must include `api://AzureADTokenExchange` — often forgotten |
| `userAssignedIdentities/federatedIdentityCredentials` | Name constraint: 3-120 chars, `^[a-zA-Z0-9][a-zA-Z0-9-_]{2,119}$` | Naming rule for federated credential resources |
| `userAssignedIdentities` | `properties.principalId` (read-only) | The AAD object ID — needed for RBAC role assignments referencing the identity |
| `userAssignedIdentities` | `properties.clientId` (read-only) | The AAD application ID — needed for SDK `ManagedIdentityCredential` |
| `userAssignedIdentities` | `properties.tenantId` (read-only) | The home tenant ID — important for cross-tenant scenarios |
| System-assigned identity | `identity.type: 'SystemAssigned' \| 'UserAssigned' \| 'SystemAssigned,UserAssigned'` | The Bicep `identity` block shape for enabling system-assigned managed identity on Azure resources |
| Federated credential subject format | `subject: 'system:serviceaccount:<ns>:<sa>'` (AKS) or `repo:<owner>:<repo>:ref:refs/heads/<branch>` (GitHub) | Required subject format varies by issuer type |

**Flexible federated credentials (Preview)** — when a fixed `subject` is too rigid (for example, trusting all branches of one repo), use a `claimsMatchingExpression` instead. Constraints (per Entra docs, checked 2026-08-25): Preview; supported only for **federated credentials on app registrations** (not managed identities); supported issuers are GitHub, GitLab, and Terraform Cloud; operators are `matches` (wildcards `*`/`?`), `eq`, `and`; GitHub expressions must match `sub` **and** at least one immutable claim (`repository_id` / `repository_owner_id`). Azure CLI/PowerShell/Terraform do not yet support creating them — use Microsoft Graph via `az rest`:

```bash
az rest --method post \
  --url "https://graph.microsoft.com/beta/applications/<app-object-id>/federatedIdentityCredentials" \
  --body "{'name': 'fic-flexible', 'issuer': 'https://token.actions.githubusercontent.com', 'audiences': ['api://AzureADTokenExchange'], 'claimsMatchingExpression': {'value': \"claims['sub'] matches 'repo:contoso/contoso-repo:ref:refs/heads/*' and claims['repository_id'] eq '456789'\", 'languageVersion': 1}}"
```

## Currency and verification

- Date checked: 2026-08-25.
- Verify Azure API versions before final IaC: `az provider show --namespace <namespace> --query "resourceTypes[?resourceType=='<type>'].apiVersions" -o tsv`.
- Verify Azure.Identity package behaviour against the version used by the repo, especially when constraining `DefaultAzureCredential`.
- Prefer Microsoft Learn, GitHub Docs, SDK docs, and provider schemas over blog posts.

## Azure DevOps Workload Identity Federation: creation order changed (verified live 2026-08-25)

Since ADO sprint 253, newly created WorkloadIdentityFederation service connections default to the
**Microsoft Entra issuer** (`https://login.microsoftonline.com/{tenant}/v2.0` with subject
`/eid1/c/pub/t/{b64(tenant)}/a/{b64(appId)}/sc/{orgGuid}/{endpointGuid}`), NOT the classic
`https://vstoken.dev.azure.com/{orgGuid}` + `sc://{org}/{project}/{connectionName}` pair. The
Entra-flavour subject embeds the server-assigned **endpoint id**, so it is uncomputable before
creation.

1. Create the service connection FIRST, read back
   `authorization.parameters.workloadIdentityFederationIssuer` / `...Subject`, then create the
   federated credential from those exact values (docs: "automate-service-connections").
2. A brand-new SC/environment is a protected resource: pipeline runs park at
   `Checkpoint.Authorization` until you PATCH
   `{org}/{project}/_apis/pipelines/pipelinepermissions/endpoint/{id}` and `/environment/{id}`
   (api-version 7.2-preview.1, method PATCH, body `{"pipelines":[{"id":N,"authorized":true}]}`)
   - PUT returns 405.
3. Symptom of flavour mismatch: pipeline `az login --service-principal --federated-token ...`
   fails `AADSTS700211: No matching federated identity record found`.
4. The well-known Azure DevOps resource id `499b84ac-1321-427f-aa17-42cd2cad8a26` may not exist in
   every tenant; use the resource id your tenant actually registered (some CSP/CDX tenants expose a
   different GUID) - probe before hardcoding.
5. Multi-tenant apps: the vstoken issuer is deprecated (retirement 2027) but multi-tenant app
   registrations are explicitly excluded; single-tenant/MI targets should migrate to Entra issuer.
