Files
AutoGPT/docs/engineering/microsoft-local-e2e.md
Nicholas TindleandClaude Opus 5.5 25e1ea300c feat(platform): Continue with Microsoft, with the Microsoft 365 offer as a movable onboarding step (#16269)
> Builds on #16268, which merged into `dev` on 2026-10-30. This PR now
targets `dev`, and its diff is only its own changes.

### Why / What / How

**Why:** people whose work runs on Microsoft 365 had no way to sign in
with Microsoft, and connecting their mail and files came later as a
separate step most never found. The onboarding wizard also had a fixed
order, so trying the connection offer earlier or later meant a code
change.

**What:**

- **Continue with Microsoft** on the login and signup pages. It asks
Microsoft only for name and email (`openid profile email`, no Graph
permission).
- A new account's onboarding asks **"Also connect Microsoft 365?"**
using the panel from #16268. Skipping it is fine.
- The wizard's order is data, so an experiment can move the step without
a code change.

**How:**

- **Who signs in** (`lib/auth/microsoft-login.ts`,
`microsoft-sign-up-policy.ts`): Better Auth keys accounts on Microsoft's
object ID (`oid`), never the email address. An address counts only when
Microsoft vouches for it: a personal account's own Outlook.com, Hotmail,
Live or MSN mailbox, or a work address its organization verified
(`xms_edov`). A personal Microsoft account can carry any address and
keeps it after its owner loses the mailbox, and a work address without
`xms_edov` is whatever an admin typed in. A guest's address is never
trusted either: a personal account invited into someone else's
organization signs in with that organization's tenant, whose `xms_edov`
would vouch for it. A token counts as a member's only when its `idp` is
absent or exactly the issuing tenant's issuer. Any other address neither
joins an existing account nor creates one. Both answer Better Auth's
`account_not_linked`, the same redirect to the byte, so a refusal
doesn't reveal whether an AutoGPT account uses the address. /login gives
one message for both and points to email sign-up. Emailing a
confirmation link would not be safe: the mailbox's owner could confirm
an account that the other person's Microsoft login still opens.
- **Team addresses** (`microsoft-sign-up-policy.ts`): an AutoGPT team
address (`@agpt.co`) gets an account only through Google, however
Microsoft vouches for it. A refused one lands on
`/login?provider=microsoft&error=team_email_requires_google` with its
own message. A team member's existing account still signs in with
Microsoft. Microsoft's checks run first in the user-create hook, so its
sign-ups get these codes. #16445 applies the team-address and
verified-email rules to every provider; whichever of the two PRs merges
second keeps that hook order (Microsoft's check, then the team-address
check, then the verified-email check), and a trial merge of both passes
their sign-up tests together.
- **Only the redirect flow signs in**: Better Auth's ID-token sign-in
(`POST /sign-in/social` with `idToken`) is switched off for Microsoft,
because it creates accounts without the callback's checks; the sign-up
check also follows the provider rather than the callback path.
- **Refusals**: a Microsoft sign-up the signup gate refuses shows the
"Email Not Allowed" dialog on /login. Better Auth sends a refusal from
our checks back with its code as `error` (`SIGN_UP_NOT_ALLOWED` for the
gate, which `isWaitlistSignInError` reads). A blocked network, a
disposable address or one mail can't reach say the sign-up isn't allowed
instead of "try again", and a blocked sign-in says so too. The page
never shows the URL's own text. A refusal keeps the page's `next`, so a
deep link still opens after logging in another way. Since #15459 every
provider's refusal comes back to /login; Microsoft's also says
`provider=microsoft`, and only then does /login use the Microsoft
wording.
- **The access step** (`onboarding/steps/AccessStep/`): it shows when
the account signed in with a provider whose data Otto can use.
`accessProviders.tsx` maps each provider to its connection, so Google or
GitHub can join with one entry. `GET /api/auth/sign-in-providers` names
the account's sign-in providers, looked up once per account; Better
Auth's own `/list-accounts` stays disabled because it returns account
details.
- **Movable order** (`onboarding/store.ts`): `DEFAULT_STEP_ORDER` lists
the steps. The `onboarding-step-order` flag's payload names the steps to
lead with; the rest keep their default order, and `preparing` stays
last. With payments on, the access step always follows the subscription
step, whatever the experiment asks: a new account has no plan until
then, and connecting a service needs one. The order is frozen per
account when the wizard loads, through `useFrozenPerUser`, which the
payment snapshot now uses too (dev's ref read during render raised 14
`react-hooks/refs` warnings). Progress is saved by step name, so a
resumed wizard lands on the right step even if the order changed.

### Changes 🏗️

- Frontend auth: the Microsoft login provider (shown only when
`AUTH_MICROSOFT_CLIENT_ID`, `AUTH_MICROSOFT_CLIENT_SECRET` and
`NEXT_PUBLIC_AUTH_MICROSOFT_ENABLED` are set), the sign-up refusal for
unconfirmed addresses, error messages on /login, and Microsoft sign-ups
counted as such in analytics.
- Onboarding: the access step, `resolveStepOrder` (with the paywall
rule) and `buildStepLayout` with an `order`, `useFrozenPerUser`, and
`access` as a saved step name (backend `WizardStep`).
- Backend: the `onboarding-step-order` payload flag, and a seeded e2e
user who signed up with Microsoft.
- From CodeRabbit's and Sentry's reviews, once this targeted `dev`:
  - Login and signup start one provider sign-in at a time.
- The sign-in-provider lookup gives up 5 seconds after it starts, its
retry included, so it holds onboarding for at most 5 seconds. A quick
failure gets one retry in what's left of that time; a lookup that timed
out isn't retried.
- A saved draft keeps the role pick once the person moves past the role
step, in whatever order the wizard used. It reads that order from
`completedSteps`. With an experiment that asks the role first, drafts
saved on the steps after it used to leave the pick out until
`painPoints`.
- A regression from #16268: the Copilot executor again reports a refused
Microsoft refresh as `microsoft_365_copilot_credential_unavailable`, not
`…_not_found`. Since #16268 the refused refresh raised
`MicrosoftAuthError`, a `ValueError` subclass.
- With Better Auth 1.7.12 (#16419) merged from `dev`:
- The Microsoft refusals carry codes (`account_not_linked`,
`team_email_requires_google`). 1.7's OAuth callback only redirects an
error that has one; any other ends on a raw 403 JSON page.
  - /login explains the codes dev's own sign-up checks now send.
- Accounts are keyed on `oid`, as 1.7 does, instead of `${tid}:${oid}`.
Dev and prod have no Microsoft login rows yet, so nothing needs
migrating.
- The Playwright spec signs in through the API. The login form can be
replaced just after it loads, which dropped the email the spec typed
(smoke failed once on CI).
- From Pwuts's review:
- An address Microsoft didn't confirm gets `account_not_linked` with no
message, as described above.
- Sign-in no longer fetches the Microsoft profile photo, so it reads
only name and email.
- The Outlook.com domains are the 113 whose primary MX is Outlook.com's
consumer servers. That adds `hotmail.se`, `live.se` and the other
country domains, and drops `outlook.co.uk` (its mail goes to a third
party) and `hotmail.nl`, `hotmail.cz` and `outlook.cz` (a Microsoft 365
organization first).
- Both sign-in buttons' logos are decorative (`alt=""`), so a screen
reader says each provider once.
- `openapi.json` regenerated; `.secrets.baseline` line numbers updated.
- `docs/engineering/microsoft-local-e2e.md`: sign-in, the access step,
moving it, and the extra Entra settings.

**Deploying:** this builds on #16268, whose Entra step 1 (token version
2, then personal accounts in the sign-in audience) has to happen before
this release reaches an environment. The Entra app also needs the
`{frontend}/api/auth/callback/microsoft` redirect and the `xms_edov`
optional ID-token claim. Without the claim, work and school accounts
can't use Continue with Microsoft. Dev is configured; prod is not.

### Agents and large language models used

- Claude Code with Claude Opus 5.5: design, implementation, tests and
live testing.

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
- [x] `lib/auth/__tests__/microsoft-sign-in-flow.test.ts` runs Better
Auth's real handler with Microsoft's endpoints stubbed: an Outlook.com
personal account signs up; a verified work address joins the account
that uses it; a personal account's other address, an unverified work
address and a guest's address are kept out of it; an unconfirmed address
creates nothing, with email verification on or off; an ID token for an
Outlook.com address, signed with a test key served as Microsoft's key
set, can't sign in directly; and a waitlist refusal comes back as a code
/login recognizes. Before the fixes, the guest case joined the existing
account and the ID token created one; with `disableIdTokenSignIn`
removed, the ID token still signs in.
- [x] `vitest` over onboarding, login, signup, `lib/auth`, the Microsoft
panel and feature flags: 935 passed, including the access step offering
only "Continue" once connected and following the paywall when payments
are on. `pnpm types` and `pnpm lint` pass.
- [x] Backend: `pytest backend/data/onboarding_wizard_test.py
backend/util/feature_flag test/test_e2e_seed_credentials.py`: 284
passed.
- [x] After #16268 merged, with `dev` merged in (only the shared
engineering doc conflicted; this PR's copy, which extends #16268's, was
kept): backend 674 passed (onboarding wizard, feature flags, Microsoft
integrations, Copilot transports), black, isort and pyright clean, and
`openapi.json` matches the export. Frontend `pnpm types` and `pnpm lint`
pass, and all four `vitest` shards pass under `TZ=UTC` (12,700 tests).
- [x] The team-address rule: `microsoft-sign-in-flow.test.ts` drives the
real callback for an `@agpt.co` work address (refused, nothing created)
and for a team member's existing account (signed in), and the login page
test shows the message.
- [x] Screenshots, before (dev-builder) and after (this branch) at 1440
and 390 px, of /login and /signup with the Microsoft button, /login
after a refused sign-in, and the access step:
https://github.com/Significant-Gravitas/AutoGPT/pull/16269#issuecomment-6560941949
- [x] Playwright `microsoft-sign-in-happy-path.spec.ts`: a seeded
Microsoft sign-up opens onboarding on the offer, and "Not now" moves on.
It passed locally against a full stack and in CI's smoke run on 7ff101a.
  - [x] After #16419 (Better Auth 1.7.12) merged, with `dev` merged in:
