Add "Continue with Microsoft" sign in for GitHub - #332948
Merged
Merged
Conversation
Brokers a GitHub session from an Entra token the built-in `microsoft` provider already holds, so someone signed in to Microsoft can reach Copilot without a second browser round trip. The flow is deliberately two exchanges. The first buys a `read:user` discovery token, just enough to `GET /user` and show which GitHub account the Entra identity maps to. Nothing is published until the user confirms that identity. The second exchange then mints the scopes the caller actually asked for. The discovery token is never persisted and never published as a session. Entra-brokered sessions live in memory for the life of the window and are never written to the Keychain. What survives a reload is the user's consent, recorded in global state as a GitHub label, a Microsoft label, and the GitHub user id. A fresh window mints the session again from that row, silently, re-verifying through discovery that the row still points at the same account. Rows are keyed by GitHub account label, because that is what VS Code itself keys an account by: `getAccounts` collapses sessions by label and the account preference is stored by label. The id is kept for one job only, checking that the token GitHub just returned belongs to the account the row names. Signing out of the Microsoft account drops the sessions, since nothing can renew them, but leaves the rows alone. The Microsoft account list is a per-window cache that reads empty for a moment while it repopulates, and the rows are global state shared by every window, so acting on a blink of that list would sign the user out everywhere with no way back. Dropping only the sessions self-heals: the next read mints them again from the row that is still there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Contributor
There was a problem hiding this comment.
Pull request overview
Adds Microsoft-backed GitHub authentication by exchanging Entra tokens for temporary GitHub sessions.
Changes:
- Adds token exchange, renewal, account-link persistence, and session restoration.
- Adds the Microsoft option to Copilot sign-in UI behind an experimental setting.
- Adds UI fixtures, icons, and unit coverage.
Show a summary per file
| File | Description |
|---|---|
sessionsSignInDialog.fixture.ts |
Adds Microsoft sign-in fixture. |
defaultAccount.test.ts |
Tests additive sign-in scopes. |
chatSetup.test.ts |
Tests Microsoft setup UI and routing. |
microsoft-light.svg |
Adds light Microsoft icon. |
microsoft-dark.svg |
Adds dark Microsoft icon. |
chatSetup.css |
Styles Microsoft provider icon. |
chatSetupRunner.ts |
Adds Microsoft button and strategy. |
chatSetupProviders.ts |
Adds provider fallback metadata. |
chatSetupController.ts |
Adds provider fallback metadata. |
chatSetup.ts |
Defines Microsoft setup strategy. |
chat.shared.contribution.ts |
Registers the experimental setting. |
chatSettings.ts |
Defines the setting identifier. |
product.ts |
Extends product provider configuration. |
product.json |
Configures provider and trusted access. |
testMemento.ts |
Adds in-memory test storage. |
flows.test.ts |
Tests provider validation. |
entraTokenExchange.test.ts |
Tests exchange and renewal behavior. |
accountLinks.test.ts |
Tests persisted account mappings. |
httpClient.ts |
Adds injectable HTTP implementation. |
githubServer.ts |
Integrates Microsoft token exchange. |
github.ts |
Manages transient sessions and restoration. |
flows.ts |
Adds Microsoft provider typing. |
entraTokenExchange.ts |
Implements two-stage token exchange. |
microsoftAuthentication.ts |
Wraps Microsoft authentication APIs. |
http.ts |
Defines HTTP abstractions. |
gitHubAccount.ts |
Extracts GitHub account lookup. |
accountLinks.ts |
Persists Microsoft–GitHub mappings. |
Review details
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Suppressed comments (2)
extensions/github-authentication/src/github.ts:380
- A successful ID check can return a renamed GitHub login. The session is then stored under the new label while the link remains keyed by the old label, so every later unscoped
getSessionsconsiders that row missing and performs another exchange, accumulating duplicate transient sessions. Rewrite the link when the verified login changed before publishing the restored session.
const session = this.sessionFor(renewed.account, renewed.token, [...renewed.scopes]);
this._transientSessions.set(session.id, { session, expiresAt: Date.now() + renewed.expiresIn * 1000 });
extensions/github-authentication/src/github.ts:507
- The renewal deliberately returns the freshly verified account, but spreading the old session discards it. If the GitHub login or avatar changed while the numeric ID stayed the same, the published session and account-link row remain stale, contrary to the ID-based rename handling. Update the session account and rewrite the link from
renewed.accountbefore firingchanged.
// The same session with a new token, so it keeps its id and is reported as changed rather
// than as one session going away and another arriving.
const next: vscode.AuthenticationSession = { ...session, accessToken: renewed.token };
this._transientSessions.set(next.id, { session: next, expiresAt: Date.now() + renewed.expiresIn * 1000 });
- Files reviewed: 25/27 changed files
- Comments generated: 7
- Review effort level: Balanced
The hygiene job fails on eslint warnings, and both warnings were in entraTokenExchange.test.ts: an `in` operator check and a double-quoted string outside of localization. The harness override is now a positive `noExchangeEndpoint` boolean, and the assertion uses single quotes. The browser test broke because main added @INativeManagedSettingsService to the DefaultAccountProvider constructor. The signIn helper now stubs both managed-settings services with their existing Null implementations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Verify the granted token against the account the user confirmed, not just the discovery token, and give that mismatch its own failure kind so a restore only forgets a link when GitHub positively names somebody else. Make a failed unlink write stick for the window that did it, so a sign out cannot leave a row behind that silently signs the user back in. Discard a token whose Microsoft account was signed out while the exchange was in flight, settle every expired session rather than only those with nothing to hand back, and warn when GitHub grants fewer scopes than asked. Adds a provider-level test suite driven through the real getSessions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TylerLeonhardt
marked this pull request as ready for review
August 27, 2026 16:19
TylerLeonhardt
enabled auto-merge (squash)
August 27, 2026 16:19
Ben Villalobos (benvillalobos)
approved these changes
Aug 27, 2026
This was referenced Aug 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lets someone already signed in to Microsoft reach Copilot without a second trip through the browser. The
microsoftprovider already holds an Entra token; this trades it for a GitHub one.How the flow works
Two exchanges, on purpose.
The first buys a
read:userdiscovery token, just enough to callGET /userand find out which GitHub account the Entra identity maps to. That account is shown to the user, and nothing is published until they confirm it. The second exchange then mints the scopes the caller actually asked for.The discovery token is never persisted and never published as a session. Neither token is ever logged.
What survives a reload
Entra-brokered sessions live in memory for the life of the window and never touch the Keychain. What persists is the user's consent: a row in global state holding the GitHub account label, the Microsoft account label, and the GitHub user id. A fresh window mints the session again from that row without prompting, re-running discovery to confirm the row still points at the same account.
Rows are keyed by GitHub account label, because that is what VS Code itself keys an account by.
getAccountscollapses sessions by label and the account preference is stored by label. A session read back from the Keychain can carry a placeholder id, an old numeric one, or one from a lookup that failed, so the id is no way to tell two accounts apart. The id has exactly one job here: checking that the token GitHub just returned belongs to the account the row names, which a label cannot do because logins get renamed and freed logins get taken.Signing out
Signing out of GitHub drops the row. Signing out of Microsoft drops the sessions, since nothing can renew them, but deliberately leaves the rows alone.
That asymmetry matters. The Microsoft account list is a per-window cache that legitimately reads empty for a moment while it repopulates, and the rows are global state shared by every window. Acting on a blink of that list would sign the user out everywhere with no way back. Dropping only the in-memory sessions self-heals: the next read mints them again from the row that is still there. Getting it wrong costs one round trip instead of the account.
Testing
37 unit tests pass across
accountLinks.test.ts,entraTokenExchange.test.ts,flows.test.ts, plus the workbench tests inchatSetup.test.tsanddefaultAccount.test.ts. End to end this was exercised through sign in, window reload, cross-window sign in from the Agents window, and both sign-out paths.Note for reviewers
extensions/github-authentication/src/config.tsis intentionally untouched here. The client secret this endpoint requires lives in vscode-distro, not in this repo.Draft while the endpoint registration is finalized.
🤖 Generated with Claude Code