Normalize composite channels for all user token operations#457
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes OAuth refresh/token operations for M365 Copilot Web activities that arrive with composite channelId values (e.g., msteams:COPILOT) by ensuring Bot Framework token API calls consistently use the base channel (msteams) while preserving the original composite channel in _FlowState for storage/continuation context.
Changes:
- Normalize
_OAuthFlow’s operational tokenchannel_idto the base segment viasplit(":", 1)[0]. - Add a focused regression test asserting
get_user_token()callsUserToken.get_token(channel_id="msteams")whileflow.flow_state.channel_idremains unchanged (msteams:COPILOT).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_oauth/_oauth_flow.py |
Derives _OAuthFlow’s token-operation channel_id from the base channel while preserving the composite channel in stored flow state. |
tests/hosting_core/_oauth/test_oauth_flow.py |
Adds regression coverage for get_user_token() with a composite channel ID. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@microsoft-github-policy-service agree |
ac6aa94 to
51aa996
Compare
|
Updated per the maintainer suggestion on #456: the implementation now normalizes at the |
|
Confirmed end-to-end in a deployed M365 Copilot custom-engine agent after resetting both the base and composite Bot Framework token partitions. Fresh-user-path resultA brand-new Copilot conversation completed silent SSO with no sign-in card and no client restart:
The original activity, reply context, and delivery telemetry continued to report The M365 client's Our telemetry pipeline duplicates stored span rows, so counts above were deduplicated by operation ID; the runtime console independently shows one This validates the boundary requested by the maintainer: normalize all Bot Framework token operations in |
rodrigobr-msft
left a comment
There was a problem hiding this comment.
I will need to perform some testing before approving this, just to check off all the bases, but it looks good.
rodrigobr-msft
left a comment
There was a problem hiding this comment.
Thanks for contributing!
TL;DR
Fixes #456.
M365 Copilot custom-engine activities arrive with a composite channel such as
msteams:COPILOT. Bot Framework user-token storage is partitioned by its base channel (msteams). PR #392 normalized only the initialGetTokenOrSignInResourcepath; every other PythonUserTokenoperation still sent the composite channel.In a deployed agent this split caused a deterministic silent-SSO loop:
GetTokenOrSignInResourcefound the resource;signin/tokenExchangestored the token under rawmsteams:COPILOT;msteams, found no token, and initiated sign-in again;signInInfoendpoint returned HTTP 429.This PR follows the maintainer's suggested design and the .NET implementation: normalize at the
UserTokenboundary so every Bot Framework token operation uses the same base-channel partition.Change
UserToken._base_channel_id()usesChannelId(...).channel, and all six operations normalize before telemetry and HTTP query construction:get_token_get_token_or_sign_in_resourceget_aad_tokensexchange_tokenget_token_statussign_outThis matches .NET
UserTokenRestClient, which useschannelId?.Channelacross these operations.Safety invariants
Activityis not mutated.msteams:COPILOTvalue.msteamsare unchanged.None, empty, whitespace-only, and malformed values are not broadened into an unscoped request.Regression coverage
The tests use a real local
aiohttpserver and execute all six operations, asserting every wire-levelchannelIdismsteams. They also cover omitted optional channels and malformed/edge values.Before this change, the composite-channel test observes
msteams:COPILOTon those requests; after it, all six usemsteams.Validation
Run from a clean Python 3.12 editable-install environment:
139 passed2984 passed, 88 skippedReviewer guide
_base_channel_id()and the six call sites inuser_token_client.py.UserTokenRestClientand the requested direction in [Bug] UserAuthorization refresh sends composite Copilot channel to GetToken #456.Related
force_base_channelsupport to conversation references for Copilot Web OAuth flows #392 (initial sign-in-resource normalization only)