- `microsoft-sign-in-flow.test.ts` on 1.7: before the fix, the
unconfirmed-address and team-address refusals got no redirect, and
accounts were keyed on `oid` alone.
- With the fix, `vitest` over `lib/auth`, login, signup, onboarding,
`api/auth`, `components/auth` and the auth callback: 1,249 passed.
- `pnpm types`, `pnpm lint` and `pnpm format` pass, detect-secrets
passes on the changed files, and backend ruff, isort and black are
clean.
  - [x] Live, on a local stack with the dev Entra app:
- [x] A work account that signed in with Microsoft lands on "Also
connect Microsoft 365?", connects through Microsoft's consent screen,
sees each service's status, and "Continue" moves the wizard on.
- [x] A personal account whose address belongs to an existing AutoGPT
account is refused: a personal Microsoft account on
[email protected] went through Microsoft's consent and came back
to /login with "An AutoGPT account already uses that email. Log in the
way you usually do.", and no Microsoft login was linked to that account.
Under the old rule it would have joined it.

- [x] After #16459 (payment as the `ENABLE_PLATFORM_PAYMENT` setting)
and #15853 (PostHog-only flags) merged, with `dev` merged in on
2026-11-03:
- Conflicts in `useOnboardingLayout.ts`, its test and the default mocks.
The step placement freezes on its own inputs (the step-order flag and
the sign-in lookup), and only the paywall rule follows #16459's payment
rule: off until a read succeeds, so a failed read that later succeeds
moves the access step behind the paywall and nothing else moves (Sentry
caught an earlier version that left the whole placement live). Tests
cover both cases, and breaking any of the four rules turns a test red.
- All four `vitest` shards under `TZ=UTC`: 1,185 files passed. `pnpm
types`, `pnpm lint` and `pnpm format` pass, and `openapi.json` matches
the export.
- Backend, against private Postgres, Redis and RabbitMQ: all of
`backend/api` plus the paywall-route, architecture and tool-schema
guards and this PR's suites, 6,085 passed. The 18 failures all come from
the local setup and are in code this PR doesn't touch: no ClamAV for
dev's fail-closed virus scanner (17) and a one-node Redis cluster (1).
CI has both.

- [x] After #16577 (Better Auth 1.7.16, the Nov 3 security release) and
#16511 (one role token set for colours) merged, with `dev` merged in on
2026-11-04:
    - The only conflict was the `.secrets.baseline` timestamp.
- The Microsoft sign-in flow tests, which run Better Auth's real
handler, pass on 1.7.16. This PR's 32 frontend files use none of the
colour names #16511 removed, and `pnpm lint` passes with its new rules.
- `vitest` over `lib/auth`, login, signup, onboarding, `api/auth`,
`components/auth`, the auth callback and the Microsoft panel: 1,318
passed, and `pnpm types` passes. Both ran on the first merge
(`ad22a2d`); nine more `dev` commits merged cleanly after that, sharing
only `openapi.json` with this PR. CI checks the final head, and
`openapi.json` matches the export there.
- Backend, against private Postgres, Redis and RabbitMQ: all of
`backend/api` plus the paywall-route, architecture and tool-schema
guards, the Copilot executor and background-subtask tests, and this PR's
suites. 6,501 passed; the same 18 failures as before all come from the
local setup (no ClamAV, a one-node Redis cluster).

- [x] After SECRT-3384 (new sign-ups go to onboarding, not Home) merged,
with `dev` merged in again on 2026-11-04:
- The conflicts were only the login and signup hooks' imports: dev
replaced `sanitizeAuthNext` with `getAuthDeepLink`, which the bodies
already used. The Microsoft sign-in passes the callback the same
filtered link, so a new Microsoft account also lands on onboarding,
where the Microsoft 365 step is.
- `vitest` over `lib/auth`, `auth-redirect`, login, signup, onboarding,
`api/auth`, `components/auth`, the auth callback, `PaywallGate`,
`TopUpPrompt`, the onboarding provider and the Microsoft panel: 1,474
passed, including dev's new onboarding-first tests that render the login
page with the Microsoft button. `pnpm types` and `pnpm lint` pass.
- Backend, against private Postgres, Redis and RabbitMQ: all of
`backend/api`, dev's changed guards (architecture, tool schema, the new
secrets guard), the Copilot executor tests and this PR's suites. 6,675
passed; the same 18 failures as before come from the local setup (no
ClamAV, a one-node Redis cluster).

