Conversation
…ession ID into token claims When COPILOT_AGENT_SESSION_ID is set, inject xms_agent_session into the claims challenge and client_session into query parameters for MSAL token requests, enabling downstream services to distinguish agent-driven from human-driven operations.
❌AzureCLI-FullTest
|
️✔️AzureCLI-BreakingChangeTest
|
|
The git hooks are available for azure-cli and azure-cli-extensions repos. They could help you run required checks before creating the PR. Please sync the latest code with latest dev branch (for azure-cli) or main branch (for azure-cli-extensions). pip install azdev --upgrade
azdev setup -c <your azure-cli repo path> -r <your azure-cli-extensions repo path>
|
|
Thank you for your contribution! We will review the pull request and get back to you soon. |
There was a problem hiding this comment.
Pull request overview
This PR adds “agentic session” support to Azure CLI authentication so requests originating from an orchestrated agent context (e.g., Copilot) can be differentiated from manual sessions by attaching a session ID to token acquisition via MSAL.
Changes:
- Introduces
agentic_session.pyto build agentic-session claims and merge them into existing claims challenges. - Updates
UserCredential.acquire_tokento automatically add agentic session claims and aclient_sessionparameter whenCOPILOT_AGENT_SESSION_IDis present. - Adds unit tests covering
build_agentic_session_paramsandmerge_access_token_claims.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/azure-cli-core/azure/cli/core/auth/agentic_session.py |
New helper module to construct and merge agentic session token claims. |
src/azure-cli-core/azure/cli/core/auth/msal_credentials.py |
Injects agentic session ID into MSAL token acquisition (claims + params). |
src/azure-cli-core/azure/cli/core/auth/tests/test_agentic_session.py |
Adds unit tests for new agentic session helpers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| :param new_claims: New claims_challenge JSON string to merge in (or None). | ||
| :returns: Merged claims_challenge JSON string (or existing_claims if new_claims is None). |
There was a problem hiding this comment.
The docstring for merge_access_token_claims says it returns existing_claims when new_claims is None, but the implementation raises ValueError when new_claims is falsy. Please update the docstring to match the actual behavior (or adjust the function to follow the documented contract).
| :param new_claims: New claims_challenge JSON string to merge in (or None). | |
| :returns: Merged claims_challenge JSON string (or existing_claims if new_claims is None). | |
| :param new_claims: New claims_challenge JSON string to merge in. Must not be None or empty, | |
| and must contain a non-empty ``access_token`` object. | |
| :returns: Merged claims_challenge JSON string. | |
| :raises ValueError: If ``new_claims`` is None, empty, or does not contain a non-empty | |
| ``access_token`` object. |
| # Apply agentic session parameters for user identity flows | ||
| from .agentic_session import build_agentic_session_params, merge_access_token_claims | ||
| agentic_session_id, agentic_claims = build_agentic_session_params() | ||
| if agentic_session_id: | ||
| claims_challenge = merge_access_token_claims(claims_challenge, agentic_claims) | ||
| kwargs["params"] = kwargs.get("params") or {} | ||
| kwargs["params"]["client_session"] = agentic_session_id |
There was a problem hiding this comment.
By injecting xms_agent_session into claims_challenge, the existing info-level log later in this method will end up logging the agent session ID value. If the session ID is considered sensitive/correlation-only, consider redacting it (or logging only the presence of a claims challenge / specific keys) to avoid leaking it into logs.
| # Apply agentic session parameters for user identity flows | ||
| from .agentic_session import build_agentic_session_params, merge_access_token_claims | ||
| agentic_session_id, agentic_claims = build_agentic_session_params() | ||
| if agentic_session_id: | ||
| claims_challenge = merge_access_token_claims(claims_challenge, agentic_claims) | ||
| kwargs["params"] = kwargs.get("params") or {} | ||
| kwargs["params"]["client_session"] = agentic_session_id | ||
|
|
There was a problem hiding this comment.
The new agentic-session behavior in UserCredential.acquire_token (merging claims and adding the client_session param) isn't covered by tests. Consider adding a unit test that patches COPILOT_AGENT_SESSION_ID and uses a stubbed PublicClientApplication to assert the merged claims_challenge and passed-through kwargs params are correct.
…al sessions Agent-tagged tokens (with xms_agent_session claim) are now removed from the MSAL token cache immediately after acquisition. This ensures that subsequent manual (non-agent) CLI calls don't reuse agent-tagged tokens, which would break the 'same developer, different security posture' guarantee required by Entra Agentic Sessions. Agent calls always bypass the cache via claims_challenge, so this removal only affects the manual path. Cache cleanup is best-effort and will not fail the command if removal encounters an error. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Related command
az loginDescription
When Azure CLI runs inside an AI agent context (e.g., Copilot, Azure MCP Server), the orchestrator sets the
COPILOT_AGENT_SESSION_IDenvironment variable. This PR makes CLI read that env var and pass it to MSAL as both:client_session) so ESTS can identify the agentic sessionxms_agent_session) so ESTS embeds an agentic marker claim in the access token — and MSAL bypasses its token cache to ensure a fresh, agent-tagged token is always fetchedThis enables downstream systems (RBAC, Defender, Purview) to enforce differentiated policies for agent-driven vs. human-driven operations.
How it works:
agentic_session.pyprovidesbuild_agentic_session_params()andmerge_access_token_claims()UserCredential.acquire_token()inmsal_credentials.pycalls these helpers before everyacquire_token_silent_with_errorcallCOPILOT_AGENT_SESSION_IDis not set (normal CLI usage), behavior is completely unchanged — no claims are added, cache works normallyCOPILOT_AGENT_SESSION_IDis set, the session ID is injected into the claims challenge and query params, forcing MSAL to fetch a fresh token with the agentic markerTesting Guide
14 unit tests added in
test_agentic_session.py:TestBuildAgenticSessionParams:Nonewhen env var is not setNonewhen env var is empty stringTestMergeAccessTokenClaims:ValueErrorwhennew_claimsisNoneValueErrorwhennew_claimshas nullaccess_tokenNone(no existing claims)access_tokenkeys (e.g.,id_token)access_tokenkey when missing in existing claimsaccess_tokenin existing claimsRun tests:
History Notes
[Core]
az login: Support Entra agentic session differentiation for Copilot agent requestsThis checklist is used to make sure that common guidelines for a pull request are followed.
The PR title and description has followed the guideline in Submitting Pull Requests.
I adhere to the Command Guidelines.
I adhere to the Error Handling Guidelines.