Skip to content

FEAT: Add browser-session authentication for M365 Copilot - #2514

Open
Adrian Gavrila (adrian-gavrila) wants to merge 4 commits into
microsoft:mainfrom
adrian-gavrila:adrian-gavrila-design-m365-copilot-target
Open

FEAT: Add browser-session authentication for M365 Copilot#2514
Adrian Gavrila (adrian-gavrila) wants to merge 4 commits into
microsoft:mainfrom
adrian-gavrila:adrian-gavrila-design-m365-copilot-target

Conversation

@adrian-gavrila

@adrian-gavrila Adrian Gavrila (adrian-gavrila) commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Description

Adds interactive browser-session authentication for the M365 Copilot WebSocket target.

The new BrowserSessionCopilotAuthenticator:

  • Uses a persistent Microsoft Edge profile for user-driven authentication.
  • Supports MFA, Conditional Access, passwordless authentication, and account selection.
  • Captures Copilot tokens without storing user credentials.
  • Keeps tokens in memory and refreshes them on use near expiry.
  • Retains a minimized browser session for automatic token recapture.
  • Supports standalone scripts and Windows Jupyter notebooks.
  • Isolates personas through separate browser profile paths.
  • Preserves the existing automated-credential and manual-token authenticators.

This also updates the default M365 Copilot ChatHub endpoint to substrate.svc.cloud.microsoft while leaving the separate image-upload endpoint unchanged.

The M365 Copilot WebSocket protocol remains undocumented and may change.

Tests and Documentation

  • Added unit coverage for token validation, expiry handling, concurrent capture, cancellation cleanup, browser lifecycle, URL filtering, persona isolation, and event-loop ownership.
  • Existing Copilot authenticator and WebSocket target tests pass.
  • Ruff, ty, and repository pre-commit checks pass.
  • Live-tested text, multi-turn, parallel, image, standalone browser-session, Jupyter, and explicit token-recapture flows.
  • Added an executable browser-session authentication example to the paired Jupytext documentation.
  • Jupytext strict synchronization passes.
  • Natural near-expiry renewal soak test is pending.

@adrian-gavrila
Adrian Gavrila (adrian-gavrila) marked this pull request as ready for review August 28, 2026 20:05
@adrian-gavrila Adrian Gavrila (adrian-gavrila) changed the title [DRAFT] FEAT: Add browser-session authentication for M365 Copilot FEAT: Add browser-session authentication for M365 Copilot Aug 28, 2026
@behnam-o Behnam (behnam-o) self-assigned this Sep 2, 2026

@behnam-o Behnam (behnam-o) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me and works great (except the minor comment on URI, I had to change it to get it working locally.)


DEFAULT_TOKEN_CAPTURE_TIMEOUT_SECONDS = 60
DEFAULT_EXPIRY_BUFFER_SECONDS = 300
DEFAULT_WEBSOCKET_BASE_URL = "wss://substrate.svc.cloud.microsoft/m365Copilot/Chathub"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noticed sometimes the websocket base URL ends with StreamHub , and this won't pick those up ... good idea to allow for multiple URIs here, or look for just the wss://substrate.svc.cloud.microsoft/m365Copilot part?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh also, this is case sensitive? I accidentally typed Streamhub and it didn't work ... do we need to be that strict?

page = await self._ensure_browser_session_async()

def handle_websocket(websocket: Any) -> None:
self._handle_websocket_url(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: [discussed offline] maybe a good idea to explore: can we intercept the oauth calls and extract the auth tokens from those? that has the pro of being way more likely to still happen regardless of the websocket, even if copilot moves away from websockets, it still needs to get a token to authorize.

it looks like the copilot authenticator (username+password) does a similar thing? can they both share the same logic for extracting the token? one just drives the login automatically using username+password, and the other expects the user to login when username+password is not enough

but not a blocker at all, and just a suggestion/exploration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants