Authenticate Entra Agent ID agents
Let an agent that runs as a Microsoft Entra Agent ID agent identity authenticate to Keycard with its own Entra token. No secret in the agent or in Keycard.
Microsoft Entra Agent ID gives each agent its own agent identity, created from an agent identity blueprint. An agent identity has no credential of its own: the blueprint holds the credential and requests tokens on the agent identity’s behalf. The token the agent identity ends up with is an ordinary Entra app-only token whose subject is the agent identity.
Keycard accepts that token the same way it accepts any deployed workload’s token. You register your tenant as a Provider and give each agent identity an Application with a Workload Identity credential. Neither the agent nor Keycard holds a secret.
How it works
Section titled “How it works”flowchart LR
A[Managed identity] -->|"Token for the blueprint's federated credential"| B[Blueprint]
B -->|"Token with fmi_path = agent identity ID"| C[Agent identity]
C -->|"App-only token, sub = agent identity ID"| D[Keycard]
D -->|"Access token for the Resource"| E[Resource]
- The agent runs on an Azure service with a user-assigned managed identity. The blueprint has a federated identity credential that trusts that managed identity, so the managed identity’s token stands in for the blueprint’s credential.
- The agent requests the blueprint’s token with
fmi_pathset to the agent identity’s ID.fmi_pathnames the agent identity the blueprint is acting for. - The agent presents the blueprint’s token as the agent identity’s client assertion and gets the agent identity’s own token for Azure Resource Manager. That token’s
issis your tenant’s v1 issuer,https://sts.windows.net/<tenant-id>/, and itssubis the agent identity’s ID. - The agent sends that token to Keycard as its client assertion. Keycard matches the issuer and subject to a Workload Identity credential and authenticates the request as the agent identity’s Application.
Keycard matches on the subject, and the subject is the agent identity, so each agent identity is its own Application. The blueprint appears in the token only as the xms_par_app_azp claim. Microsoft recommends logging that claim and not authorizing on it, because a decision on the blueprint applies to every agent created from it.
Prerequisites
Section titled “Prerequisites”- Manager access to the Zone (see Roles & Permissions).
- The Azure CLI, signed in to your tenant with
az login --tenant <tenant-id>. When you sign in as a Global Administrator, the Azure CLI’s Microsoft Graph token can create every Entra object in this guide. - With a narrower Entra role, the Microsoft Graph permissions that Create an agent identity blueprint lists for each request.
- An Azure service to run the agent on, such as Container Apps, App Service, or a virtual machine, with a user-assigned managed identity attached.
- The Resource the agent will call, registered in your Zone.
Register your tenant as a Provider
Section titled “Register your tenant as a Provider”If your Zone already has a Provider for your tenant’s v1 issuer, skip this section. Service principals and managed identities that authenticate to Keycard with tokens for Azure Resource Manager use the same issuer, so one Provider serves them and your agent identities.
-
Add the Provider
In Keycard Console, navigate to Providers → Add Provider:
Field Value Name A label, for example Entra <tenant-name>Issuer URL https://sts.windows.net/<tenant-id>/, including the trailing slashLeave Client ID and Client Secret empty. Keycard discovers the tenant’s signing keys from the issuer’s OpenID Connect discovery document.
You can also create the Provider with the Keycard Terraform provider, using keycard_provider with oauth2.issuer set to the issuer above.
Create the blueprint and an agent identity
Section titled “Create the blueprint and an agent identity”These requests go to Microsoft Graph through az rest, and every one needs the OData-Version: 4.0 header. Your user ID, for the sponsor and owner fields, is the output of az ad signed-in-user show --query id -o tsv.
-
Create the blueprint
A blueprint needs a sponsor, the person accountable for its agents. This request makes you its sponsor and owner:
Terminal window az rest --method post --url https://graph.microsoft.com/v1.0/applications/ \--headers OData-Version=4.0 Content-Type=application/json \--body '{"@odata.type": "Microsoft.Graph.AgentIdentityBlueprint", "displayName": "<blueprint-name>","sponsors@odata.bind": ["https://graph.microsoft.com/v1.0/users/<your-user-id>"],"owners@odata.bind": ["https://graph.microsoft.com/v1.0/users/<your-user-id>"]}'Note the
appIdin the response. -
Create the blueprint principal
Terminal window az rest --method post --url https://graph.microsoft.com/v1.0/serviceprincipals/microsoft.graph.agentIdentityBlueprintPrincipal \--headers OData-Version=4.0 Content-Type=application/json \--body '{"appId": "<blueprint-app-id>"}' -
Trust the agent’s managed identity
A federated identity credential on the blueprint lets the managed identity’s token stand in for the blueprint’s credential. The managed identity must be in the same tenant as the blueprint. Its subject is the managed identity’s principal ID:
Terminal window az identity show --resource-group <resource-group> --name <managed-identity-name> --query principalId -o tsvaz rest --method post --url https://graph.microsoft.com/v1.0/applications/<blueprint-app-id>/federatedIdentityCredentials \--headers OData-Version=4.0 Content-Type=application/json \--body '{"name": "<credential-name>", "issuer": "https://login.microsoftonline.com/<tenant-id>/v2.0","subject": "<managed-identity-principal-id>", "audiences": ["api://AzureADTokenExchange"]}' -
Create the agent identity
Terminal window az rest --method post --url https://graph.microsoft.com/beta/serviceprincipals/Microsoft.Graph.AgentIdentity \--headers OData-Version=4.0 Content-Type=application/json \--body '{"displayName": "<agent-name>", "agentIdentityBlueprintId": "<blueprint-app-id>","sponsors@odata.bind": ["https://graph.microsoft.com/v1.0/users/<your-user-id>"]}'Note the
idin the response. It is the agent identity’s ID: thesubof its tokens and the subject of its credential in Keycard. Repeat this step for each agent you create from the blueprint.
Register the agent identity in Keycard
Section titled “Register the agent identity in Keycard”-
Create an Application for the agent identity
In Keycard Console, navigate to Applications → Add Application and name it after the agent identity. Don’t generate a client ID and secret for it.
-
Add a Workload Identity credential
On the Application’s Application Credentials tab, click Add credential and choose Workload Identity:
Field Value Provider The Provider for your tenant Subject The agent identity’s ID Console marks Subject optional. Set it anyway, because a credential with no subject matches no token.
Then give the Application access the same way as any agent that acts as itself. For MCP tools behind a Gateway, that is an Access Policy that names the Application’s identifier, as Autonomous Agent Access describes.
Get a Keycard token in the agent
Section titled “Get a Keycard token in the agent”The agent requests three tokens in Azure, then one from Keycard. This Python example uses msal for the blueprint’s request, which takes fmi_path, and azure-identity for the managed identity and the agent identity:
pip install "msal>=1.39" azure-identity httpximport os
import httpximport msalfrom azure.identity import ClientAssertionCredential, ManagedIdentityCredential
TENANT_ID = os.environ["ENTRA_TENANT_ID"]AGENT_IDENTITY_ID = os.environ["AGENT_IDENTITY_ID"]TOKEN_EXCHANGE_SCOPE = "api://AzureADTokenExchange/.default"
managed_identity = ManagedIdentityCredential(client_id=os.environ["MANAGED_IDENTITY_CLIENT_ID"])
blueprint = msal.ConfidentialClientApplication( client_id=os.environ["BLUEPRINT_APP_ID"], client_credential={"client_assertion": lambda: managed_identity.get_token(TOKEN_EXCHANGE_SCOPE).token}, authority=f"https://login.microsoftonline.com/{TENANT_ID}",)
def blueprint_token() -> str: # fmi_path names the agent identity the blueprint is acting for. result = blueprint.acquire_token_for_client([TOKEN_EXCHANGE_SCOPE], fmi_path=AGENT_IDENTITY_ID) if "access_token" not in result: raise RuntimeError(f"{result.get('error')}: {result.get('error_description')}") return result["access_token"]
agent_identity = ClientAssertionCredential( tenant_id=TENANT_ID, client_id=AGENT_IDENTITY_ID, func=blueprint_token,)entra_token = agent_identity.get_token("https://management.azure.com/.default").token
response = httpx.post( f"{os.environ['KEYCARD_ISSUER'].rstrip('/')}/oauth/2/token", data={ "grant_type": "client_credentials", "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "client_assertion": entra_token, "resource": os.environ["KEYCARD_RESOURCE_URL"], }, headers={"User-Agent": "<agent-name>"},)response.raise_for_status()access_token = response.json()["access_token"]| Variable | Value |
|---|---|
ENTRA_TENANT_ID |
Your tenant ID |
MANAGED_IDENTITY_CLIENT_ID |
The managed identity’s client ID, not its principal ID |
BLUEPRINT_APP_ID |
The blueprint’s appId |
AGENT_IDENTITY_ID |
The agent identity’s id |
KEYCARD_ISSUER |
Your Zone’s Issuer URL, from Settings → Connection |
KEYCARD_RESOURCE_URL |
The Resource the agent needs a token for |
The agent identity’s token is for Azure Resource Manager (https://management.azure.com/.default). That resource exists in every tenant, the agent identity needs no Azure role to get a token for it, and its tokens carry the v1 issuer your Provider trusts. The Keycard request has no client ID, because Keycard identifies the Application from the token. Send the access token to the Resource as a bearer token.
Register a blueprint with Terraform
Section titled “Register a blueprint with Terraform”A blueprint usually has many agent identities, and each needs its own Application and credential. This configuration lists a blueprint’s agent identities from Microsoft Graph with the msgraph provider and registers each one with the Keycard Terraform provider:
terraform { required_providers { keycard = { source = "keycardai/keycard" version = "~> 0.7" } msgraph = { source = "microsoft/msgraph" version = "~> 0.6" } }}
variable "zone_id" { type = string }variable "tenant_id" { type = string }variable "blueprint_app_id" { type = string }variable "identifier_prefix" { type = string } # For example urn:<org>:agent
# The Provider for your tenant's v1 issuer, not the /v2.0 one Entra sign-in usesdata "keycard_provider" "entra" { zone_id = var.zone_id identifier = "https://sts.windows.net/${var.tenant_id}/"}
data "msgraph_resource" "agent_identities" { url = "servicePrincipals/microsoft.graph.agentIdentity" api_version = "v1.0" headers = { "OData-Version" = "4.0" } query_parameters = { "$select" = ["id", "displayName", "agentIdentityBlueprintId"] } response_export_values = { agent_identities = "value[].{id: id, display_name: displayName, blueprint_app_id: agentIdentityBlueprintId}" }}
locals { agent_identities = { for agent in coalesce(data.msgraph_resource.agent_identities.output.agent_identities, []) : agent.id => agent.display_name if agent.blueprint_app_id == var.blueprint_app_id }}
resource "keycard_application" "agent" { for_each = local.agent_identities
zone_id = var.zone_id name = each.value identifier = "${var.identifier_prefix}:${each.key}"}
resource "keycard_application_workload_identity" "agent" { for_each = local.agent_identities
zone_id = var.zone_id application_id = keycard_application.agent[each.key].id provider_id = data.keycard_provider.entra.id subject = each.key}The keycard_provider lookup finds the Provider by its identifier, which Console and the Keycard provider set to the Issuer URL unless you give the Provider a different one. identifier_prefix starts each Application’s identifier, so with urn:<org>:agent an agent identity’s Application gets the identifier urn:<org>:agent:<agent-identity-id>.
The msgraph provider can use your Azure CLI sign-in, and the identity it runs as must be able to read agent identities in your tenant. The Keycard provider takes a Service Account’s client ID and secret, for example from the KEYCARD_CLIENT_ID and KEYCARD_CLIENT_SECRET environment variables. Each apply brings the Zone in line with the blueprint: an agent identity created since the last apply gets an Application and credential, and one deleted in Entra loses them. Access still comes from your Access Policies, so grant each new Application access the same way as the first.
Verify
Section titled “Verify”Run the agent, then open its Application in Keycard Console and select the Activity tab. Each credential Keycard issued to the agent identity is listed there. A request that a policy denied shows in the Audit Log with the agent identity’s Application as the actor.
Troubleshooting
Section titled “Troubleshooting”invalid_client: No token application credential configured for provider ‘…’ and subject ‘…’
Section titled “invalid_client: No token application credential configured for provider ‘…’ and subject ‘…’”Keycard has no Workload Identity credential for the subject in the token. If the subject is the agent identity’s ID, add the credential as in Register the agent identity in Keycard, or apply the Terraform above.
Keycard rejects the blueprint’s token
Section titled “Keycard rejects the blueprint’s token”Send Keycard the agent identity’s token, from the request with client_id set to the agent identity. The blueprint’s token has the v2 issuer, https://login.microsoftonline.com/<tenant-id>/v2.0, and a sub that isn’t the agent identity’s ID, so no credential matches it.
Entra refuses the blueprint’s token request
Section titled “Entra refuses the blueprint’s token request”The federated identity credential’s subject must be the managed identity’s principal ID, and the agent must pass the managed identity’s client ID. They are different values, and each place takes a different one.
The managed identity must also be in the blueprint’s tenant. Microsoft doesn’t let an application trust a managed identity from another tenant (see Configure an application to trust a managed identity). An agent that runs in another tenant needs a managed identity in that tenant.
What’s next
Section titled “What’s next”- Autonomous Agent Access: give the agent identity’s Application access to MCP tools through a Gateway.
- Authenticate deployed workloads: the Provider and Workload Identity credential model behind these steps.
- Agent identities in Microsoft’s documentation: blueprints, sponsors, and how Entra issues agent identity tokens.