- [x] After #16629 (the SECRT-3384 follow-up) merged, with `dev` merged
in on 2026-11-05:
- #16629 sends OAuth round trips the whole sanitized `next` (`safeNext`,
tracking parameters included) and lets `/auth/callback` decide. The
merge keeps `providerCallbackPath` and passes it `safeNext`, so Google's
callback URL is the one #16629 builds (its exact-string tests pass) and
Microsoft's adds `provider=microsoft` for sign-up attribution.
- `vitest` over the same areas plus #16629's new onboarding-provider
tests: 1,537 passed. `pnpm types` and `pnpm lint` pass.
- Backend: all of `backend/api`, the guards and the Copilot executor
tests (which cover dev's two `processor.py` changes beside this PR's
error code): 6,798 passed; the same 18 local-setup failures.

- [x] After #16308 (plan-first trial, hire step removed) merged, with
`dev` merged in on 2026-11-06:
- #16308 retires the hire step and shows the plan on its own when there
is no paywall. The merge takes `hire` out of `DEFAULT_STEP_ORDER`, so an
experiment can't bring it back, and shows the plan step wherever #16308
does. The access step still comes first by default and still follows the
paywall when payments are on. The longest layout is still 8 steps:
self-hosted, with the plan, connect and access steps.
- `vitest` over the same areas: 1,662 passed, all 45 onboarding files
included. `pnpm types` and `pnpm lint` pass, and `openapi.json` matches
the export.
- Backend: all of `backend/api` (dev's new onboarding plan tests
included), the guards and the Copilot executor tests: 7,120 passed; the
same 18 local-setup failures.

