Skip to main content
The outlook pack calls Microsoft Graph with a delegated token from the Microsoft identity platform. One endpoint serves work and school accounts and personal Outlook.com accounts alike, and the segment in its path decides which of them can sign in. The app is registered in Microsoft Entra ID: Register a Microsoft Entra app has the screens.

The server

microsoft_server.py
common matches an app registered for any organisation and personal Microsoft accounts, the All Microsoft account users of the registration’s overview. The segment has to agree with the Supported account types you chose: An app registered for one organisation answers common with AADSTS50194. dead_grant_errors is how Microsoft says the user has to sign in again: Refresh behaviour. There is no revocation, because Microsoft gives your app nothing to send: Disconnecting.

Connecting a user

microsoft_users.py
The client takes a copy of the refresh token rather than reading the store before each refresh, as the Linear and Notion pages do. That’s safe here because Microsoft doesn’t retire a refresh token when it issues the next one: Refresh behaviour.

Scopes

Every Outlook tool declares the Graph permissions Microsoft’s reference accepts for its operation, least privileged first, and scopes_for picks the narrowest set that covers the tools you hand out. Hand out only the reading tools, and the consent screen asks for no write or send permission; hand out a reader and a writer, and it asks for Mail.ReadWrite alone, because that one reads too:
microsoft_scopes.py
Where an operation’s table lists only Mail.Read, Mail.ReadWrite is accepted too, because Microsoft’s permissions reference says it “allows the app to create, read, update, and delete” mail. Mail.ReadBasic is offered only to the folder readers: it carries no message body and no attachments. Graph refuses a permission the token lacks with 403 ErrorAccessDenied and names none, so InsufficientScope reports what the tool declares. Each scope is written with its resource, https://graph.microsoft.com/, which is how Microsoft tells a Graph permission from another API’s. offline_access is not a Graph permission and no tool declares it, so the flow adds it: without it, the code exchange returns no refresh token, and flow.exchange raises rather than store a connection that dies within the hour. Microsoft asks each user for what the link requests, whether or not the app’s API permissions list it. That list is what an administrator approves for a whole organisation. A grant covers the scopes it was issued for, so handing out a tool that needs a new one means sending the user through consent again.

Refresh behaviour

An access token lasts about an hour. Each refresh returns a new refresh token as well, and Microsoft doesn’t revoke the one it replaced, so a process holding an older copy can still refresh. Store the new one anyway: a refresh token lasts 90 days by default, so a connection that refreshes at least that often never lapses, and one left idle longer has to be connected again. A refresh that needs the user, such as a multifactor sign-in their organisation now requires, fails with interaction_required. Microsoft returns that code from its token endpoint, where the standard has it only at the authorization endpoint. Declared in dead_grant_errors, it gets the same handling as invalid_grant: the client stops asking the token endpoint for a minute at a time, and the CredentialError it raises has reauthorize set, so your app can show the user a reconnect button.

Disconnecting

Microsoft has no revocation endpoint an app can call with the user’s token. Ending a user’s grant takes an administrator’s permission over the whole directory, which your app doesn’t have. So MICROSOFT declares no revocation, and disconnecting is deleting your row:
microsoft_disconnect.py
SubjectProvider.revoke raises a DeclarationError for a server with no revocation, rather than report a disconnect that never happened. The grant itself lasts until its refresh token goes 90 days unused, or until the user removes your app. A personal account removes it at account.live.com/consent/Manage. In a work or school tenant, an administrator can remove the user’s grant.

Where the rest is