- [x] Review round (`c4a9807d78`), then `dev` merged in again on
2026-11-09 (`4b9c1ca07d`, 21 commits, no conflicts):
- Continue with Microsoft asks for `openid profile email` only: with the
profile photo off nothing calls Graph, so `User.Read` only stored an
unused Graph token. A refused Microsoft sign-in returns to `/login` with
the page's sanitized `next`. The access step's sign-in lookup is cached
per account, so a tab that switches account looks again. The tracking
plan lists `access` and says the step order varies. Each new test fails
with its fix undone.
- `vitest` over the same areas: 1,723 passed. `pnpm types` and `pnpm
lint` pass, and `openapi.json` matches the export.
- Backend: all of `backend/api`, every architecture guard (dev added
one), the Copilot executor tests, dev's OAuth refresh-failure suites
(#15081) beside the Microsoft integration suites, and this PR's suites:
7,414 passed; the same 18 local-setup failures.

- [x] After #15459 (self-serve account deletion) merged, with `dev`
merged in on 2026-11-10 (`ed4d09a856`):
- #15459 sends every provider's refused sign-in to /login and shows its
own notice for a banned account. The merge keeps one error page for
every provider (the login page with the sanitized `next`), marks
Microsoft's with `provider=microsoft`, and shows the Microsoft copy and
the "Email Not Allowed" dialog only for those, so a Google refusal never
mentions Microsoft and other providers' codes show nothing, as on dev.
#15459's banned notice comes before the Microsoft copy. Both new flags
are kept. New tests cover a Google refusal and a banned Microsoft
account.
- `vitest` over the same areas plus the feature flags: 1,829 passed.
`pnpm types` and `pnpm lint` pass, and `openapi.json` matches the
export.
- Backend, the same set: 7,632 passed; the same 18 local-setup failures.

#### For configuration changes:

- [x] `.env.default` is updated or already compatible with my changes
- [x] `docker-compose.yml` is updated or already compatible with my
changes
- [x] I have included a list of my configuration changes in the PR
description (under **Changes**): `AUTH_MICROSOFT_CLIENT_ID`,
`AUTH_MICROSOFT_CLIENT_SECRET` and `NEXT_PUBLIC_AUTH_MICROSOFT_ENABLED`
in `frontend/.env.default`, commented out; the Entra app changes are
under **Deploying**.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (Claude Code) <[email protected]>
2026-11-10 08:11:26 +00:00

12 KiB
Raw Permalink Blame History

Microsoft connection and sign-in

One Entra app per environment does everything Microsoft: the Teams bot, the Microsoft 365 Copilot chat route, mail/calendar/files/Teams access for Otto and workflows, Work IQ, and "Continue with Microsoft" on the login page.

What a person sees

Continue with Microsoft signs in with the account picker and asks only for name and email. A new account's onboarding then opens with an access step that offers the data connection; skipping it is fine. Existing accounts go straight where they were heading and can connect from Settings.

Connect Microsoft (Settings → Integrations, the Copilot card, or the access step) creates one microsoft connection per Microsoft account:

  • Personal accounts get mail, calendar and files.
  • Work or school accounts can add Teams chats, Microsoft 365 Copilot and Work IQ. Each service reports its own status: ready, needs your permission (one Allow click, or Allow everything listed), needs your admin, needs organization setup (Work IQ), or work accounts only.
  • An admin can choose Approve for my whole organization, after which everyone in that tenant gets every service in one click.
  • Work IQ is never asked for together with anything else, neither in "Allow everything listed" nor in the organization approval: until Microsoft has set Work IQ up for the organization, asking for it fails everything asked with it (AADSTS650052), and nothing shows that before someone asks. Its permission is admin-consent only (Microsoft marks WorkIQAgent.Ask AdminConsentRequired), so it shows "needs your admin" until an admin approves it, from its own Allow or for the whole organization. After that every existing connection gets Work IQ from its own refresh token, with no new sign-in.
  • If Microsoft stops accepting a connection (an expired or revoked grant), the panel asks the person to sign in again.

Copilot chats run on a work connection that has Copilot's permissions; there is no separate Copilot sign-in. Copilot connections made before this keep working until they are disconnected.

What each permission is for

The panel, the Copilot card and the admin setup page (/microsoft-setup) say this wherever someone grants a permission.

Service Permissions What it lets AutoGPT do
Mail Mail.ReadWrite, Mail.Send Otto reads, drafts and sends the person's email.
Calendar Calendars.ReadWrite Otto sees, adds and changes their calendar events.
Files Files.ReadWrite Otto opens, creates and edits their OneDrive files.
Teams chats Chat.ReadWrite Otto reads and sends messages in their Teams chats.
Microsoft 365 Copilot Copilot's read permissions (Sites.Read.All, Mail.Read, Chat.Read, …) Microsoft 365 Copilot answers chats routed to it, from what the account can open.
Work IQ WorkIQAgent.Ask Otto asks Work IQ, which can read and change the person's Microsoft 365 content.

Every sign-in also asks for User.Read and openid profile email offline_access, to identify the account and keep it connected. Today the Copilot chat route is the only thing that runs on the connection; Otto's mail, calendar, files, Teams and Work IQ tools follow it.

How it works

  • backend/integrations/oauth/microsoft.py: browser sign-in (authorization code with PKCE) and the sign-in-code fallback. Both produce the same credential, recording which app issued it.
  • microsoft_tokens.py: token requests. Every request goes out as the app that issued the grant; only the deployment's own app sends its secret. Work IQ tokens are minted from the same refresh token: a Microsoft refresh token isn't tied to one resource, and gets tokens for any resource the app has consent for, including consent an admin gives after the sign-in.
  • microsoft_services.py: services and their permissions. A sign-in may ask consent for Graph and Work IQ together; the stored token is always Graph.
  • microsoft_access.py: the per-service status and the organization approval link (POST /api/integrations/microsoft/check).
  • frontend/src/components/contextual/MicrosoftConnection/: the connection panel that Settings, the Microsoft 365 Copilot card and the access step share. A personal and a work account can share an address and stay separate connections.
  • frontend/src/app/(no-navbar)/onboarding/steps/AccessStep/: the access step. accessProviders.tsx maps a sign-in provider to the connection it offers; Google or GitHub join by adding an entry there.
  • frontend/src/lib/auth/microsoft-login.ts: the login provider. Better Auth keys accounts on Microsoft's object ID (oid), never the email address. An address only counts when Microsoft vouches for it: a personal account's own Outlook.com, Hotmail, Live or MSN address, or a work address with xms_edov. A guest's address is never trusted: a guest signs in through an organization that isn't its own, which vouches for addresses it never verified. Any other address joins no existing account (Better Auth answers account_not_linked) and creates none (microsoft-sign-up-policy.ts refuses with the same account_not_linked and no message, so the redirect is the same either way and doesn't reveal whether an account uses the address), whether or not email verification is on. "Outlook.com" here means the domains in MICROSOFT_MAIL_DOMAINS, each checked to route mail to Outlook.com's consumer servers. Sign-in reads only name and email: it asks for openid profile email and no Graph permission, and Better Auth's profile-photo fetch is switched off. Only the redirect flow signs in: Better Auth's ID-token sign-in is switched off for Microsoft. Emailing a confirmation link instead would not be safe: the mailbox's owner could confirm it, and the other person's Microsoft login would still sign in to the account. An AutoGPT team address (@agpt.co) gets an account only through Google, however Microsoft vouches for it (team_email_requires_google); a team member's existing account still signs in with Microsoft. The hook runs these Microsoft checks before the rules every provider shares, so a Microsoft sign-up gets these codes, which /login explains. Every provider's refused sign-in returns to /login with the page's next, so a deep link still opens after logging in another way. Microsoft's also carries provider=microsoft, and /login uses its Microsoft wording only then.

Moving the access step

The wizard's order is data (DEFAULT_STEP_ORDER in onboarding/store.ts), so an experiment can move a step without a code change. The onboarding-step-order flag's payload is a JSON list of step names. The steps it names lead, in its order; every other step follows in the default order, and preparing is always last. It only moves steps: which ones show still depends on the account (the access step needs a provider sign-in, the paywall needs payments on). With payments on, the access step always follows the subscription step, because connecting a service needs a plan and a new account has none until then. For example, without the paywall:

  • [] (or no flag): access, team, autopilot, role, painPoints, then subscription, which shows the plan on its own, ...
  • ["team", "autopilot", "role", "access"]: the access step after the role.

The order is fixed when the wizard loads, so changing the flag never reorders it under someone. Progress is saved by step name, so a later visit resumes at the right step even if the order changed in between. New steps, such as recommendations drawn from connected services where each service's terms allow it, slot into the same list.

Entra app settings (per environment)

On the environment's app (dev: autogpt-dev, prod: AutoGPT):

  1. Manifest: api.requestedAccessTokenVersion = 2, then signInAudience = AzureADandPersonalMicrosoftAccount. The order matters; the audience change fails without token version 2. The Teams bot is unaffected: it authenticates against the home tenant with its own secret.
  2. Web redirect URIs: {frontend}/auth/integrations/oauth_callback (data connection and organization approval) and {frontend}/api/auth/callback/microsoft (Continue with Microsoft).
  3. Token configuration: add the optional ID-token claim xms_edov. Without it, work and school accounts cannot use Continue with Microsoft.
  4. Certificates & secrets: the bot's existing secret also serves sign-in. Rotate by adding a new secret, deploying it, then removing the old one.
  5. API permissions: add the connection's delegated Microsoft Graph permissions: User.Read, openid, profile, email, offline_access, Mail.ReadWrite, Mail.Send, Calendars.ReadWrite, Files.ReadWrite, Chat.ReadWrite, and Copilot's Sites.Read.All, Mail.Read, People.Read.All, OnlineMeetingTranscript.Read.All, Chat.Read, ChannelMessage.Read.All and ExternalItem.Read.All. Sign-ins ask for their permissions as they go, so this changes nothing for them. It is what an admin approves when they grant consent outside AutoGPT: the Entra admin center and Microsoft's /adminconsent?client_id=… link, which /microsoft-setup points to in sign-in-code mode, approve only the permissions listed here. Leave Work IQ out: listed, it would fail that approval wherever Microsoft hasn't set Work IQ up (AADSTS650052).

Then set MICROSOFT_BROWSER_SIGN_IN=true. A secret alone doesn't turn browser sign-in on, because the bot's app has one long before it has the redirect. Until the flag is on, Microsoft connects with sign-in codes through the same app, at /common (the Copilot-only connection used /organizations). So step 1 is needed before an environment gets this release, not only before the flag: without it, personal accounts can't sign in. Continue with Microsoft has its own switch, below.

Dev's app has steps 1–4 (2026-10-26); step 5 and the flags remain. Prod needs step 1 before this release reaches it, and step 5 so an admin's approval covers every service; then steps 2 and 3 and the flags.

Environment variables

Where Setting Notes
backend MICROSOFT_CLIENT_ID, MICROSOFT_CLIENT_SECRET, MICROSOFT_TENANT_ID Shared with the Teams bot. Without a secret, connections use sign-in codes.
backend MICROSOFT_BROWSER_SIGN_IN=true Browser sign-in instead of sign-in codes, once the app has the redirect above.
frontend AUTH_MICROSOFT_CLIENT_ID, AUTH_MICROSOFT_CLIENT_SECRET Same app as the backend.
frontend NEXT_PUBLIC_AUTH_MICROSOFT_ENABLED=true Shows the login button.

Installs without an app secret

.env.default ships no Microsoft settings. The backend then uses AutoGPT's public client (ce01a4d6-…) with sign-in codes, so a fresh local install can still connect Microsoft and use Copilot. Browser sign-in and Continue with Microsoft need an app with a secret, the redirects above and their switches.

Testing locally

Run the normal AutoGPT stack behind one private HTTPS origin (for example a Tailscale listener), set the frontend and backend public URLs to it, and register its two callbacks on the dev app. Then:

  1. Log in, open Settings → Integrations → Connect Service → Microsoft, and sign in. Check each service's status, then try Allow everything listed on a work account.
  2. Open the Microsoft 365 Copilot card; a work connection with Copilot's permissions should already count as connected.
  3. Log out and use Continue with Microsoft with a personal and a work account. A new account's onboarding should open on "Also connect Microsoft 365?".

Work IQ also needs Microsoft's own setup in the tenant (the fdcc1f02-… service, admin approval and Copilot Credits billing). Until then its status is "Your organization still needs to set this up".